trader.strategies.llm_strategy module

A local LLM as a trading strategy.

It implements the same Strategy Protocol as RSI or Turtle, which is the entire point: the model’s decisions flow through the guardrails, executor, and reconciliation that Slice 2 already built and tested. There is no separate authority path for the model to take, so requirements §6.2’s “no strategy bypasses §8” holds structurally rather than by convention.

Not a frozen dataclass, unlike the rule strategies: this one holds collaborators and records what the last call saw, so the pipeline can persist it.

class trader.strategies.llm_strategy.LlmStrategy(id, llm, news_provider, max_news_items=10, lookback_bars=30, company_aliases=(), max_news_age_hours=72.0, max_reuse_age_minutes=240.0, reuse_price_band_pct=1.0, decision_memory=None, clock=None, alias_resolver=None, analyst_provider=None, earnings_provider=None)[source]

Bases: object

Asks a language model for a long-only decision.

Parameters:
  • id (str)

  • llm (LlmProvider)

  • news_provider (NewsProvider)

  • max_news_items (int)

  • lookback_bars (int)

  • company_aliases (Sequence[str])

  • max_news_age_hours (float)

  • max_reuse_age_minutes (float)

  • reuse_price_band_pct (float)

  • decision_memory (DecisionMemory | None)

  • clock (Callable[[], datetime] | None)

  • alias_resolver (Callable[[str], Sequence[str]] | None)

  • analyst_provider (AnalystProvider | None)

  • earnings_provider (EarningsProvider | None)

backtestable = False
warmup_bars()[source]

Enough bars to fill the lookback window.

The model tolerates fewer — it is shown whatever exists — but the pipeline uses this to size its fetch.

Return type:

int

set_benchmark_bars(bars, symbol)[source]

Give this cycle’s benchmark bars to the next evaluate() call.

Deliberately not a constructor parameter or an evaluate() argument: the Strategy Protocol’s evaluate(bars, position) signature is shared by four strategy classes and two callers, and CLAUDE.md records how expensive changing it atomically was last time. This is a duck-typed hook instead — run_once.py’s _process_symbol calls it via getattr(strategy, “set_benchmark_bars”, None) immediately before evaluate(), the mirror image of the getattr(strategy, “last_inputs”, {}) read it already does immediately after. A caller that never calls this (replay, most tests, llm-check) leaves both fields None, which build_technical_summary already renders as “Relative strength: not available.” — the same degrade-to-absent contract every other advisory input in this class uses.

Parameters:
  • bars (Sequence[Bar] | None)

  • symbol (str | None)

Return type:

None

evaluate(bars, position)[source]

Ask the model, and never let a model failure become a trade.

Parameters:
Return type:

Signal