WU Trading Library 0.2.0
A backtesting and trading strategy library
Loading...
Searching...
No Matches
basic.c
Go to the documentation of this file.
1/**
2 * This file implements a basic portfolio management system that
3 * supports multiple assets with a shared cash pool. The portfolio can
4 * process buy and sell signals, manage positions for each asset, and
5 * calculate the total value and profit/loss (PnL) of the portfolio. It
6 * also includes features such as transaction costs, slippage,
7 * stop-loss, and take-profit mechanisms.
8 */
9
10#include <stdlib.h>
11#include <string.h>
12#include <math.h>
13#include <assert.h>
14#include "wu.h"
15
16/**
17 * Returns default portfolio parameters with reasonable values.
18 */
20 return (WU_PortfolioParams) {
21 .direction = WU_DIRECTION_LONG,
22 .initial_cash = 100000.0,
23 .risk_free_rate = 0.03, // 3% annual risk-free rate
24 .execution_policy = {
26 .execution_mean = 0.0,
27 .execution_stdev = 0.0,
29 .tx_cost_value = 0.001,
30 .stop_loss_pct = NAN,
31 .take_profit_pct = NAN
32 },
33 .borrow_params = {
34 .rate = 0.0,
35 .limit = 0.0
36 },
37 .position_sizing = {
38 .size_type = WU_POSITION_SIZE_PCT,
39 .size_value = 0.5
40 }
41 };
42}
43
44/**
45 * Helper function to calculate the time difference in years between
46 * two timestamps, accounting for different time units.
47 */
48static double calculate_years_held(WU_TimeStamp open_time,
49 WU_TimeStamp close_time) {
50 if (open_time.units != close_time.units) return 0.0;
51 int64_t diff = close_time.mark - open_time.mark;
52 double seconds;
53 switch (open_time.units) {
55 seconds = (double)diff;
56 break;
58 seconds = (double)diff / 1000.0;
59 break;
61 seconds = (double)diff / 1000000.0;
62 break;
64 seconds = (double)diff / 1000000000.0;
65 break;
66 default:
67 seconds = 0.0;
68 }
69 return seconds / (365.0 * 86400.0);
70}
71
72/**
73 * Calculates the execution price based on the execution policy.
74 * For IMMEDIATE and NEXT_CLOSE, returns the base price.
75 * For FIXED_SLIPPAGE, applies a fixed slippage percentage.
76 * For RANDOM_SLIPPAGE, applies random slippage based on mean and stdev.
77 */
78static inline
79double execution_price(double price, WU_ExecutionPolicy policy, bool is_buy) {
80 switch (policy.policy) {
83 return price;
85 double slippage = policy.execution_mean;
86 return price * (1.0 + (is_buy ? slippage : -slippage));
87 }
89 double mean = policy.execution_mean;
90 double stdev = policy.execution_stdev;
91 double random_factor = ((double)rand() / RAND_MAX - 0.5) * 2.0;
92 double random_slippage = mean + stdev * random_factor;
93 return price * (1.0 + (is_buy ? random_slippage : -random_slippage));
94 }
95 default:
96 return price;
97 }
98}
99
100/**
101 * Helper function to calculate the total value of all positions
102 * in a position vector at a given price.
103 */
104static double calculate_positions_value(WU_PositionVector* vec, double price) {
105 double value = 0.0;
106 for (int i = 0; i < vec->capacity; i++) {
107 bool found = false;
108 struct WU_Position_ pos = wu_position_get(vec, i, &found);
109 if (found) {
110 value += pos.quantity * price;
111 }
112 }
113 return value;
114}
115
116/**
117 * To calculate the total value of the portfolio, an iterative process
118 * is used to sum the value of each position across all assets, based on
119 * the last known price for each asset. The cash balance is then added
120 * to this total to get the overall portfolio value. This function has a
121 * complexity of O(N*M) where N is the number of assets and M is the
122 * average number of positions per asset. Although the performance can
123 * be an issue, it provides an accurate valuation of the portfolio at a
124 * given point in time. In the future, it can consider caching the total
125 * value and only recalculating when there are changes to positions or
126 * prices, to improve performance. Additionally, it can be optimized by
127 * maintaining a running total of the portfolio value that gets updated
128 * when positions modified.
129 */
130static double calculate_portfolio_value(const struct WU_Portfolio_ *portfolio)
131{
132 const WU_BasicPortfolio p = (const WU_BasicPortfolio) portfolio;
133 double position_value = 0.0;
134 for (int asset_idx = 0; asset_idx < p->num_assets; asset_idx++) {
135 WU_PositionVector* vec = p->positions[asset_idx];
136 position_value += calculate_positions_value(vec, vec->last_price);
137 }
138 return p->cash + position_value;
139}
140
141/**
142 * This function calculates the position size for a given signal based
143 * on the portfolio's position sizing parameters. It supports multiple
144 * sizing strategies, including absolute size, percentage of cash, equal
145 * percentage of portfolio, and strategy-guided sizing. The function
146 * takes into account the current price of the asset and the available
147 * cash in the portfolio to determine the appropriate quantity to buy or
148 * sell. It ensures that the calculated position size does not exceed
149 * the available cash for buy signals and can be adjusted based on the
150 * signal's quantity for strategy-guided sizing.
151 */
152static double calculate_position_size(WU_BasicPortfolio portfolio,
153 WU_Signal signal) {
154 struct WU_PositionSizingParams* ps = &portfolio->params.position_sizing;
155 double price = signal.price;
156 switch (ps->size_type) {
158 return ps->size_value;
160 double portfolio_value = calculate_portfolio_value(
161 (WU_Portfolio) portfolio);
162 // If signal.quantity is between 0 and 1, treat as target allocation %
163 // Otherwise use size_value as % of available cash
164 if (signal.quantity > 0.0 && signal.quantity < 1.0) {
165 double target_proportion = signal.quantity;
166 return portfolio_value * target_proportion * ps->size_value / price;
167 } else {
168 return (portfolio->cash * ps->size_value) / price;
169 }
170 }
172 double portfolio_value = calculate_portfolio_value(
173 (WU_Portfolio) portfolio);
174 double alloc_per_asset = portfolio_value / portfolio->num_assets;
175 double target_value = alloc_per_asset * ps->size_value;
176 return target_value / price;
177 }
179 double portfolio_value = calculate_portfolio_value(
180 (WU_Portfolio) portfolio);
181 double target_proportion = signal.quantity;
182 if (target_proportion < 0.0) target_proportion = 0.0;
183 if (target_proportion > 1.0) target_proportion = 1.0;
184 return portfolio_value * target_proportion * ps->size_value
185 / price;
186 }
187 default: // unreachable
188 return 0.0;
189 }
190}
191
192/**
193 * An auxiliary struct to hold the result of setting a buy position and
194 * calculating the total cost.
195 */
201
202/**
203 * Computes the total cost of a trade including transaction costs.
204 * Supports both fixed and proportional transaction cost models.
205 */
206static double compute_total_cost(double quantity, double price,
207 WU_ExecutionPolicy policy) {
208 double cost = quantity * price;
209 double tx_cost;
211 tx_cost = policy.tx_cost_value;
212 } else {
213 tx_cost = cost * policy.tx_cost_value;
214 }
215 return cost + tx_cost;
216}
217
218/**
219 * This function calculates the position size for a buy signal, applies
220 * execution policy to determine the effective price, and computes the total cost
221 * of the purchase including transaction costs. It also checks if the
222 * total cost exceeds the available cash in the portfolio, and if so,
223 * it adjusts the quantity to fit within the cash constraints. The result
224 * includes the position details (timestamp, quantity, price) and the
225 * total cost and transaction cost for the trade.
226 */
227static struct BuyPositionResult set_buy_position(WU_BasicPortfolio portfolio,
228 WU_Signal signal) {
229 double quantity = calculate_position_size(portfolio, signal);
230 double exec_price = execution_price(signal.price,
231 portfolio->params.execution_policy, true);
232 double total_cost = compute_total_cost(quantity, exec_price,
233 portfolio->params.execution_policy);
234
235 if (total_cost > portfolio->cash) {
236 double cost_multiplier;
237 if (portfolio->params.execution_policy.tx_cost_type == WU_TRANSACTION_COST_FIXED) {
238 cost_multiplier = exec_price + portfolio->params.execution_policy.tx_cost_value / quantity;
239 } else {
240 cost_multiplier = exec_price * (1.0 + portfolio->params.execution_policy.tx_cost_value);
241 }
242 quantity = portfolio->cash / cost_multiplier;
243 total_cost = compute_total_cost(quantity, exec_price,
244 portfolio->params.execution_policy);
245 }
246 return (struct BuyPositionResult) {
247 .position = {
248 .timestamp = signal.timestamp,
249 .quantity = quantity,
250 .price = exec_price,
251 .active = true
252 },
253 .total_cost = total_cost,
254 .tx_cost = total_cost - (quantity * exec_price)
255 };
256}
257
258/**
259 * This function calculates the total cost basis for all active
260 * positions in a given position vector. It iterates through each
261 * position, checks if it is active, and if so, it multiplies the
262 * quantity by the price to get the cost for that position, and sums it
263 * up to get the total cost basis. This total cost basis is used when
264 * closing positions to determine the profit and loss (PnL) from the
265 * trade, as it represents the total amount invested in those positions.
266 */
268 double total_cost = 0.0;
269 for (int i = 0; i < vec->capacity; i++) {
270 bool found = false;
271 struct WU_Position_ pos = wu_position_get(vec, i, &found);
272 if (found) {
273 total_cost += pos.quantity * pos.price;
274 }
275 }
276 return total_cost;
277}
278
279/**
280 * This function executes a buy signal. For portfolios without positions or
281 * with long positions, it creates new long positions. When there are short
282 * positions (negative quantity), it closes them (buy to cover). It updates
283 * the portfolio's cash balance and tracks transaction fees in stats.
284 */
285static void execute_buy(WU_BasicPortfolio portfolio, WU_Signal signal,
286 int asset_index) {
287 if (asset_index < 0 || asset_index >= portfolio->num_assets) return;
288 if (portfolio->params.direction == WU_DIRECTION_SHORT) return;
289
290 WU_PositionVector* vec = portfolio->positions[asset_index];
291 double total_quantity = wu_position_total_quantity(vec);
292
293 if (total_quantity < 0) {
294 // Closing short positions (buying to cover)
295 double abs_quantity = -total_quantity;
296 double total_cost = calculate_total_cost_basis(vec);
297 double abs_cost = -total_cost;
298 double buy_price = execution_price(signal.price,
299 portfolio->params.execution_policy, true);
300 double cost_to_close = abs_quantity * buy_price;
301 double tx_cost;
302 if (portfolio->params.execution_policy.tx_cost_type == WU_TRANSACTION_COST_FIXED) {
303 tx_cost = portfolio->params.execution_policy.tx_cost_value;
304 } else {
305 tx_cost = cost_to_close * portfolio->params.execution_policy.tx_cost_value;
306 }
307 double pnl = abs_cost - cost_to_close - tx_cost;
308 portfolio->cash -= cost_to_close + tx_cost;
309 portfolio->stats->accum_tx_fees += tx_cost;
312 }
313
314 // Check borrow limit
315 if (portfolio->cash < -portfolio->params.borrow_params.limit) return;
316
317 struct BuyPositionResult result = set_buy_position(portfolio, signal);
318 if (result.position.quantity <= 0) return;
319 wu_position_add(portfolio->positions[asset_index], &result.position);
320 portfolio->cash -= result.total_cost;
321 portfolio->stats->accum_tx_fees += result.tx_cost;
322}
323
324/**
325 * This function handles the logic for closing a position and updating
326 * the portfolio's cash balance and trading statistics. It calculates
327 * the proceeds from the sale, the transaction cost based on the defined
328 * transaction cost policy, and the profit and loss (PnL) from the
329 * trade. The portfolio's cash balance is updated by adding the proceeds
330 * and subtracting the transaction cost, and fees are tracked in stats.
331 */
332static void close_and_update_portfolio(WU_BasicPortfolio portfolio,
333 double quantity, double cost_basis, double sell_price,
334 WU_CloseReason reason) {
335 double proceeds = quantity * sell_price;
336 double tx_cost;
337 if (portfolio->params.execution_policy.tx_cost_type == WU_TRANSACTION_COST_FIXED) {
338 tx_cost = portfolio->params.execution_policy.tx_cost_value;
339 } else {
340 tx_cost = proceeds * portfolio->params.execution_policy.tx_cost_value;
341 }
342 double pnl = proceeds - cost_basis - tx_cost;
343 portfolio->cash += proceeds - tx_cost;
344 portfolio->stats->accum_tx_fees += tx_cost;
345 wu_portfolio_stats_record_trade(portfolio->stats, pnl, reason);
346}
347
348/**
349 * This function executes a sell signal. For long positions, it closes
350 * existing positions. For short and both-direction portfolios, it can
351 * initiate a short sale even without existing positions (borrowing assets).
352 */
353static void execute_sell(WU_BasicPortfolio portfolio, WU_Signal signal,
354 int asset_index) {
355 if (asset_index < 0 || asset_index >= portfolio->num_assets) return;
356
357 WU_PositionVector* vec = portfolio->positions[asset_index];
358 double total_quantity = wu_position_total_quantity(vec);
359 bool can_short = (portfolio->params.direction == WU_DIRECTION_SHORT ||
360 portfolio->params.direction == WU_DIRECTION_BOTH);
361
362 if (total_quantity > 0) {
363 // Closing long positions
365 double sell_price = execution_price(signal.price,
366 portfolio->params.execution_policy, false);
367 close_and_update_portfolio(portfolio, total_quantity, total_cost,
368 sell_price, WU_CLOSE_REASON_SIGNAL);
370 } else if (total_quantity < 0) {
371 // Closing short positions (buying to cover)
372 double abs_quantity = -total_quantity;
374 double abs_cost = -total_cost;
375 double buy_price = execution_price(signal.price,
376 portfolio->params.execution_policy, true);
377 double cost_to_close = abs_quantity * buy_price;
378 double tx_cost;
379 if (portfolio->params.execution_policy.tx_cost_type == WU_TRANSACTION_COST_FIXED) {
380 tx_cost = portfolio->params.execution_policy.tx_cost_value;
381 } else {
382 tx_cost = cost_to_close * portfolio->params.execution_policy.tx_cost_value;
383 }
384 double pnl = abs_cost - cost_to_close - tx_cost;
385 portfolio->cash -= cost_to_close + tx_cost;
386 portfolio->stats->accum_tx_fees += tx_cost;
389 }
390
391 if (can_short && total_quantity == 0) {
392 // Calculate total current short exposure
393 double total_short_value = 0.0;
394 for (int i = 0; i < portfolio->num_assets; i++) {
395 double qty = wu_position_total_quantity(portfolio->positions[i]);
396 if (qty < 0) {
397 total_short_value += (-qty) * portfolio->positions[i]->last_price;
398 }
399 }
400
401 // Check borrow limit
402 double quantity = calculate_position_size(portfolio, signal);
403 double exec_price = execution_price(signal.price,
404 portfolio->params.execution_policy, false);
405 double new_short_value = quantity * exec_price;
406
407 if (total_short_value + new_short_value > portfolio->params.borrow_params.limit) return;
408
409 double proceeds = new_short_value;
410 double tx_cost;
411 if (portfolio->params.execution_policy.tx_cost_type == WU_TRANSACTION_COST_FIXED) {
412 tx_cost = portfolio->params.execution_policy.tx_cost_value;
413 } else {
414 tx_cost = proceeds * portfolio->params.execution_policy.tx_cost_value;
415 }
416
417 struct WU_Position_ short_pos = {
418 .timestamp = signal.timestamp,
419 .quantity = -quantity,
420 .price = exec_price,
421 .active = true
422 };
423 wu_position_add(vec, &short_pos);
424 portfolio->cash += proceeds - tx_cost;
425 portfolio->stats->accum_tx_fees += tx_cost;
426 }
427}
428
429/**
430 * This function checks if a position should be closed based on the
431 * current price and the portfolio's stop-loss and take-profit
432 * thresholds. For short positions (negative quantity), the PnL logic
433 * is inverted. NAN values disable stop-loss or take-profit.
434 */
435static bool close_position(WU_BasicPortfolio portfolio,
436 struct WU_Position_ pos, double current_price) {
437 bool is_short = (pos.quantity < 0);
438 double pnl_pct = is_short ?
439 (pos.price - current_price) / pos.price :
440 (current_price - pos.price) / pos.price;
441 bool should_close = false;
443 if (!isnan(portfolio->params.execution_policy.stop_loss_pct) &&
444 portfolio->params.execution_policy.stop_loss_pct > 0 &&
445 pnl_pct <= -portfolio->params.execution_policy.stop_loss_pct) {
446 should_close = true;
448 }
449 if (!isnan(portfolio->params.execution_policy.take_profit_pct) &&
450 portfolio->params.execution_policy.take_profit_pct > 0 &&
451 pnl_pct >= portfolio->params.execution_policy.take_profit_pct) {
452 should_close = true;
454 }
455 if (!should_close) return false;
456
457 double abs_quantity = is_short ? -pos.quantity : pos.quantity;
458 double price = is_short ?
459 execution_price(current_price, portfolio->params.execution_policy, true) :
460 execution_price(current_price, portfolio->params.execution_policy, false);
461 double cost_basis = abs_quantity * pos.price;
462 close_and_update_portfolio(portfolio, abs_quantity, cost_basis,
463 price, reason);
464 return true;
465}
466
467/**
468 * This function iterates through all active positions for a given asset
469 * and checks if any of them can be closed based on the current price
470 * and the portfolio's stop-loss and take-profit thresholds. If a
471 * position is closed, then it is removed from the position vector.
472 */
473static void check_and_close_positions(WU_BasicPortfolio portfolio,
474 int asset_index, double current_price) {
475 assert(asset_index >= 0 && asset_index < portfolio->num_assets);
476 WU_PositionVector* vec = portfolio->positions[asset_index];
477 if (vec->count == 0) return;
478 for (int i = 0; i < vec->capacity; i++) {
479 bool found = false;
480 struct WU_Position_ pos = wu_position_get(vec, i, &found);
481 if (!found) continue;
482 if (close_position(portfolio, pos, current_price))
483 wu_position_remove(vec, i);
484 }
485}
486
487/**
488 * The profit and loss (PnL) of the portfolio is calculated by taking
489 * the current total value of the portfolio (cash + value of all
490 * positions) and subtracting the initial cash invested. This provides a
491 * measure of the overall performance of the portfolio since inception.
492 */
493static double calculate_portfolio_pnl(const struct WU_Portfolio_ *portfolio) {
494 WU_BasicPortfolio p = (WU_BasicPortfolio) portfolio;
495 double current_value = calculate_portfolio_value(portfolio);
496 return current_value - p->params.initial_cash;
497}
498
499/**
500 * Helper function to calculate total value of all short positions across
501 * all assets in the portfolio.
502 */
503static double calculate_total_short_value(WU_BasicPortfolio p) {
504 double total_short_value = 0.0;
505 for (int i = 0; i < p->num_assets; i++) {
506 double qty = wu_position_total_quantity(p->positions[i]);
507 if (qty < 0) {
508 total_short_value += (-qty) * p->positions[i]->last_price;
509 }
510 }
511 return total_short_value;
512}
513
514/**
515 * Helper function to update last prices, apply borrow interest on short
516 * positions, check exits, and update stats with current portfolio state.
517 */
518static void update_prices_and_check_exits(WU_BasicPortfolio p,
519 const WU_Signal* signals, int count) {
520 // Get current timestamp from first valid signal
521 bool has_valid_signal = false;
522 WU_TimeStamp current_time;
523 for (int i = 0; i < count && !has_valid_signal; i++) {
524 if (wu_signal_validate(&signals[i])) {
525 current_time = signals[i].timestamp;
526 has_valid_signal = true;
527 }
528 }
529
530 if (!has_valid_signal) return;
531
532 // Apply borrow interest if we have short positions
533 double total_short_value = calculate_total_short_value(p);
534 if (total_short_value > 0.0 && p->params.borrow_params.rate > 0.0 &&
535 p->last_update_time.mark != 0) {
536 double years_elapsed = calculate_years_held(p->last_update_time, current_time);
537 double borrow_interest = total_short_value * p->params.borrow_params.rate * years_elapsed;
538 p->cash -= borrow_interest;
539 p->stats->accum_borrow_interest += borrow_interest;
540 }
541
542 p->last_update_time = current_time;
543
544 // Update prices and check exits
545 for (int i = 0; i < count; i++) {
546 WU_Signal signal = signals[i];
547 if (!wu_signal_validate(&signal)) continue;
548 p->positions[i]->last_price = signal.price;
549 check_and_close_positions(p, i, signal.price);
550 }
551
552 // Update stats with current portfolio state
553 double pf_value = wu_portfolio_value((WU_Portfolio)p);
554 wu_portfolio_stats_update(p->stats, p->cash, pf_value, current_time);
555
556 // Update position info in stats
557 for (int i = 0; i < p->num_assets; i++) {
558 double qty = wu_position_total_quantity(p->positions[i]);
559 double value = wu_basic_portfolio_asset_value(p, i);
561 p->positions[i]->symbol, qty, value, p->positions[i]->last_price);
562 }
563}
564
565/**
566 * Helper function to process all sell signals. It iterates through the
567 * signals, validates them, and if a signal is a sell signal, it calls
568 * the execute_sell function to close the corresponding positions. This
569 * function should be called first to ensure that all sell signals are
570 * handled before processing buy signals, which is important for proper
571 * cash management and to avoid situations where buy signals are ignored
572 * due to insufficient cash after processing sell signals.
573 */
574static void process_sell_signals(WU_BasicPortfolio p,
575 const WU_Signal* signals, int count) {
576 for (int i = 0; i < count; i++) {
577 WU_Signal signal = signals[i];
578 if (!wu_signal_validate(&signal)) continue;
579 if (signal.side == WU_SIDE_SELL) execute_sell(p, signal, i);
580 }
581}
582
583/**
584 * Helper function to count valid buy signals.
585 */
586static int count_buy_signals(const WU_Signal* signals, int count) {
587 int buy_count = 0;
588 for (int i = 0; i < count; i++) {
589 if (wu_signal_validate(&signals[i])
590 && signals[i].side == WU_SIDE_BUY) {
591 buy_count++;
592 }
593 }
594 return buy_count;
595}
596
597/**
598 * Helper function to check if signals use strategy-guided allocation.
599 */
600static bool uses_strategy_guided_allocation(WU_BasicPortfolio p,
601 const WU_Signal* signals, int count) {
602 if (p->params.position_sizing.size_type == WU_POSITION_SIZE_PCT) {
603 for (int i = 0; i < count; i++) {
604 if (wu_signal_validate(&signals[i]) && signals[i].side == WU_SIDE_BUY) {
605 if (signals[i].quantity > 0.0 && signals[i].quantity < 1.0) {
606 return true;
607 }
608 }
609 }
610 }
611 return (p->params.position_sizing.size_type == WU_POSITION_SIZE_STRATEGY_GUIDED ||
612 p->params.position_sizing.size_type == WU_POSITION_SIZE_PCT_EQUAL);
613}
614
615/**
616 * Helper function to process all buy signals. If cash splitting is
617 * enabled, it divides the available cash equally among the buy signals
618 * to ensure that the total allocated cash does not exceed the
619 * portfolio's cash balance. Each buy signal is processed sequentially,
620 * and the cash balance is updated after each purchase to reflect the
621 * remaining cash for subsequent buys. If cash splitting is not enabled,
622 * each buy signal is processed with the full available cash, which may
623 * lead to some buy signals being ignored if the cash runs out. This
624 * function ensures that the portfolio's cash management is consistent
625 * with the defined position sizing strategy and prevents
626 * over-allocation of funds when multiple buy signals are present.
627 */
628static void process_buy_signals(WU_BasicPortfolio p,
629 const WU_Signal* signals, int count, int buy_count) {
630 bool use_cash_splitting = !uses_strategy_guided_allocation(p, signals, count);
631 double cash_per_signal = use_cash_splitting
632 ? (p->cash / buy_count) : 0.0;
633 for (int i = 0; i < count; i++) {
634 WU_Signal signal = signals[i];
635 if (!wu_signal_validate(&signal)) continue;
636 if (signal.side != WU_SIDE_BUY) continue;
637 if (use_cash_splitting) {
638 double original_cash = p->cash;
639 p->cash = cash_per_signal;
640 execute_buy(p, signal, i);
641 double cash_used = cash_per_signal - p->cash;
642 p->cash = original_cash - cash_used;
643 } else {
644 execute_buy(p, signal, i);
645 }
646 }
647}
648
649/**
650 * Helper function to execute pending orders from previous update.
651 * This is used for NEXT_CLOSE execution policy.
652 */
653static void execute_pending_orders(WU_BasicPortfolio p) {
654 if (!p->pending_orders) return;
655
656 for (int i = 0; i < p->num_assets; i++) {
657 WU_Signal pending = p->pending_orders[i];
658 if (!wu_signal_validate(&pending)) continue;
659
660 if (pending.side == WU_SIDE_BUY) {
661 execute_buy(p, pending, i);
662 } else if (pending.side == WU_SIDE_SELL) {
663 execute_sell(p, pending, i);
664 }
665
666 // Clear the pending order
667 p->pending_orders[i].side = WU_SIDE_HOLD;
668 }
669}
670
671/**
672 * The update_portfolio function is the core of the portfolio management
673 * logic. It processes incoming signals to update the portfolio's state.
674 * For IMMEDIATE execution, signals are processed right away.
675 * For NEXT_CLOSE execution, signals are stored and executed on the next update.
676 * The function first executes any pending orders from previous update,
677 * then updates prices and checks for exits, processes sell signals,
678 * and finally processes buy signals (or stores them as pending).
679 */
680static void update_portfolio(WU_Portfolio portfolio,
681 const WU_Signal* signals) {
682 WU_BasicPortfolio p = (WU_BasicPortfolio) portfolio;
683 int count = p->num_assets;
684
685 // Execute pending orders first (for NEXT_CLOSE policy)
686 if (p->params.execution_policy.policy == WU_EXECUTION_POLICY_NEXT_CLOSE) {
688 }
689
690 update_prices_and_check_exits(p, signals, count);
691
692 // Process signals based on execution policy
693 if (p->params.execution_policy.policy == WU_EXECUTION_POLICY_NEXT_CLOSE) {
694 // Store signals as pending orders for next update
695 for (int i = 0; i < count; i++) {
696 WU_Signal signal = signals[i];
697 if (wu_signal_validate(&signal) && signal.side != WU_SIDE_HOLD) {
698 p->pending_orders[i] = signal;
699 }
700 }
701 } else {
702 // Execute immediately
703 process_sell_signals(p, signals, count);
704 int buy_count = count_buy_signals(signals, count);
705 if (buy_count > 0) {
706 process_buy_signals(p, signals, count, buy_count);
707 }
708 }
709}
710
711/**
712 * This function is responsible for freeing all memory associated with the
713 * portfolio. It iterates through each asset's position vector and deletes
714 * it, then frees the array of position vectors, pending orders array,
715 * deletes the portfolio statistics, and finally frees the portfolio structure
716 * itself. This ensures that all resources are properly released when the
717 * portfolio is no longer needed, preventing memory leaks.
718 */
719static void wu_basic_portfolio_free(struct WU_Portfolio_* portfolio) {
720 WU_BasicPortfolio p = (WU_BasicPortfolio) portfolio;
721 if (!p) return;
722 for (int i = 0; i < p->num_assets; i++) {
723 wu_position_vector_delete(p->positions[i]);
724 }
725 free(p->positions);
726 free(p->pending_orders);
728 free(portfolio);
729}
730
731/**
732 * Helper function to initialize position vectors for all assets.
733 * Allocates memory and creates position vectors. Returns NULL on failure.
734 */
735static WU_PositionVector** initialize_positions(const char* symbols[],
736 int num_assets) {
737 WU_PositionVector** positions = malloc(num_assets
738 * sizeof(WU_PositionVector*));
739 if (!positions) return NULL;
740 for (int i = 0; i < num_assets; i++) {
741 positions[i] = wu_position_vector_new(symbols[i]);
742 if (!positions[i]) {
743 for (int j = 0; j < i; j++) {
744 wu_position_vector_delete(positions[j]);
745 }
746 free(positions);
747 return NULL;
748 }
749 }
750 return positions;
751}
752
753/**
754 * This constructor function creates a new basic portfolio instance. It
755 * takes in the portfolio parameters and an array of asset symbols, and
756 * initializes the portfolio's internal state accordingly. Memory is
757 * dynamically allocated for the portfolio structure and the position
758 * vectors for each asset. The function also sets up the function pointers
759 * for the portfolio operations (update, value, pnl, delete) and initializes
760 * the cash balance and trading statistics. If any memory allocation fails,
761 * it ensures that all previously allocated resources are freed to prevent
762 * memory leaks.
763 */
765 const char* symbols[]) {
766 WU_BasicPortfolio portfolio = malloc(sizeof(struct WU_BasicPortfolio_));
767 if (!portfolio) return NULL;
768 int num_assets;
769 for (num_assets = 0; symbols[num_assets] != NULL; num_assets++);
770 portfolio->positions = initialize_positions(symbols, num_assets);
771 if (!portfolio->positions) {
772 free(portfolio);
773 return NULL;
774 }
775
776 // Initialize pending orders array for NEXT_CLOSE policy
777 portfolio->pending_orders = calloc(num_assets, sizeof(WU_Signal));
778 if (!portfolio->pending_orders) {
779 for (int i = 0; i < num_assets; i++) {
780 wu_position_vector_delete(portfolio->positions[i]);
781 }
782 free(portfolio->positions);
783 free(portfolio);
784 return NULL;
785 }
786
787 portfolio->base = (struct WU_Portfolio_){
788 .update = update_portfolio,
792 };
793 portfolio->params = params;
794 portfolio->cash = params.initial_cash;
795 portfolio->num_assets = num_assets;
796 portfolio->last_update_time = (WU_TimeStamp){.mark = 0, .units = WU_TIME_UNIT_SECONDS};
797 portfolio->stats = wu_portfolio_stats_new(params.initial_cash, params.risk_free_rate);
798
799 // Initialize stats with asset symbols
800 for (int i = 0; i < num_assets; i++) {
801 wu_portfolio_stats_update_position(portfolio->stats, i,
802 portfolio->positions[i]->symbol, 0.0, 0.0, 0.0);
803 }
804
805 return portfolio;
806}
807
808/**
809 * The value of a specific asset's positions is calculated by summing
810 * the value of each active position. This function does bound checking.
811 */
812double wu_basic_portfolio_asset_value(WU_BasicPortfolio portfolio,
813 int asset_index) {
814 if (!portfolio || asset_index < 0
815 || asset_index >= portfolio->num_assets) {
816 return 0.0;
817 }
818 WU_PositionVector* vec = portfolio->positions[asset_index];
819 return calculate_positions_value(vec, vec->last_price);
820}
821
822/**
823 * Computes the total quantity held for a specific asset by summing the
824 * quantities of all active positions in the corresponding position
825 * vector. It does bound checking.
826 */
827double wu_basic_portfolio_asset_quantity(WU_BasicPortfolio portfolio,
828 int asset_index) {
829 if (!portfolio || asset_index < 0
830 || asset_index >= portfolio->num_assets) {
831 return 0.0;
832 }
833 return wu_position_total_quantity(portfolio->positions[asset_index]);
834}
static double execution_price(double price, WU_ExecutionPolicy policy, bool is_buy)
Calculates the execution price based on the execution policy.
Definition basic.c:79
WU_BasicPortfolio wu_basic_portfolio_new(WU_PortfolioParams params, const char *symbols[])
This constructor function creates a new basic portfolio instance.
Definition basic.c:764
static double calculate_position_size(WU_BasicPortfolio portfolio, WU_Signal signal)
This function calculates the position size for a given signal based on the portfolio's position sizin...
Definition basic.c:152
static double compute_total_cost(double quantity, double price, WU_ExecutionPolicy policy)
Computes the total cost of a trade including transaction costs.
Definition basic.c:206
static void process_buy_signals(WU_BasicPortfolio p, const WU_Signal *signals, int count, int buy_count)
Helper function to process all buy signals.
Definition basic.c:628
static int count_buy_signals(const WU_Signal *signals, int count)
Helper function to count valid buy signals.
Definition basic.c:586
double wu_basic_portfolio_asset_quantity(WU_BasicPortfolio portfolio, int asset_index)
Computes the total quantity held for a specific asset by summing the quantities of all active positio...
Definition basic.c:827
static double calculate_years_held(WU_TimeStamp open_time, WU_TimeStamp close_time)
Helper function to calculate the time difference in years between two timestamps, accounting for diff...
Definition basic.c:48
WU_PortfolioParams wu_portfolio_params_default(void)
This file implements a basic portfolio management system that supports multiple assets with a shared ...
Definition basic.c:19
static double calculate_total_short_value(WU_BasicPortfolio p)
Helper function to calculate total value of all short positions across all assets in the portfolio.
Definition basic.c:503
static void check_and_close_positions(WU_BasicPortfolio portfolio, int asset_index, double current_price)
This function iterates through all active positions for a given asset and checks if any of them can b...
Definition basic.c:473
static void execute_buy(WU_BasicPortfolio portfolio, WU_Signal signal, int asset_index)
This function executes a buy signal.
Definition basic.c:285
static void process_sell_signals(WU_BasicPortfolio p, const WU_Signal *signals, int count)
Helper function to process all sell signals.
Definition basic.c:574
static void close_and_update_portfolio(WU_BasicPortfolio portfolio, double quantity, double cost_basis, double sell_price, WU_CloseReason reason)
This function handles the logic for closing a position and updating the portfolio's cash balance and ...
Definition basic.c:332
static void execute_pending_orders(WU_BasicPortfolio p)
Helper function to execute pending orders from previous update.
Definition basic.c:653
static bool close_position(WU_BasicPortfolio portfolio, struct WU_Position_ pos, double current_price)
This function checks if a position should be closed based on the current price and the portfolio's st...
Definition basic.c:435
static struct BuyPositionResult set_buy_position(WU_BasicPortfolio portfolio, WU_Signal signal)
This function calculates the position size for a buy signal, applies execution policy to determine th...
Definition basic.c:227
double wu_basic_portfolio_asset_value(WU_BasicPortfolio portfolio, int asset_index)
The value of a specific asset's positions is calculated by summing the value of each active position.
Definition basic.c:812
static bool uses_strategy_guided_allocation(WU_BasicPortfolio p, const WU_Signal *signals, int count)
Helper function to check if signals use strategy-guided allocation.
Definition basic.c:600
static double calculate_portfolio_pnl(const struct WU_Portfolio_ *portfolio)
The profit and loss (PnL) of the portfolio is calculated by taking the current total value of the por...
Definition basic.c:493
static double calculate_total_cost_basis(WU_PositionVector *vec)
This function calculates the total cost basis for all active positions in a given position vector.
Definition basic.c:267
static WU_PositionVector ** initialize_positions(const char *symbols[], int num_assets)
Helper function to initialize position vectors for all assets.
Definition basic.c:735
static void wu_basic_portfolio_free(struct WU_Portfolio_ *portfolio)
This function is responsible for freeing all memory associated with the portfolio.
Definition basic.c:719
static void update_portfolio(WU_Portfolio portfolio, const WU_Signal *signals)
The update_portfolio function is the core of the portfolio management logic.
Definition basic.c:680
static double calculate_portfolio_value(const struct WU_Portfolio_ *portfolio)
To calculate the total value of the portfolio, an iterative process is used to sum the value of each ...
Definition basic.c:130
static void update_prices_and_check_exits(WU_BasicPortfolio p, const WU_Signal *signals, int count)
Helper function to update last prices, apply borrow interest on short positions, check exits,...
Definition basic.c:518
static void execute_sell(WU_BasicPortfolio portfolio, WU_Signal signal, int asset_index)
This function executes a sell signal.
Definition basic.c:353
static double calculate_positions_value(WU_PositionVector *vec, double price)
Helper function to calculate the total value of all positions in a position vector at a given price.
Definition basic.c:104
bool wu_signal_validate(const WU_Signal *signal)
Definition init.c:48
@ WU_TRANSACTION_COST_PROPORTIONAL
Definition portfolios.h:72
@ WU_TRANSACTION_COST_FIXED
Definition portfolios.h:71
@ WU_EXECUTION_POLICY_IMMEDIATE
Definition portfolios.h:64
@ WU_EXECUTION_POLICY_RANDOM_SLIPPAGE
Definition portfolios.h:67
@ WU_EXECUTION_POLICY_FIXED_SLIPPAGE
Definition portfolios.h:66
@ WU_EXECUTION_POLICY_NEXT_CLOSE
Definition portfolios.h:65
#define wu_portfolio_value(portfolio)
Calculates the current value of the portfolio.
Definition portfolios.h:37
@ WU_DIRECTION_BOTH
Definition portfolios.h:60
@ WU_DIRECTION_SHORT
Definition portfolios.h:59
@ WU_DIRECTION_LONG
Definition portfolios.h:58
#define wu_position_clear(vec)
Definition positions.h:42
#define wu_position_get(vec, index, found)
Definition positions.h:43
#define wu_position_add(vec, pos)
Definition positions.h:40
WU_PositionVector * wu_position_vector_new(const char *symbol)
@ WU_POSITION_SIZE_PCT_EQUAL
Definition positions.h:62
@ WU_POSITION_SIZE_STRATEGY_GUIDED
Definition positions.h:63
@ WU_POSITION_SIZE_ABS
Definition positions.h:60
@ WU_POSITION_SIZE_PCT
Definition positions.h:61
#define wu_position_remove(vec, index)
Definition positions.h:41
#define wu_position_total_quantity(vec)
Definition positions.h:44
#define wu_position_vector_delete(vec)
Definition positions.h:45
#define wu_portfolio_stats_update_position(stats, idx, sym, qty, val, price)
Updates position info for a specific asset.
Definition stats.h:146
#define wu_portfolio_stats_record_trade(stats, pnl, reason)
Records a trade in the stats.
Definition stats.h:138
WU_PortfolioStats wu_portfolio_stats_new(double initial_cash, double risk_free_rate)
Creates a new WU_PortfolioStats instance.
Definition stats.c:295
#define wu_portfolio_stats_delete(stats)
Definition stats.h:170
#define wu_portfolio_stats_update(stats, cash, value, timestamp)
Updates portfolio state in stats.
Definition stats.h:130
An auxiliary struct to hold the result of setting a buy position and calculating the total cost.
Definition basic.c:196
double tx_cost
Definition basic.c:199
double total_cost
Definition basic.c:198
struct WU_Position_ position
Definition basic.c:197
A basic portfolio implementation that supports multiple assets.
Definition portfolios.h:112
WU_ExecutionPolicyValue policy
Definition portfolios.h:76
WU_TransactionCostType tx_cost_type
Definition portfolios.h:79
The parameters to define a portfolio.
Definition portfolios.h:93
Base for a portfolio, which defines the minimal interface for updating the portfolio with signals,...
Definition portfolios.h:18
Position sizing policy.
Definition positions.h:70
WU_PositionSizeType size_type
Definition positions.h:71
WU_PositionVector is a data structure that holds multiple positions for a single asset.
Definition positions.h:24
struct WU_Position_ * positions
Definition positions.h:34
WU_Position represents an open position in the portfolio.
Definition positions.h:13
double quantity
Definition positions.h:15
double price
Definition positions.h:16
WU_Signal represents a trading signal generated by a strategy.
Definition types.h:38
double price
Definition types.h:42
double quantity
Definition types.h:43
WU_TimeStamp timestamp
Definition types.h:40
WU_Side side
Definition types.h:41
A timestamp represent a mark in time given relative to unix epoch.
Definition timeutils.h:21
int64_t mark
Definition timeutils.h:22
WU_TimeUnit units
Definition timeutils.h:23
@ WU_TIME_UNIT_SECONDS
Definition timeutils.h:11
@ WU_TIME_UNIT_NANOS
Definition timeutils.h:14
@ WU_TIME_UNIT_MILLIS
Definition timeutils.h:12
@ WU_TIME_UNIT_MICROS
Definition timeutils.h:13
@ WU_SIDE_HOLD
Definition types.h:12
@ WU_SIDE_SELL
Definition types.h:14
@ WU_SIDE_BUY
Definition types.h:13
WU_CloseReason
WU_CloseReason represents the reason for closing a position, which can be a trading signal,...
Definition types.h:53
@ WU_CLOSE_REASON_TAKE_PROFIT
Definition types.h:56
@ WU_CLOSE_REASON_STOP_LOSS
Definition types.h:55
@ WU_CLOSE_REASON_SIGNAL
Definition types.h:54