From 5c86864d2f6a4472950596b7df274cb87c81d730 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Thu, 28 May 2026 23:47:37 +0800 Subject: [PATCH] 1 --- app/db/paper_trading.py | 15 +++++++++--- tests/test_paper_trading.py | 46 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/app/db/paper_trading.py b/app/db/paper_trading.py index 96b499f..d9d525e 100644 --- a/app/db/paper_trading.py +++ b/app/db/paper_trading.py @@ -1122,9 +1122,18 @@ def _fill_paper_order(conn, order: dict, rec: dict, current_price: float, event_ base_notional = _safe_float(order.get("notional_usdt"), default_notional_usdt(cfg)) global_ok, global_detail = _global_risk_entry_check(conn, rec, base_notional, cfg) if not global_ok: - result = _cancel_paper_order(conn, order, "global_risk_rejected", event_time) - result["risk_detail"] = global_detail - return result + # Market/account risk is a temporary execution gate, not proof that the + # original limit order is invalid. Keep the order pending so it can fill + # later if risk improves, instead of instantly wiping every touched order. + conn.execute("UPDATE paper_orders SET updated_at=%s WHERE id=%s", (event_time, order["id"])) + return { + "skipped": True, + "reason": "paper_order_risk_paused", + "paper_order_id": order.get("id"), + "target_price": order.get("target_price"), + "current_price": current_price, + "risk_detail": global_detail, + } adjusted_notional = _market_risk_adjusted_notional(base_notional, global_detail, cfg) pause_ok, pause_reason, pause_detail = _portfolio_entry_pause_check(conn, adjusted_notional, event_time, cfg) if not pause_ok: diff --git a/tests/test_paper_trading.py b/tests/test_paper_trading.py index 57cc6ed..b0973da 100644 --- a/tests/test_paper_trading.py +++ b/tests/test_paper_trading.py @@ -761,6 +761,52 @@ def test_pending_paper_order_reconciles_from_latest_price_cache(monkeypatch): assert order["fill_price"] == pytest.approx(95) +def test_touched_wait_pullback_order_stays_pending_when_global_risk_pauses(monkeypatch): + monkeypatch.setenv("ALPHAX_PAPER_TRADING_ENABLED", "1") + monkeypatch.setenv("ALPHAX_PAPER_GLOBAL_RISK_GATE_ENABLED", "1") + monkeypatch.setattr( + "app.db.paper_trading.evaluate_global_risk", + lambda **kwargs: { + "allow_new_entries": False, + "decision": "block_critical_weak_score", + "risk_level": "critical", + "reasons": ["critical 市场环境下推荐分不足"], + }, + ) + altcoin_db.init_db() + rec_id = altcoin_db.create_recommendation( + symbol="RISKPAUSE/USDT", + rec_state="蓄力", + rec_score=22, + entry_price=95, + stop_loss=90, + tp1=105, + tp2=112, + signals=["等待回踩"], + entry_plan={"entry_action": "等回踩", "entry_price": 95, "stop_loss": 90, "tp1": 105, "risk_reward_ok": True, "rr1": 2.0}, + ) + rec = { + "id": rec_id, + "symbol": "RISKPAUSE/USDT", + "execution_status": "wait_pullback", + "action_status": "等回踩", + "entry_price": 95, + "stop_loss": 90, + "tp1": 105, + "tp2": 112, + "entry_plan": {"entry_action": "等回踩", "entry_price": 95, "stop_loss": 90, "tp1": 105, "risk_reward_ok": True, "rr1": 2.0}, + } + + created = sync_recommendation(rec, 100, event_time="2026-05-16T10:00:00") + paused = sync_recommendation(rec, 94.9, event_time="2026-05-16T10:05:00") + + assert created["reason"] == "paper_order_created" + assert paused["reason"] == "paper_order_risk_paused" + assert list_paper_trades()["total"] == 0 + assert list_paper_orders(status="pending")["items"][0]["symbol"] == "RISKPAUSE/USDT" + assert list_paper_orders(status="canceled")["total"] == 0 + + def test_wait_pullback_order_cancels_when_recommendation_invalid(monkeypatch): monkeypatch.setenv("ALPHAX_PAPER_TRADING_ENABLED", "1") altcoin_db.init_db()