feat(kevin): reconcile Alpaca bracket auto-closes + order status
Bracket stop-loss/take-profit legs fill at Alpaca without passing through the executor, so those closes (and their P&L) were invisible locally. - broker: add get_order(nested) + list_orders to BaseBroker/AlpacaBroker (+ SimulatedBroker); BrokerOrder carries child legs - Trade gains broker_order_id (migration f6a7b8c9d0e1); executor stamps the entry order id - new api_gateway trade-reconcile loop: books a closing SELL + realized P&L when a bracket leg fills (idempotent on the leg order id), syncs PENDING->terminal status, logs drift; runs alongside portfolio_sync [ci skip] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
52b3c76482
commit
82dc622544
13 changed files with 1049 additions and 8 deletions
|
|
@ -912,6 +912,34 @@ class TestExecutorDBPersistence:
|
|||
assert trade_obj.ticker == "AAPL"
|
||||
assert trade_obj.signal_id == signal.signal_id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_entry_trade_records_broker_order_id(self):
|
||||
"""The persisted entry Trade must carry the Alpaca order id so
|
||||
reconciliation can find the bracket order later."""
|
||||
config = _make_config()
|
||||
broker = _mock_broker(positions=[], account=_make_account(100_000))
|
||||
publisher = AsyncMock()
|
||||
publisher.publish = AsyncMock(return_value=b"1-0")
|
||||
counters = {
|
||||
"trades_executed": MagicMock(),
|
||||
"rejections": MagicMock(),
|
||||
"fill_latency": MagicMock(),
|
||||
}
|
||||
|
||||
signal = _make_kevin_signal(direction=SignalDirection.LONG)
|
||||
mock_session = AsyncMock()
|
||||
mock_session.add = MagicMock()
|
||||
mock_session.commit = AsyncMock()
|
||||
db_factory = _make_mock_db_session_factory(mock_session)
|
||||
|
||||
with patch.object(RiskManager, "check_risk", return_value=(True, "approved")):
|
||||
await process_signal(
|
||||
signal, RiskManager(config, broker), broker, publisher, counters, db_factory
|
||||
)
|
||||
|
||||
trade_obj = mock_session.add.call_args[0][0]
|
||||
assert trade_obj.broker_order_id == "ord-123"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_trade_not_persisted_without_db(self):
|
||||
"""When db_session_factory is None, no DB write should happen."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue