One GenServer holds open positions, pending orders, strategy state, portfolio risk, and contract rolls — for a system trading real capital. This post is a tour of its state struct.
What lives in state
The RealtimeTrader is the core GenServer in the trading pipeline. Part of a fleet of session-scoped processes, one trader per active session. Here's its state struct:
defstruct [
:session,
trading?: false,
orders: [],
trades: [],
open_position: nil,
candles: [],
strategy: %{module: nil, state: %{}, args: %{}},
market_data_source: %{module: nil, args: nil},
account_manager: %{module: nil, args: nil},
account_pnl_info: %{},
account_balance: %{},
order_template: nil,
latest_partial_candle: nil,
candle_key: nil,
contract_details: nil,
warmup_periods: 0,
portfolio_heat: Decimal.new("0"),
portfolio_heat_warned: false,
portfolio_heat_blocked: false,
rolling?: false,
rolling_close_order_id: nil,
rolling_close_qty: nil
]
Three categories. The categories matter more than the fields.
Runtime data
orders, open_position, candles, latest_partial_candle, account_balance, portfolio_heat — the current state of the trading session. Orders are an in-memory list sorted by sent_at descending. Position is a runtime struct with a signed integer — positive for long, negative for short. Candles are the bar history the strategy needs to make decisions.
None of this is authoritative — these are working copies. The broker is the source of truth, and the system doesn't trust itself for long — position reconciles every 30 seconds, orders every 5 minutes.
Collaborator references
strategy, market_data_source, account_manager — each is a map with a module key and additional context. The GenServer holds references to its own dependencies. The strategy map also carries state — the strategy's own stateful data, nested inside the GenServer's state.
This is a deliberate design choice. From the GenServer's perspective the strategy is a behavior module invoked synchronously within its callback chain. Its state lives here because it needs to survive across ticks but doesn't need its own process lifecycle.
Operational flags
trading?, rolling?, portfolio_heat_warned, portfolio_heat_blocked — state machine flags that control what the GenServer is allowed to do. When rolling? is true, the strategy doesn't run. When portfolio_heat_blocked is true, new entries are rejected — but exits are never blocked. These flags encode the operational rules that keep the system safe.
Discussion
Create a free account to join the conversation.