增加策略交易
This commit is contained in:
@@ -1511,6 +1511,9 @@ def init_db():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from strategy_db import init_strategy_tables
|
||||
|
||||
init_strategy_tables(conn)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -2839,12 +2842,11 @@ def parse_and_compute_trend_pullback_plan(form_dict):
|
||||
return None, "杠杆格式错误"
|
||||
if leverage <= 0 or risk_percent <= 0:
|
||||
return None, "杠杆与风险比例必须大于0"
|
||||
if direction == "long":
|
||||
if not (stop_loss < add_upper):
|
||||
return None, "做多:止损价须低于补仓上沿"
|
||||
else:
|
||||
if not (stop_loss > add_upper):
|
||||
return None, "做空:止损价须高于补仓下沿"
|
||||
from strategy_trend_lib import validate_trend_bounds
|
||||
|
||||
bound_err = validate_trend_bounds(direction, stop_loss, add_upper)
|
||||
if bound_err:
|
||||
return None, bound_err
|
||||
snap = get_available_trading_usdt()
|
||||
if snap is None or snap <= 0:
|
||||
return None, "无法读取合约账户 USDT 可用余额,请检查 API 与账户类型"
|
||||
@@ -2873,10 +2875,21 @@ def parse_and_compute_trend_pullback_plan(form_dict):
|
||||
)
|
||||
if remainder_total is None:
|
||||
remainder_total = 0.0
|
||||
n_legs, leg_json, per_ref = _trend_build_leg_amounts_json(exchange_symbol, remainder_total, TREND_PULLBACK_DCA_LEGS)
|
||||
from strategy_trend_lib import build_grid_prices, build_leg_amounts_json
|
||||
|
||||
ensure_markets_loaded()
|
||||
market = exchange.market(exchange_symbol)
|
||||
min_amt = float((market.get("limits", {}).get("amount", {}) or {}).get("min") or 0)
|
||||
n_legs, leg_json, per_ref = build_leg_amounts_json(
|
||||
exchange_symbol,
|
||||
remainder_total,
|
||||
TREND_PULLBACK_DCA_LEGS,
|
||||
_safe_amount_to_precision,
|
||||
min_amt,
|
||||
)
|
||||
if n_legs <= 0:
|
||||
return None, "剩余计划张数不足以拆出补仓档(低于交易所最小张数),请提高风险比例、放宽止损与补仓区间间距,或减少补仓档数"
|
||||
grid = _trend_build_grid_prices(direction, stop_loss, add_upper, n_legs)
|
||||
grid = build_grid_prices(direction, stop_loss, add_upper, n_legs)
|
||||
if len(grid) != n_legs:
|
||||
return None, "补仓网格生成失败"
|
||||
opened_at = app_now_str()
|
||||
@@ -5278,7 +5291,7 @@ def render_main_page(page="trade"):
|
||||
preview_expires_ms = None
|
||||
trend_preview_expired = False
|
||||
trend_preview_id_arg = ""
|
||||
if page == "trade":
|
||||
if page == "strategy_trend":
|
||||
_trend_cleanup_stale_previews(conn)
|
||||
trend_preview_id_arg = (request.args.get("preview_id") or "").strip()
|
||||
if trend_preview_id_arg:
|
||||
@@ -6182,17 +6195,17 @@ def preview_trend_pullback():
|
||||
if not okp:
|
||||
conn.close()
|
||||
flash(reasonp)
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
ok_live, reason_live = ensure_exchange_live_ready()
|
||||
if not ok_live:
|
||||
conn.close()
|
||||
flash(reason_live)
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
payload, err = parse_and_compute_trend_pullback_plan(request.form)
|
||||
if err:
|
||||
conn.close()
|
||||
flash(err)
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
pid = str(uuid.uuid4())
|
||||
exp_ms = int(time.time() * 1000) + int(TREND_PULLBACK_PREVIEW_TTL_SECONDS) * 1000
|
||||
created = app_now_str()
|
||||
@@ -6231,7 +6244,7 @@ def preview_trend_pullback():
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash(f"预览已生成,有效期 {TREND_PULLBACK_PREVIEW_TTL_SECONDS} 秒,请核对后点击「确认执行」。")
|
||||
return redirect(url_for("trade_page", preview_id=pid))
|
||||
return redirect(url_for("strategy_trend_page", preview_id=pid))
|
||||
|
||||
|
||||
@app.route("/execute_trend_pullback", methods=["POST"])
|
||||
@@ -6240,7 +6253,7 @@ def execute_trend_pullback():
|
||||
pid = (request.form.get("preview_id") or "").strip()
|
||||
if not pid:
|
||||
flash("缺少预览 ID")
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
conn = get_db()
|
||||
_trend_cleanup_stale_previews(conn)
|
||||
pr = conn.execute("SELECT * FROM trend_pullback_previews WHERE id=?", (pid,)).fetchone()
|
||||
@@ -6248,30 +6261,30 @@ def execute_trend_pullback():
|
||||
if not pr or int(pr["expires_at_ms"] or 0) < now_ms:
|
||||
conn.close()
|
||||
flash("预览已过期或不存在,请重新生成预览")
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
okp, reasonp = precheck_trend_pullback_start(conn)
|
||||
if not okp:
|
||||
conn.close()
|
||||
flash(reasonp)
|
||||
return redirect(url_for("trade_page", preview_id=pid))
|
||||
return redirect(url_for("strategy_trend_page", preview_id=pid))
|
||||
ok_live, reason_live = ensure_exchange_live_ready()
|
||||
if not ok_live:
|
||||
conn.close()
|
||||
flash(reason_live)
|
||||
return redirect(url_for("trade_page", preview_id=pid))
|
||||
return redirect(url_for("strategy_trend_page", preview_id=pid))
|
||||
snap_prev = float(pr["snapshot_available_usdt"] or 0)
|
||||
snap_now = get_available_trading_usdt()
|
||||
if snap_now is None or snap_now <= 0:
|
||||
conn.close()
|
||||
flash("无法读取当前合约可用余额,请稍后重试")
|
||||
return redirect(url_for("trade_page", preview_id=pid))
|
||||
return redirect(url_for("strategy_trend_page", preview_id=pid))
|
||||
drift_pct = abs(float(snap_now) - snap_prev) / max(snap_prev, 1e-9) * 100.0
|
||||
if drift_pct > float(TREND_PREVIEW_MAX_BALANCE_DRIFT_PCT):
|
||||
conn.close()
|
||||
flash(
|
||||
f"当前可用余额与预览快照偏差 {drift_pct:.2f}%,超过允许 {TREND_PREVIEW_MAX_BALANCE_DRIFT_PCT}% ,请重新生成预览"
|
||||
)
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
symbol = pr["symbol"]
|
||||
exchange_symbol = pr["exchange_symbol"]
|
||||
direction = pr["direction"] or "long"
|
||||
@@ -6293,7 +6306,7 @@ def execute_trend_pullback():
|
||||
if live_price is None:
|
||||
conn.close()
|
||||
flash("获取实时价格失败")
|
||||
return redirect(url_for("trade_page", preview_id=pid))
|
||||
return redirect(url_for("strategy_trend_page", preview_id=pid))
|
||||
try:
|
||||
o1 = place_exchange_order(exchange_symbol, direction, first_amt, leverage, stop_loss=None, take_profit=None)
|
||||
fill1 = resolve_order_entry_price(o1, exchange_symbol, live_price)
|
||||
@@ -6301,7 +6314,7 @@ def execute_trend_pullback():
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
flash(friendly_exchange_error(e, available_usdt=snap_now))
|
||||
return redirect(url_for("trade_page", preview_id=pid))
|
||||
return redirect(url_for("strategy_trend_page", preview_id=pid))
|
||||
now = app_now()
|
||||
trading_day = get_trading_day(now)
|
||||
opened_at = app_now_str()
|
||||
@@ -6356,7 +6369,7 @@ def execute_trend_pullback():
|
||||
f"趋势回调已执行:可用余额(执行时){round(snap, 2)}U;计划保证金约 {round(margin_plan, 2)}U;"
|
||||
f"总张数约 {target_amt},首仓 {first_amt},补仓 {n_legs} 档;已挂交易所止损,止盈由程序监控。"
|
||||
)
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
|
||||
|
||||
@app.route("/cancel_trend_pullback_preview", methods=["POST"])
|
||||
@@ -6373,7 +6386,7 @@ def cancel_trend_pullback_preview():
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash("已取消预览")
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
|
||||
|
||||
@app.route("/trend_pullback_breakeven/<int:pid>", methods=["POST"])
|
||||
@@ -6388,7 +6401,7 @@ def trend_pullback_breakeven(pid):
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
flash("保本偏移% 格式无效")
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM trend_pullback_plans WHERE id=? AND status='active'", (pid,)
|
||||
@@ -6396,7 +6409,7 @@ def trend_pullback_breakeven(pid):
|
||||
if not row:
|
||||
conn.close()
|
||||
flash("未找到运行中的趋势回调计划")
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
ok, err = apply_trend_pullback_manual_breakeven(conn, row, offset_pct=offset_pct)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -6404,7 +6417,7 @@ def trend_pullback_breakeven(pid):
|
||||
flash("已手动保本:交易所止损已按均价+偏移更新")
|
||||
else:
|
||||
flash(err or "手动保本失败")
|
||||
return redirect(url_for("trade_page"))
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
|
||||
|
||||
@app.route("/stop_trend_pullback/<int:pid>")
|
||||
@@ -7358,6 +7371,19 @@ except Exception as _hub_err:
|
||||
print(f"[hub_bridge] gate_bot: {_hub_err}")
|
||||
|
||||
|
||||
def strategy_trend_page():
|
||||
return render_main_page("strategy_trend")
|
||||
|
||||
|
||||
from strategy_config import build_strategy_config
|
||||
from strategy_register import attach_strategy_templates, register_strategy_trading
|
||||
|
||||
attach_strategy_templates(app, _REPO_ROOT)
|
||||
_strategy_cfg = build_strategy_config(sys.modules[__name__], trend_enabled=True)
|
||||
_strategy_cfg["render_trend_page"] = login_required(strategy_trend_page)
|
||||
register_strategy_trading(app, _strategy_cfg)
|
||||
|
||||
|
||||
# 启动
|
||||
if __name__ == "__main__":
|
||||
threading.Thread(target=background_task, daemon=True).start()
|
||||
|
||||
Reference in New Issue
Block a user