trader.domain module¶
Typed value objects shared across the Core Service Layer.
Adapters convert third-party SDK responses into these objects so no alpaca-py or pandas type ever reaches the CLI or persistence layers. Money is always Decimal, never float.
- class trader.domain.Account(account_id, cash, equity, buying_power, portfolio_value, is_paper, last_equity=Decimal('0'))[source]¶
Bases:
objectA broker account snapshot at a point in time.
- Parameters:
account_id (str)
cash (Decimal)
equity (Decimal)
buying_power (Decimal)
portfolio_value (Decimal)
is_paper (bool)
last_equity (Decimal)
- account_id: str¶
- cash: Decimal¶
- equity: Decimal¶
- buying_power: Decimal¶
- portfolio_value: Decimal¶
- is_paper: bool¶
- last_equity: Decimal¶
- class trader.domain.Bar(symbol, timestamp, open, high, low, close, volume)[source]¶
Bases:
objectA single OHLCV candle.
- Parameters:
symbol (str)
timestamp (datetime)
open (Decimal)
high (Decimal)
low (Decimal)
close (Decimal)
volume (int)
- symbol: str¶
- timestamp: datetime¶
- open: Decimal¶
- high: Decimal¶
- low: Decimal¶
- close: Decimal¶
- volume: int¶
- class trader.domain.MarketClock(is_open, next_open, next_close)[source]¶
Bases:
objectWhether the exchange is open, and when that next changes.
The daemon sleeps on these values, so they carry the holiday and half-day schedule the broker knows about and a local 09:30–16:00 weekdays rule does not. Both timestamps are tz-aware UTC; the adapter converts and refuses naive input rather than letting a naive value reach an arithmetic comparison against an aware now.
next_open is meaningful even while the market is open — it is the following session — so the daemon must branch on is_open rather than inferring the state from the timestamps.
- Parameters:
is_open (bool)
next_open (datetime)
next_close (datetime)
- is_open: bool¶
- next_open: datetime¶
- next_close: datetime¶
- class trader.domain.OrderSide(*values)[source]¶
Bases:
StrEnumLong-only order directions.
- BUY = 'buy'¶
- SELL = 'sell'¶
- class trader.domain.Position(symbol, quantity, avg_entry_price, current_price, market_value, unrealized_pl)[source]¶
Bases:
objectA single open position.
- Parameters:
symbol (str)
quantity (Decimal)
avg_entry_price (Decimal)
current_price (Decimal)
market_value (Decimal)
unrealized_pl (Decimal)
- symbol: str¶
- quantity: Decimal¶
- avg_entry_price: Decimal¶
- current_price: Decimal¶
- market_value: Decimal¶
- unrealized_pl: Decimal¶
- class trader.domain.ProtectedEntry(entry, protective_leg)[source]¶
Bases:
objectAn atomic OTO entry, together with the protective leg attached to it.
Two orders come back from one submit_limit_buy_with_stop request, and both have to reach trades. The parent’s row is what makes the entry fill attributable; the leg’s row is what makes the exit fill attributable, and a stop-driven exit is this app’s dominant exit path. Returning only the parent — which is what this type replaced — meant raw.legs was discarded at the adapter boundary and the leg was never recorded, so reconcile_fills saw its eventual fill as “not ours”, the round trip stayed open forever, total_realized_pl() reported 0 against a real loss, and the symbol then wedged on “a round trip is already open”. That is why this exists at all.
A small result object rather than a protective_leg field on SubmittedOrder, for two reasons. SubmittedOrder is the shape of one broker order and is persisted as one row; a nested order inside it would be meaningless for every other order type and silently dropped by every repository that writes one. And a distinct return type makes the caller name the leg to ignore it, so the next caller cannot repeat the original mistake by accident. Same reasoning, and same shape, as RatchetedStop.
protective_leg is None only when the broker’s response carried no leg at all. That is not a normal outcome — it means the entry may be unprotected — so the adapter logs it loudly rather than treating it as an ordinary absence, and the end-of-cycle §8 check is what catches the resulting position.
- Parameters:
entry (SubmittedOrder)
protective_leg (SubmittedOrder | None)
- entry: SubmittedOrder¶
- protective_leg: SubmittedOrder | None¶
- class trader.domain.Quote(symbol, price, as_of)[source]¶
Bases:
objectThe most recent observed price for a symbol.
- Parameters:
symbol (str)
price (Decimal)
as_of (datetime)
- symbol: str¶
- price: Decimal¶
- as_of: datetime¶
- class trader.domain.SubmittedOrder(order_id, symbol, side, quantity, order_type, status, submitted_at, limit_price=None, trail_percent=None, stop_price=None, filled_qty=None, filled_avg_price=None, filled_at=None)[source]¶
Bases:
objectAn order the broker accepted, as a domain object.
quantity is Decimal to match the rest of the money surface even though this app submits whole shares only: the broker reports fractional quantities for positions it acquired other ways, and truncating here would misreport what actually exists at the broker.
- Parameters:
order_id (str)
symbol (str)
side (OrderSide)
quantity (Decimal)
order_type (str)
status (str)
submitted_at (datetime)
limit_price (Decimal | None)
trail_percent (Decimal | None)
stop_price (Decimal | None)
filled_qty (Decimal | None)
filled_avg_price (Decimal | None)
filled_at (datetime | None)
- order_id: str¶
- symbol: str¶
- quantity: Decimal¶
- order_type: str¶
- status: str¶
- submitted_at: datetime¶
- limit_price: Decimal | None¶
- trail_percent: Decimal | None¶
- stop_price: Decimal | None¶
- filled_qty: Decimal | None¶
- filled_avg_price: Decimal | None¶
- filled_at: datetime | None¶
- class trader.domain.TradableAsset(symbol, tradable, asset_class, exchange, name=None)[source]¶
Bases:
objectWhat the broker knows about a symbol.
tradable=False and “the broker has never heard of it” are different answers and discovery treats them the same way — but only because both mean “do not order this”. Keeping them distinct here means a future caller can tell an OTC halt from a typo.
- Parameters:
symbol (str)
tradable (bool)
asset_class (str)
exchange (str)
name (str | None)
- symbol: str¶
- tradable: bool¶
- asset_class: str¶
- exchange: str¶
- name: str | None¶
The issuer’s name as the broker states it — “Apple Inc. Common Stock”. Optional because a broker may omit it and because nothing depends on it being present: trader.news.aliases derives news-matching aliases from it when it is there, and falls back to bare-ticker matching when it is not. It reached this object only because Alpaca already sends it and the mapper was discarding it (issue #6) — no extra call was added for it.