Add period selector and crypto-style trend plan preview table.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user