Add period selector and crypto-style trend plan preview table.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 22:52:47 +08:00
parent d3b92de703
commit 24190bf679
6 changed files with 253 additions and 25 deletions
+6 -1
View File
@@ -61,7 +61,8 @@ CREATE TABLE IF NOT EXISTS trend_pullback_plans (
avg_entry_price REAL,
lots_open INTEGER DEFAULT 0,
opened_at TEXT,
message TEXT
message TEXT,
period TEXT DEFAULT '15m'
)
"""
@@ -138,6 +139,10 @@ def init_strategy_tables(conn) -> None:
CTP_SIM_POSITIONS_SQL,
):
conn.execute(sql)
try:
conn.execute("ALTER TABLE trend_pullback_plans ADD COLUMN period TEXT DEFAULT '15m'")
except Exception:
pass
if not conn.execute("SELECT id FROM ctp_sim_account WHERE id=1").fetchone():
conn.execute("INSERT INTO ctp_sim_account (id, balance, available) VALUES (1, 100000, 100000)")
conn.commit()
+120
View File
@@ -111,3 +111,123 @@ def trend_dca_level_reached(direction: str, mark_price: float, level: float) ->
d = (direction or "long").strip().lower()
pf, lv = float(mark_price), float(level)
return pf <= lv if d == "long" else pf >= lv
def trend_strategy_periods() -> list[dict[str, str]]:
"""策略页可选 K 线周期。"""
from kline_chart import MARKET_PERIODS
skip = frozenset({"timeshare", "w"})
return [p for p in MARKET_PERIODS if p["key"] not in skip]
def trend_period_label(key: str) -> str:
k = (key or "").strip()
for p in trend_strategy_periods():
if p["key"] == k:
return p["label"]
return k or "15分"
def normalize_trend_period(key: str) -> str:
valid = {p["key"] for p in trend_strategy_periods()}
k = (key or "15m").strip()
return k if k in valid else "15m"
def _avg_after_entries(entries: list[tuple[float, int]]) -> float:
total = sum(q for _, q in entries)
if total <= 0:
return 0.0
return sum(p * q for p, q in entries) / total
def enrich_trend_plan_preview(
plan: dict,
*,
symbol: str,
symbol_name: str = "",
period: str = "15m",
) -> dict[str, Any]:
"""补全预览:周期、风险金额、分档表格(对齐币圈预览样式)。"""
out = dict(plan)
d = (out.get("direction") or "long").strip().lower()
sl = float(out["stop_loss"])
tp = float(out["take_profit"])
mult = float(out.get("mult") or 1)
entry0 = float(out["live_price_ref"])
first_lots = int(out["first_lots"])
leg_amounts = [int(x) for x in (out.get("leg_amounts") or [])]
grid = [float(x) for x in (out.get("grid") or [])]
capital = float(out.get("capital_snapshot") or 0)
risk_pct = float(out.get("risk_percent") or 0)
budget = capital * risk_pct / 100.0
remainder = int(out.get("remainder_lots") or sum(leg_amounts))
out["symbol"] = symbol
out["symbol_name"] = symbol_name or symbol
out["period"] = normalize_trend_period(period)
out["period_label"] = trend_period_label(out["period"])
out["stop_loss_budget"] = round(budget, 2)
out["direction_label"] = "做多" if d == "long" else "做空"
entries: list[tuple[float, int]] = [(entry0, first_lots)]
rows: list[dict[str, Any]] = []
def leg_metrics() -> tuple[float, float, float, Optional[float]]:
total = sum(q for _, q in entries)
avg = _avg_after_entries(entries)
if d == "long":
profit = (tp - avg) * total * mult
loss = (avg - sl) * total * mult
else:
profit = (avg - tp) * total * mult
loss = (sl - avg) * total * mult
rr = profit / loss if loss > 0 else None
return (
round(avg, 4),
round(profit, 2),
round(loss, 2),
round(rr, 2) if rr is not None else None,
)
avg, profit, loss, rr = leg_metrics()
rows.append({
"level": "首仓",
"price": round(entry0, 4),
"lots": first_lots,
"avg_after": avg,
"profit_at_tp": profit,
"loss_at_sl": loss,
"rr_ratio": rr,
})
out["first_rr_ratio"] = rr
for i, lots in enumerate(leg_amounts):
price = grid[i] if i < len(grid) else sl
entries.append((float(price), int(lots)))
avg, profit, loss, rr = leg_metrics()
rows.append({
"level": f"补仓{i + 1}",
"price": round(float(price), 4),
"lots": int(lots),
"avg_after": avg,
"profit_at_tp": profit,
"loss_at_sl": loss,
"rr_ratio": rr,
})
out["preview_rows"] = rows
out["summary_line"] = (
f"{out['symbol_name']} {out['symbol']} {out['direction_label']} {out['period_label']}"
f" | 权益 {capital:.2f}"
f" | 参考价 {entry0}"
f" | 计划保证金 ≈ {out.get('plan_margin')}"
f" | 总手 {out.get('target_lots')}(首仓 {first_lots} + 补仓 {remainder}"
)
out["detail_line"] = (
f"止损价 {sl} | 止损金额 {out['stop_loss_budget']} 元(权益 × 风险 {risk_pct}%"
f" | 补仓边界 {float(out['add_upper'])} | 止盈价 {tp}"
f" | 首仓盈亏比 {out['first_rr_ratio'] if out['first_rr_ratio'] is not None else ''}"
)
return out