trader.persistence.simulation module

Repository for the shadow-portfolio simulator’s five tables (issue #33).

One session per call, matching every other repository in this package — the simulator is not performance-critical (it steps at most a handful of times per real trading day, per strategy), so consistency with the rest of the codebase’s style matters more than batching writes into fewer transactions.

simulation/step.py reads a value (e.g. a position’s current high_water_mark) via one call, decides what to do with it, then writes the result via a second call — deliberately, so the “test the stop against the prior high-water mark” ordering the design spec requires falls out of the caller’s own control flow rather than needing an in-repository transaction.

trader.persistence.simulation.ABB_PORTFOLIO_SUFFIX = '::abb'

Suffix appended to a real strategy_id to build the Always-be-Buying variant’s portfolio identity (issue #42) — its own SimPortfolio row, reading the same strategy_id’s recorded decisions but with sell signals ignored, so only the trailing stop can ever close a position. sim_portfolios.strategy_id has no format constraint, so this is a convention, not a database rule: “::” cannot appear in a bare YAML scalar id without quoting (config/strategies.yaml’s id: str has no character restriction of its own), and no configured strategy uses it, so in practice this suffix cannot collide with a real strategy id.

class trader.persistence.simulation.SimulationRepository(session_factory)[source]

Bases: object

Writes and reads the sim_* tables.

Parameters:

session_factory (sessionmaker[Session])

get_or_create_portfolio(strategy_id, *, starting_cash)[source]

The portfolio for strategy_id, creating it (seeded at starting_cash) on first use. starting_cash is ignored on every call after the first — a config edit must not silently reset an accumulating ledger.

Parameters:
  • strategy_id (str)

  • starting_cash (Decimal)

Return type:

SimPortfolio

update_cash(portfolio_id, cash)[source]
Parameters:
  • portfolio_id (int)

  • cash (Decimal)

Return type:

None

update_last_bar_ts(portfolio_id, bar_ts)[source]
Parameters:
  • portfolio_id (int)

  • bar_ts (datetime)

Return type:

None

all_portfolios()[source]
Return type:

list[SimPortfolio]

get_position(portfolio_id, symbol)[source]
Parameters:
  • portfolio_id (int)

  • symbol (str)

Return type:

SimPosition | None

all_positions(portfolio_id)[source]
Parameters:

portfolio_id (int)

Return type:

list[SimPosition]

open_position(portfolio_id, symbol, *, quantity, entry_price, high_water_mark, last_price, opened_at, entry_confidence=None)[source]

entry_confidence (issue #83) defaults to None for every caller that predates it — an intentional “unknown”, never a guessed value — and is read once here, at fill time, from the decision that caused this order (see simulation/step.py’s own lookup at the fill step).

Parameters:
  • portfolio_id (int)

  • symbol (str)

  • quantity (Decimal)

  • entry_price (Decimal)

  • high_water_mark (Decimal)

  • last_price (Decimal)

  • opened_at (datetime)

  • entry_confidence (float | None)

Return type:

SimPosition

mark_position(position_id, *, high_water_mark, last_price)[source]

Write a new mark. The caller reads the prior value before calling this — this method never returns the old one, on purpose, so there is no temptation to test a breach against a value this same call just overwrote.

Parameters:
  • position_id (int)

  • high_water_mark (Decimal)

  • last_price (Decimal)

Return type:

None

close_position(position_id)[source]
Parameters:

position_id (int)

Return type:

None

create_order(portfolio_id, symbol, *, decision_id, limit_price, quantity, created_bar_ts)[source]
Parameters:
  • portfolio_id (int)

  • symbol (str)

  • decision_id (int | None)

  • limit_price (Decimal)

  • quantity (Decimal)

  • created_bar_ts (datetime)

Return type:

SimOrder

pending_orders(portfolio_id)[source]
Parameters:

portfolio_id (int)

Return type:

list[SimOrder]

fill_order(order_id)[source]
Parameters:

order_id (int)

Return type:

None

cancel_order(order_id)[source]
Parameters:

order_id (int)

Return type:

None

record_round_trip(portfolio_id, symbol, *, decision_id, quantity, entry_price, exit_price, realized_pl, opened_at, closed_at, exit_reason)[source]
Parameters:
  • portfolio_id (int)

  • symbol (str)

  • decision_id (int | None)

  • quantity (Decimal)

  • entry_price (Decimal)

  • exit_price (Decimal)

  • realized_pl (Decimal)

  • opened_at (datetime)

  • closed_at (datetime)

  • exit_reason (str)

Return type:

SimRoundTrip

round_trips(portfolio_id)[source]
Parameters:

portfolio_id (int)

Return type:

list[SimRoundTrip]

snapshot_equity(portfolio_id, bar_ts, equity)[source]

Insert one equity snapshot. Returns whether it was inserted.

False (not an exception) on the (portfolio_id, bar_ts) unique constraint firing — the second of the step’s two idempotency mechanisms, alongside SimPortfolio.last_bar_ts: a step re-asked to apply an already-applied bar must no-op, not double the curve.

Parameters:
  • portfolio_id (int)

  • bar_ts (datetime)

  • equity (Decimal)

Return type:

bool

equity_curve(portfolio_id)[source]
Parameters:

portfolio_id (int)

Return type:

list[SimEquitySnapshot]