Restructure into modules/ with single-process CTP and config/ layout.
Move business code under modules/, env template to config/, PM2 single qihuo process, and _legacy shims for old imports. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
# 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
# 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。
|
||||
# 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md
|
||||
|
||||
"""趋势回调:纯计算(期货整数手)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
from modules.core.contract_specs import get_contract_spec
|
||||
|
||||
|
||||
def validate_trend_bounds(direction: str, stop_loss: float, add_upper: float) -> Optional[str]:
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "long":
|
||||
if not (float(stop_loss) < float(add_upper)):
|
||||
return "做多:止损须低于补仓上沿"
|
||||
else:
|
||||
if not (float(stop_loss) > float(add_upper)):
|
||||
return "做空:止损须高于补仓下沿"
|
||||
return None
|
||||
|
||||
|
||||
def build_grid_prices(direction: str, sl: float, upper: float, n_legs: int) -> list[float]:
|
||||
sl, upper = float(sl), float(upper)
|
||||
out: list[float] = []
|
||||
if n_legs <= 0:
|
||||
return out
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "long":
|
||||
if upper <= sl:
|
||||
return out
|
||||
span = upper - sl
|
||||
for i in range(1, n_legs + 1):
|
||||
out.append(sl + (i / float(n_legs + 1)) * span)
|
||||
out.sort(reverse=True)
|
||||
else:
|
||||
if sl <= upper:
|
||||
return out
|
||||
span = sl - upper
|
||||
for i in range(1, n_legs + 1):
|
||||
out.append(upper + (i / float(n_legs + 1)) * span)
|
||||
out.sort()
|
||||
return [round(p, 4) for p in out]
|
||||
|
||||
|
||||
def compute_trend_plan_futures(
|
||||
*,
|
||||
direction: str,
|
||||
stop_loss: float,
|
||||
add_upper: float,
|
||||
take_profit: float,
|
||||
risk_percent: float,
|
||||
capital: float,
|
||||
live_price: float,
|
||||
ths_code: str,
|
||||
dca_legs: int = 5,
|
||||
) -> Tuple[Optional[dict[str, Any]], Optional[str]]:
|
||||
err = validate_trend_bounds(direction, stop_loss, add_upper)
|
||||
if err:
|
||||
return None, err
|
||||
spec = get_contract_spec(ths_code)
|
||||
mult = spec["mult"]
|
||||
d = (direction or "long").strip().lower()
|
||||
if d == "short":
|
||||
worst_per_lot = (float(stop_loss) - float(add_upper)) * mult
|
||||
else:
|
||||
worst_per_lot = (float(add_upper) - float(stop_loss)) * mult
|
||||
if worst_per_lot <= 0:
|
||||
return None, "止损与补仓边界无法计算风险"
|
||||
budget = float(capital) * float(risk_percent) / 100.0
|
||||
total_lots = int(math.floor(budget / worst_per_lot))
|
||||
if total_lots < 3:
|
||||
return None, f"按 {risk_percent}% 风险,总手数至少需 3 手才能拆分首仓+补仓(当前 {total_lots} 手)"
|
||||
first_lots = total_lots // 2
|
||||
remainder = total_lots - first_lots
|
||||
legs = max(1, min(int(dca_legs), remainder))
|
||||
per_leg = remainder // legs
|
||||
leg_amounts = [per_leg] * (legs - 1) + [remainder - per_leg * (legs - 1)]
|
||||
if any(x < 1 for x in leg_amounts):
|
||||
legs = 1
|
||||
leg_amounts = [remainder]
|
||||
grid = build_grid_prices(d, stop_loss, add_upper, len(leg_amounts))
|
||||
margin_rate = spec["margin_rate"]
|
||||
plan_margin = float(live_price) * mult * total_lots * margin_rate
|
||||
return {
|
||||
"direction": d,
|
||||
"stop_loss": float(stop_loss),
|
||||
"add_upper": float(add_upper),
|
||||
"take_profit": float(take_profit),
|
||||
"risk_percent": float(risk_percent),
|
||||
"capital_snapshot": float(capital),
|
||||
"live_price_ref": float(live_price),
|
||||
"target_lots": total_lots,
|
||||
"first_lots": first_lots,
|
||||
"remainder_lots": remainder,
|
||||
"dca_legs": len(leg_amounts),
|
||||
"leg_amounts": leg_amounts,
|
||||
"leg_amounts_json": json.dumps(leg_amounts),
|
||||
"grid_prices_json": json.dumps(grid),
|
||||
"grid": grid,
|
||||
"plan_margin": round(plan_margin, 2),
|
||||
"mult": mult,
|
||||
}, None
|
||||
|
||||
|
||||
def trend_dca_level_reached(direction: str, mark_price: float, level: float) -> bool:
|
||||
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 modules.market.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