Academy/Strategy Code Breakdown/How the Code Executes the Trade
Strategy Code BreakdownLesson 10

How the Code Executes the Trade

Once a buy signal fires, exactly which lines open the position, deduct cash, calculate shares, and eventually close the trade — traced through simulateStrategy and closeTrade.

11 minute read
4 key takeaways

How the Code Executes the Trade

The previous lesson showed which line fires the buy signal. This lesson shows what happens next — how cash is allocated, how many shares are bought, how the position is tracked, and how the exit is calculated. All of this lives in simulateStrategy() and closeTrade() in engine.ts.

Step 1 — Capital Allocation: capPerSymbol

typescript
// engine.ts — lines 778–784
const symbols      = config.asset_universe;             // e.g. ['SPY','AAPL','TSLA']
const capPerSymbol = config.initial_capital / symbols.length;
//    capPerSymbol = $100,000 / 3 = $33,333 per symbol

let cash      = config.initial_capital;                 // starts at full bankroll
const positions = new Map<string, Position>();          // symbol → open position
const trades    = [];                                   // closed trade log

capPerSymbol is calculated once before the loop starts. It is the maximum the engine will ever invest in a single stock. This is the position-sizing rule — equal-weight across the universe. If SPY is already owned, its $33k slot is locked; cash falls; the other two symbols can still take their slots.

Why Equal Weight?

Equal-weight is the simplest position sizing approach and hard to beat in practice for diversified portfolios. More sophisticated systems use volatility-adjusted sizing (e.g. Kelly Criterion or ATR-based) but require more code and more parameters to tune. The engine uses equal-weight to keep the backtest clean and reproducible.

Step 2 — Opening the Position: The Buy Block

typescript
// engine.ts — lines 807–816
if (signal === 'buy' && !hasPos && cash >= bar.close) {

  // How much money to deploy into this stock
  const allocate = Math.min(capPerSymbol, cash);
  //    allocate = min($33,333, remaining cash)
  //    The min() prevents deploying more than cash available

  // Fees reduce the invested amount — NOT added on top
  const feeRate     = config.commission + config.slippage;
  //    feeRate   = 0.001 + 0.001 = 0.002  (0.2% total round-trip cost)
  const investAmount = allocate / (1 + feeRate);
  //    investAmount = $33,333 / 1.002 = $33,266 (fees come off the top)

  // ← THIS LINE calculates how many shares are bought
  const shares = investAmount / bar.close;
  //    shares   = $33,266 / $450 (AAPL price) = 73.92 shares

  if (shares > 0) {
    // Record the open position in memory
    positions.set(sym, {
      symbol:      sym,
      shares,               // ← fractional shares allowed in backtest
      entry_price: bar.close,
      entry_date:  date,
    });
    cash -= allocate;       // ← deduct full allocated amount from cash
  }
}
VariableExample ValueWhat It Represents
allocate$33,333Capital slot for this symbol
feeRate0.002 (0.2%)Commission + slippage combined
investAmount$33,266Amount reaching the market after fees
shares73.92Number of shares purchased
cash after$66,667Remaining cash for other symbols

Why Divide by (1 + feeRate)?

If you invested $33,333 and then subtracted fees separately, you would sometimes over-spend — ending up with negative cash when all symbols trigger at once. Dividing by (1 + feeRate) first means fees are already baked in, so cash never goes negative. The math: investAmount × (1 + feeRate) = allocate exactly.

Step 3 — Closing the Position: The Sell Block

typescript
// engine.ts — lines 817–821
} else if (signal === 'sell' && hasPos) {
  const pos = positions.get(sym)!;   // retrieve the open position record

  // Compute P&L and log the closed trade
  trades.push(closeTrade(pos, bar.close, date, config));

  // Return cash: shares × current price, minus exit fees
  cash += pos.shares * bar.close * (1 - config.commission - config.slippage);
  //     = 73.92 shares × $490 × (1 - 0.002) = $36,145 back into cash

  positions.delete(sym);   // ← position is gone; symbol can be re-entered later
}

Step 4 — closeTrade: Computing Realized P&L

typescript
// engine.ts — lines 850–878
function closeTrade(pos, exitPrice, exitDate, config): BacktestTrade {
  const value     = pos.shares * exitPrice;
  //    value     = 73.92 × $490 = $36,221  (gross exit proceeds)

  const fees      = value * (config.commission + config.slippage);
  //    fees      = $36,221 × 0.002 = $72.44

  const netValue  = value - fees;
  //    netValue  = $36,221 - $72 = $36,149  (what you actually receive)

  const costBasis = pos.shares * pos.entry_price;
  //    costBasis = 73.92 × $450 = $33,266  (what you paid, already net of entry fees)

  const pnl = netValue - costBasis;
  //    pnl  = $36,149 - $33,266 = $2,883 profit

  const durationDays = Math.round(
    (new Date(exitDate) - new Date(pos.entry_date)) / 86_400_000
  );

  return {
    symbol:       pos.symbol,
    direction:    'long',
    entry_date:   pos.entry_date,
    entry_price:  pos.entry_price,
    exit_date:    exitDate,
    exit_price:   exitPrice,
    quantity:     pos.shares,
    pnl,                        // ← realized profit/loss in dollars
    pnl_percent:  (pnl / costBasis) * 100,
    duration_days: durationDays,
  };
}

The Full Lifecycle in Four Variables

allocate → what capital enters. investAmount → what actually buys shares (after entry fee). pos.shares × exitPrice → gross exit value. pnl = netValue − costBasis → the final realized profit or loss. Every number shown in the backtest trade log flows from these four calculations.

Step 5 — Options Execution: How It Differs

For options strategies (Bull Call Spread, Bear Put Spread, Iron Condor, Cash-Secured Put), execution is handled by separate simulator functions — simulateBullCallSpread, simulateIronCondor, etc. The key differences from equity execution are:

  • Capital deployed = net debit (for spreads) or collateral (for CSP), not share price × quantity
  • Positions are held for a fixed duration (roll at 21 DTE) rather than until a signal fires
  • P&L is marked-to-market daily using the binomial tree pricer — not just entry vs. exit price
  • Profit targets and stop losses are checked every day against the current spread value
  • At expiry, the CRR tree returns a terminal value of max(S−K, 0) for calls and max(K−S, 0) for puts
typescript
// engine.ts — options execution excerpt (simulateBullCallSpread)
// On the first trading day of each month, open a new spread:
const { spreadValue: entryValue } = priceBullSpread(spot, longStrike, shortStrike, dte);
const allocate  = capital * positionSize;          // e.g. 10% of capital
const contracts = Math.floor(allocate / (entryValue * 100));
capital -= contracts * entryValue * 100;           // debit paid

// Every day after entry, mark-to-market:
const { spreadValue: currentValue } = priceBullSpread(spot, longStrike, shortStrike, dte);
const unrealizedPnl = (currentValue - entryValue) * contracts * 100;

// Close early if profit target or stop loss hit:
if (currentValue >= entryValue * (1 + profitTarget)) closeSpread('profit_target');
if (currentValue <= entryValue * (1 - stopLossPct))  closeSpread('stop_loss');
if (dte <= closeDte)                                  closeSpread('time_exit');

Why Options Use a Separate Simulator

Equity simulateStrategy() uses a simple close-price comparison. Options require daily Black-Scholes / CRR pricing, DTE tracking, strike selection, and contract sizing — too much logic to fold into the generic getSignal switch. Each options strategy gets its own function so the math stays clean and each strategy's edge is accurately modelled.

Key Takeaways
  • capPerSymbol splits capital equally so no single stock can use the whole bankroll
  • Fees are deducted from the allocated amount before shares are calculated — cash never overflows
  • shares = investAmount / bar.close is the one line that determines position size
  • closeTrade computes realized P&L as netValue − costBasis

Open the Backtest Lab

Run a backtest and inspect every trade the engine opens and closes.