diff --git a/install_trading.py b/install_trading.py index 03ef6d9..6cfbcf4 100644 --- a/install_trading.py +++ b/install_trading.py @@ -67,7 +67,14 @@ from risk.account_risk_lib import ( from strategy.strategy_db import init_strategy_tables from strategy.strategy_roll_lib import preview_roll from strategy.strategy_snapshot_lib import list_snapshots, save_snapshot -from strategy.strategy_trend_lib import compute_trend_plan_futures, trend_dca_level_reached +from strategy.strategy_trend_lib import ( + compute_trend_plan_futures, + enrich_trend_plan_preview, + normalize_trend_period, + trend_dca_level_reached, + trend_period_label, + trend_strategy_periods, +) from strategy.strategy_snapshot_lib import STRATEGY_ROLL, STRATEGY_TREND from symbols import ths_to_codes, resolve_main_contract, PRODUCTS, PRODUCT_CATEGORIES, position_symbol_meta from trading_context import ( @@ -1998,15 +2005,19 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se roll_groups = conn.execute( "SELECT * FROM roll_groups WHERE status='active' ORDER BY id DESC" ).fetchall() + active_trend_row = dict(active_trend) if active_trend else None + if active_trend_row: + active_trend_row["period_label"] = trend_period_label(active_trend_row.get("period") or "15m") conn.close() return render_template( "strategy.html", capital=capital, risk_percent=get_risk_percent(get_setting), sizing_mode=get_sizing_mode(get_setting), - active_trend=dict(active_trend) if active_trend else None, + active_trend=active_trend_row, monitors=[dict(m) for m in monitors], roll_groups=[dict(g) for g in roll_groups], + trend_periods=trend_strategy_periods(), ) @app.route("/strategy/records") @@ -2471,6 +2482,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se ) if err: return jsonify({"ok": False, "error": err}), 400 + period = normalize_trend_period(d.get("period")) + sym_name = (d.get("symbol_name") or "").strip() + if not sym_name and codes: + sym_name = codes.get("name") or sym + plan = enrich_trend_plan_preview( + plan, symbol=sym, symbol_name=sym_name, period=period, + ) return jsonify({"ok": True, "plan": plan}) @app.route("/api/strategy/trend/execute", methods=["POST"]) @@ -2500,6 +2518,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se if perr: conn.close() return jsonify({"ok": False, "error": perr}), 400 + period = normalize_trend_period(d.get("period")) + sym_name = (d.get("symbol_name") or "").strip() + if not sym_name and codes: + sym_name = codes.get("name") or sym + plan = enrich_trend_plan_preview( + plan, symbol=sym, symbol_name=sym_name, period=period, + ) mode = get_trading_mode(get_setting) try: execute_order( @@ -2515,15 +2540,15 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se status, symbol, symbol_name, direction, stop_loss, add_upper, take_profit, risk_percent, capital_snapshot, plan_margin, target_lots, first_lots, remainder_lots, dca_legs, leg_amounts_json, grid_prices_json, first_order_done, avg_entry_price, - lots_open, opened_at + lots_open, opened_at, period ) VALUES ('active',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?,?)""", ( - sym, codes.get("name", sym) if codes else sym, plan["direction"], + sym, sym_name or (codes.get("name", sym) if codes else sym), plan["direction"], plan["stop_loss"], plan["add_upper"], plan["take_profit"], plan["risk_percent"], plan["capital_snapshot"], plan["plan_margin"], plan["target_lots"], plan["first_lots"], plan["remainder_lots"], plan["dca_legs"], plan["leg_amounts_json"], plan["grid_prices_json"], - price, plan["first_lots"], now, + price, plan["first_lots"], now, plan["period"], ), ) plan_id = cur.lastrowid diff --git a/scripts/deploy_trend_preview.py b/scripts/deploy_trend_preview.py new file mode 100644 index 0000000..1d5ccf3 --- /dev/null +++ b/scripts/deploy_trend_preview.py @@ -0,0 +1,26 @@ +"""Deploy trend callback period + rich preview.""" +import paramiko +import sys +from pathlib import Path + +sys.stdout.reconfigure(encoding="utf-8", errors="replace") +root = Path(__file__).resolve().parents[1] +files = [ + "strategy/strategy_trend_lib.py", + "strategy/strategy_db.py", + "install_trading.py", + "templates/strategy.html", + "static/js/strategy.js", +] + +c = paramiko.SSHClient() +c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +c.connect("192.168.8.21", username="root", password="woaini88", timeout=15) +sftp = c.open_sftp() +for rel in files: + sftp.put(str(root / rel), f"/opt/qihuo/{rel.replace(chr(92), '/')}") + print("uploaded", rel) +sftp.close() +_, o, _ = c.exec_command("cd /opt/qihuo && pm2 restart qihuo") +print(o.read().decode("utf-8", errors="replace")) +c.close() diff --git a/static/js/strategy.js b/static/js/strategy.js index 9495223..bda2ca2 100644 --- a/static/js/strategy.js +++ b/static/js/strategy.js @@ -20,29 +20,68 @@ return o; } - function showPreview(el, text, ok) { + function esc(s) { + return String(s == null ? '' : s) + .replace(/&/g, '&') + .replace(//g, '>'); + } + + function showPreview(el, content, ok, isHtml) { if (!el) return; - if (!text) { + if (!content) { el.hidden = true; el.textContent = ''; + el.innerHTML = ''; return; } el.hidden = false; - el.textContent = text; el.style.color = ok === false ? 'var(--loss)' : ''; + if (isHtml) { + el.innerHTML = content; + } else { + el.innerHTML = ''; + el.textContent = content; + } } - function formatPlan(plan) { + function fmtNum(v) { + if (v == null || v === '') return '—'; + return String(v); + } + + function renderTrendPlanHtml(plan) { if (!plan) return ''; - var lines = []; - if (plan.symbol) lines.push('品种:' + plan.symbol); - if (plan.target_lots != null) lines.push('目标手数:' + plan.target_lots); - if (plan.first_lots != null) lines.push('首仓:' + plan.first_lots + ' 手'); - if (plan.grid && plan.grid.length) { - lines.push('补仓档位:' + plan.grid.map(function (g) { return g.price; }).join(' → ')); + var summary = plan.summary_line || ( + (plan.symbol_name || plan.symbol || '') + ' ' + + (plan.direction_label || '') + ' ' + (plan.period_label || '') + ); + var detail = plan.detail_line || ''; + var rows = plan.preview_rows || []; + var html = '
| 档位 | 触发/参考价 | 手数 | 加仓后均价 | ' + + '止盈盈利(元) | 止损(元) | 盈亏比 | ' + + '
|---|---|---|---|---|---|---|
| ' + esc(row.level) + ' | ' + + '' + fmtNum(row.price) + ' | ' + + '' + fmtNum(row.lots) + ' | ' + + '' + fmtNum(row.avg_after) + ' | ' + + '' + fmtNum(row.profit_at_tp) + ' | ' + + '' + fmtNum(row.loss_at_sl) + ' | ' + + '' + fmtNum(row.rr_ratio) + ' |