Skip to content

Commit 65103cf

Browse files
committed
Expose structured LongBridge dry-run order previews
1 parent 6de63c9 commit 65103cf

4 files changed

Lines changed: 36 additions & 2 deletions

File tree

application/execution_service.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ class ExecutionCycleResult:
247247
skip_logs: tuple[str, ...]
248248
note_logs: tuple[str, ...]
249249
action_done: bool
250+
dry_run_orders: tuple[dict, ...] = ()
250251

251252

252253
DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD = 1000.0
@@ -257,6 +258,13 @@ def _noop_sleep(_seconds):
257258
return None
258259

259260

261+
def _coerce_order_quantity(value):
262+
try:
263+
return float(str(value).replace(",", "").strip())
264+
except (TypeError, ValueError):
265+
return value
266+
267+
260268
def _safe_haven_cash_symbols(*, portfolio: dict, allocation: dict) -> tuple[str, ...]:
261269
symbols: list[str] = []
262270
for symbol in allocation.get("safe_haven_symbols", ()):
@@ -565,6 +573,7 @@ def execute_rebalance_cycle(
565573
logs: list[str] = []
566574
skip_logs: list[str] = []
567575
note_logs: list[str] = []
576+
dry_run_orders: list[dict] = []
568577
small_account_cash_note_keys: set[str] = set()
569578
action_done = False
570579
sell_submitted = False
@@ -678,6 +687,18 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
678687
price_text = f"${price:.2f}" if price is not None else translator("order_type_market")
679688
side_key = "side_buy" if str(side).lower() == "buy" else "side_sell"
680689
order_type_key = "order_type_limit" if order_type == "limit" else "order_type_market"
690+
order_payload = {
691+
"symbol": str(symbol or "").strip().upper(),
692+
"side": str(side or "").strip().lower(),
693+
"quantity": _coerce_order_quantity(quantity),
694+
"order_type": str(order_type or "").strip().lower(),
695+
"status": "dry_run",
696+
}
697+
if price is not None:
698+
order_payload["price"] = round(float(price), 4)
699+
if order_type == "limit":
700+
order_payload["limit_price"] = round(float(price), 4)
701+
dry_run_orders.append(order_payload)
681702
message = translator(
682703
"dry_run_order",
683704
side=translator(side_key),
@@ -1110,4 +1131,5 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
11101131
skip_logs=tuple(skip_logs),
11111132
note_logs=tuple(note_logs),
11121133
action_done=action_done,
1134+
dry_run_orders=tuple(dry_run_orders),
11131135
)

main.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,20 @@ def _summarize_cycle_result_for_report(cycle_result, *, dry_run: bool) -> dict:
132132
logs = tuple(getattr(cycle_result, "logs", ()) or ())
133133
skip_logs = tuple(getattr(cycle_result, "skip_logs", ()) or ())
134134
note_logs = tuple(getattr(cycle_result, "note_logs", ()) or ())
135+
dry_run_orders = tuple(getattr(cycle_result, "dry_run_orders", ()) or ())
135136
order_events_count = len(logs)
136-
orders_previewed_count = order_events_count if dry_run else 0
137-
return {
137+
orders_previewed_count = len(dry_run_orders) if dry_run_orders else (order_events_count if dry_run else 0)
138+
summary = {
138139
"action_done": bool(getattr(cycle_result, "action_done", False)),
139140
"order_events_count": order_events_count,
140141
"orders_previewed_count": orders_previewed_count,
141142
"orders_skipped_count": len(skip_logs),
142143
"notes_count": len(note_logs),
143144
"dry_run_order_preview_available": bool(dry_run and orders_previewed_count > 0),
144145
}
146+
if dry_run_orders:
147+
summary["orders_previewed"] = [dict(order) for order in dry_run_orders]
148+
return summary
145149

146150

147151
signal_text = build_signal_text(t)

tests/test_rebalance_service.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,9 @@ def estimate_max_purchase_quantity(_trade_context, symbol, **_kwargs):
380380
self.assertIn("02800.HK", joined_logs)
381381
self.assertIn("03033.HK", joined_logs)
382382
self.assertNotIn(".US", joined_logs)
383+
self.assertGreaterEqual(len(result.dry_run_orders), 1)
384+
self.assertTrue(all(order["status"] == "dry_run" for order in result.dry_run_orders))
385+
self.assertTrue(all(order["symbol"].endswith(".HK") for order in result.dry_run_orders))
383386

384387
def test_run_strategy_prefers_portfolio_port_runtime_path(self):
385388
sent_messages = []

tests/test_request_handling.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,10 @@ def test_cycle_result_summary_counts_dry_run_order_previews(self):
582582
skip_logs=("skip",),
583583
note_logs=("note",),
584584
action_done=True,
585+
dry_run_orders=(
586+
{"symbol": "02800.HK", "side": "buy", "quantity": 100, "status": "dry_run"},
587+
{"symbol": "03033.HK", "side": "buy", "quantity": 200, "status": "dry_run"},
588+
),
585589
)
586590

587591
summary = module._summarize_cycle_result_for_report(cycle_result, dry_run=True)
@@ -592,6 +596,7 @@ def test_cycle_result_summary_counts_dry_run_order_previews(self):
592596
self.assertEqual(summary["orders_skipped_count"], 1)
593597
self.assertEqual(summary["notes_count"], 1)
594598
self.assertTrue(summary["dry_run_order_preview_available"])
599+
self.assertEqual(summary["orders_previewed"][0]["symbol"], "02800.HK")
595600

596601

597602
if __name__ == "__main__":

0 commit comments

Comments
 (0)