fix: bracket fills stay on legs; by-tag lookup surfaces entry-leg fill

General translation must not echo leg fills onto the OTOCO parent —
flatten_fills adds parent AND legs, double-counting entries in journal
reconstruction. The cancel-vs-fill reconcile still sees executed shares:
get_order_by_client_order_id surfaces the entry leg's fill on its result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-28 09:36:52 -05:00
1 parent a2de50479a
commit 80422d91cd
2 files changed
+39 -8

No files matched your search

+19 -1
View File
@@ -81,7 +81,9 @@ class TranslateOrderTests(unittest.TestCase):
self.assertEqual(o["symbol"], "DAMD")
self.assertEqual(o["side"], "buy")
self.assertEqual(o["qty"], "8000")
self.assertEqual(o["filled_qty"], "8000")
# fills stay on legs only — parent echoing them double-counts in
# journal reconstruction (flatten_fills adds parent AND legs)
self.assertEqual(o["filled_qty"], "0")
# the stop leg is findable the way exit_manager._stop_leg looks
stops = [leg for leg in o["legs"] if leg["type"] == "stop"
and leg["side"] == "sell" and leg["symbol"] == "DAMD"]
@@ -271,3 +273,19 @@ class CancelRacedFillTests(unittest.TestCase):
emitted = [c.args[0].__class__.__name__ for c in store.emit.call_args_list]
self.assertIn("OrderCancelledEvent", emitted)
self.assertNotIn("OrderFilledEvent", emitted)
class ByTagLookupFillTests(unittest.TestCase):
def test_lookup_surfaces_entry_leg_fill_on_bracket(self):
client = TradierClient(SETTINGS)
raw = [{"id": 1, "class": "otoco", "status": "open", "tag": "cid-9",
"leg": [{"id": 2, "symbol": "DAMD", "side": "buy", "quantity": 8000,
"type": "limit", "status": "filled",
"exec_quantity": 8000, "avg_fill_price": 1.96},
{"id": 3, "symbol": "DAMD", "side": "sell", "type": "stop",
"status": "open", "stop_price": 1.8}]}]
with patch.object(client, "_orders_raw", return_value=raw):
found = client.get_order_by_client_order_id("cid-9")
self.assertEqual(found["filled_qty"], "8000")
self.assertEqual(found["filled_avg_price"], "1.96")
self.assertEqual(found["status"], "filled")
+20 -7
View File
@@ -94,16 +94,17 @@ def translate_order(o: dict) -> dict:
filled_at = o.get("transaction_date") if status == "filled" else None
if legs and not o.get("symbol"):
# Tradier OTOCO/OTO PARENT orders carry no top-level symbol/side/qty —
# only the legs do. Inherit from the entry leg (leg[0]) so symbol-scoped
# lookups (stop-leg search, naked-stop guard) can see bracket orders;
# without this the guard reads a protected position as naked and
# force-exits it (2026-07-28 sandbox incident).
# only the legs do. Inherit identity fields from the entry leg (leg[0])
# so symbol-scoped lookups (stop-leg search, naked-stop guard) can see
# bracket orders; without this the guard reads a protected position as
# naked and force-exits it (2026-07-28 sandbox incident).
# Fill fields are NOT inherited — the legs carry the fills, and a
# parent echoing them double-counts in journal reconstruction
# (storage.journal_backfill.flatten_fills adds parent AND legs).
entry_leg = legs[0]
o = {**o, "symbol": entry_leg.get("symbol"),
"side": o.get("side") or entry_leg.get("side"),
"quantity": o.get("quantity") or entry_leg.get("qty"),
"exec_quantity": o.get("exec_quantity") or entry_leg.get("filled_qty"),
"avg_fill_price": o.get("avg_fill_price") or entry_leg.get("filled_avg_price"),
"create_date": o.get("create_date") or entry_leg.get("submitted_at"),
"transaction_date": o.get("transaction_date") or entry_leg.get("updated_at")}
return {
@@ -435,7 +436,19 @@ class TradierClient:
def get_order_by_client_order_id(self, client_order_id: str) -> dict | None:
for o in self._orders_raw():
if (o.get("tag") or "") == client_order_id:
return translate_order(o)
out = translate_order(o)
# bracket container: surface the ENTRY LEG's fill on the parent
# so the cancel-vs-fill reconcile sees executed shares (the
# general translation deliberately leaves fills on legs only)
legs = out.get("legs") or []
if legs and float(out.get("filled_qty") or 0) == 0:
entry_leg = legs[0]
if float(entry_leg.get("filled_qty") or 0) > 0:
out = {**out,
"filled_qty": entry_leg["filled_qty"],
"filled_avg_price": entry_leg.get("filled_avg_price"),
"status": entry_leg.get("status") or out.get("status")}
return out
return None
def submit_order(