08082eb88f
Unify display on trend_leg_display_price: use recorded fills or grid triggers only; last done row avg comes from live entry, never back-solve trigger prices. Co-authored-by: Cursor <cursoragent@cursor.com>
102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
"""趋势回调运行中计划:实际成交价重算补仓表与金额盈亏比。"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from strategy_snapshot_lib import attach_trend_dca_levels # noqa: E402
|
|
from strategy_trend_lib import ( # noqa: E402
|
|
calc_trend_plan_money_metrics,
|
|
trend_leg_display_price,
|
|
)
|
|
|
|
|
|
class TestTrendDcaEnrichFills(unittest.TestCase):
|
|
def _base_plan(self, **overrides):
|
|
plan = {
|
|
"direction": "long",
|
|
"stop_loss": 0.329,
|
|
"take_profit": 0.476,
|
|
"first_order_amount": 115,
|
|
"snapshot_available_usdt": 97.98,
|
|
"risk_percent": 5,
|
|
"contract_size": 1.0,
|
|
"grid_prices_json": json.dumps([0.3465, 0.343, 0.3395, 0.336, 0.3325]),
|
|
"leg_amounts_json": json.dumps([23, 23, 23, 23, 23]),
|
|
"dca_legs": 5,
|
|
"first_order_done": 1,
|
|
"legs_done": 0,
|
|
"avg_entry_price": 0.3537,
|
|
"order_amount_open": 115,
|
|
"target_order_amount": 230,
|
|
"leg_fill_prices_json": json.dumps([0.3537]),
|
|
}
|
|
plan.update(overrides)
|
|
return plan
|
|
|
|
def test_header_money_rr_not_price_rr(self):
|
|
plan = self._base_plan()
|
|
metrics = calc_trend_plan_money_metrics(plan)
|
|
self.assertAlmostEqual(metrics["risk_amount_u"], 4.899, places=2)
|
|
self.assertIsNotNone(metrics["money_rr"])
|
|
self.assertLess(metrics["money_rr"], 4.0)
|
|
|
|
def test_done_dca_uses_actual_fill_price(self):
|
|
plan = self._base_plan(
|
|
legs_done=1,
|
|
avg_entry_price=0.3512,
|
|
order_amount_open=138,
|
|
leg_fill_prices_json=json.dumps([0.3537, 0.3458]),
|
|
)
|
|
enriched = attach_trend_dca_levels(plan)
|
|
levels = enriched["dca_levels"]
|
|
self.assertEqual(len(levels), 6)
|
|
dca1 = levels[1]
|
|
self.assertEqual(dca1["status"], "done")
|
|
self.assertAlmostEqual(dca1["price"], 0.3458, places=4)
|
|
self.assertIsNotNone(dca1["avg_entry"])
|
|
self.assertIsNotNone(dca1["rr"])
|
|
dca2 = levels[2]
|
|
self.assertEqual(dca2["status"], "pending")
|
|
self.assertAlmostEqual(dca2["price"], 0.343, places=4)
|
|
|
|
def test_missing_dca_fills_use_grid_trigger_not_inferred_price(self):
|
|
"""缺补仓成交价时:触发价用计划网格,末档均价对齐头部,禁止反推离谱成交价。"""
|
|
plan = self._base_plan(
|
|
legs_done=2,
|
|
avg_entry_price=0.3507,
|
|
order_amount_open=161,
|
|
leg_fill_prices_json=json.dumps([0.3436]),
|
|
grid_prices_json=json.dumps([0.343, 0.343, 0.3395, 0.336, 0.3325]),
|
|
)
|
|
enriched = attach_trend_dca_levels(plan)
|
|
levels = enriched["dca_levels"]
|
|
dca1 = levels[1]
|
|
dca2 = levels[2]
|
|
self.assertEqual(dca1["status"], "done")
|
|
self.assertAlmostEqual(dca1["price"], 0.343, places=4)
|
|
self.assertEqual(dca2["status"], "done")
|
|
self.assertAlmostEqual(dca2["price"], 0.343, places=4)
|
|
self.assertAlmostEqual(dca2["avg_entry"], 0.3507, places=4)
|
|
self.assertLess(dca2["price"], 0.36)
|
|
|
|
def test_display_price_never_infers_from_target_avg(self):
|
|
"""四所共用:缺记录时只用网格,不因均价反推离谱触发价。"""
|
|
plan = self._base_plan(
|
|
legs_done=2,
|
|
avg_entry_price=0.3507,
|
|
leg_fill_prices_json=json.dumps([0.3436]),
|
|
grid_prices_json=json.dumps([0.343, 0.343, 0.3395, 0.336, 0.3325]),
|
|
)
|
|
self.assertAlmostEqual(trend_leg_display_price(plan, 2), 0.343, places=4)
|
|
self.assertLess(trend_leg_display_price(plan, 2), 0.36)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|