Academy/Strategy Code Breakdown/How the Code Actually Picks the Stock
Strategy Code BreakdownLesson 9

How the Code Actually Picks the Stock

A line-by-line tour of the exact functions in engine.ts that decide which stock gets a buy signal on any given day — and which gets ignored.

12 minute read
4 key takeaways

How the Code Actually Picks the Stock

Every time a backtest runs, a loop visits every symbol in the asset universe on every trading day. The pick — buy, sell, or do nothing — comes from one function: getSignal(). Here is exactly how it works, line by line, in engine.ts.

Step 1 — resolveStrategyType: Turning a Name into a Type

Before any signal can fire, the engine must know which strategy it is running. It does this by matching keywords in the strategy name string.

typescript
// engine.ts — lines 64–75
function resolveStrategyType(nameOrId: string): StrategyType {
  const s = nameOrId.toLowerCase();
  if (s.includes('iron') && s.includes('condor'))                          return 'iron-condor';
  if (s.includes('cash') && (s.includes('put') || s.includes('secured'))) return 'cash-secured-put';
  if (s.includes('bear') && (s.includes('put') || s.includes('spread')))  return 'bear-put-spread';
  if (s.includes('bull') && (s.includes('call') || s.includes('spread'))) return 'bull-call-spread';
  if (s.includes('sma') || s.includes('crossover') || s.includes('moving'))return 'sma-crossover';
  if (s.includes('rsi') || s.includes('reversion') || s.includes('mean')) return 'rsi-mean-reversion';
  return 'buy-and-hold';   // ← default if nothing matches
}

Why Keyword Matching?

Strategies are stored in the database by name, not by a strict enum. Keyword matching lets users name their strategies anything ("My RSI v2", "Mean Reversion Clone") and still have the engine detect the correct logic. The strategy_id field in the config is the fallback when the name is ambiguous.

Step 2 — The closes Array: What the Strategy Actually Sees

Before getSignal is called, the engine builds a running history of closing prices for each symbol. This is the only price data the strategy gets — no opens, no highs, no lows.

typescript
// engine.ts — lines 785–796  (inside the main day loop)
const closeHistory = new Map<string, number[]>();

for (const date of tradingDays) {
  const bars = pricesByDate.get(date)!;

  // Append today's close to each symbol's rolling history
  for (const sym of symbols) {
    const bar = bars.get(sym);
    if (!bar) continue;                              // ← symbol had no data this day: skip it
    if (!closeHistory.has(sym)) closeHistory.set(sym, []);
    closeHistory.get(sym)!.push(bar.close);          // ← THIS is the array getSignal receives
  }
  // ... signal evaluation follows immediately below
}

By the time getSignal runs on day 50, closes contains 50 values — one per trading day since the backtest started. On day 1, it contains only 1 value. This is why strategies have a warm-up guard: if closes.length < period + 1, return "hold" — not enough history yet to compute the indicator.

Step 3 — getSignal: The Exact Line That Says "Buy This Stock"

This is the single function that makes the pick. It runs once per symbol per day. The return value is either "buy", "sell", or "hold".

typescript
// engine.ts — lines 77–116
function getSignal(
  type: StrategyType,
  closes: number[],     // ← full price history for this symbol up to today
  hasPosition: boolean, // ← is the engine already holding this stock?
  params: Record<string, number>,
): Signal {
  switch (type) {

    // ── RSI Mean Reversion ────────────────────────────────────────
    case 'rsi-mean-reversion': {
      const period     = params.rsi_period   ?? 14;
      const oversold   = params.oversold     ?? 30;
      const overbought = params.overbought   ?? 70;

      if (closes.length < period + 1) return 'hold'; // ← warm-up guard

      const r = rsi(closes, period);   // ← compute RSI on the full history

      if (!hasPosition && r < oversold)   return 'buy';  // ← PICK: buy this stock
      if ( hasPosition && r > overbought) return 'sell'; // ← EXIT: sell this stock
      return 'hold';
    }

    // ── SMA Crossover ─────────────────────────────────────────────
    case 'sma-crossover': {
      const fast = params.fast_period ?? 10;
      const slow = params.slow_period ?? 30;
      if (closes.length < slow + 1) return 'hold';

      const fastNow  = sma(closes, fast);
      const slowNow  = sma(closes, slow);
      const fastPrev = sma(closes.slice(0, -1), fast); // ← yesterday's fast SMA
      const slowPrev = sma(closes.slice(0, -1), slow); // ← yesterday's slow SMA

      // Cross detected: yesterday fast ≤ slow, today fast > slow → bullish crossover
      if (!hasPosition && fastPrev <= slowPrev && fastNow > slowNow) return 'buy';
      if ( hasPosition && fastPrev >= slowPrev && fastNow < slowNow) return 'sell';
      return 'hold';
    }
  }
}

The Pick Happens in One Line

For RSI: line "if (!hasPosition && r < oversold) return 'buy'" — that single line is the entire stock selection decision. Everything before it (EMA math, RSI formula, closes array) exists solely to produce the number r so this comparison can happen.

Step 4 — The Symbol Loop: Every Stock Evaluated Every Day

typescript
// engine.ts — lines 799–822
for (const sym of symbols) {         // ← iterates every ticker in asset_universe
  const bar    = bars.get(sym);
  if (!bar) continue;                // ← no price data for this symbol today: skip

  const closes  = closeHistory.get(sym) ?? [];
  const hasPos  = positions.has(sym);

  // ← THIS is the stock selection call
  const signal  = getSignal(strategyType, closes, hasPos, params);

  if (signal === 'buy' && !hasPos && cash >= bar.close) {
    // Stock selected — move to execution (see next lesson)
    ...
  } else if (signal === 'sell' && hasPos) {
    // Exit signal — also handled in execution (see next lesson)
    ...
  }
}

Notice the loop evaluates every symbol independently. SPY can be in a buy signal while AAPL is in hold and TSLA is in sell — all on the same day. The strategy does not compare stocks against each other; it evaluates each one against its own indicator threshold.

The hasPosition Guard

The condition "!hasPosition" inside getSignal (and again in the outer if) means the engine will never double-buy the same stock. Once a position exists, buy signals are silently ignored until a sell exits it. This is why the RSI strategy can only hold one position per symbol at a time.

Key Takeaways
  • resolveStrategyType maps any strategy name string to one of five known types
  • getSignal is the single function that decides buy / sell / hold for every symbol every day
  • The closes array is the entire price history fed into the indicator — its length is what triggers "not enough data yet"
  • Each symbol in the asset universe is evaluated independently on every trading day

Backtest any strategy in the Lab

Run a backtest and watch the signal engine pick stocks in real time.