e5a586f903
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>
168 lines
5.6 KiB
Python
168 lines
5.6 KiB
Python
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
|
"""HTTP routes for plans module."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
|
|
from flask import (
|
|
Response,
|
|
flash,
|
|
jsonify,
|
|
redirect,
|
|
render_template,
|
|
request,
|
|
send_file,
|
|
session,
|
|
stream_with_context,
|
|
url_for,
|
|
)
|
|
|
|
|
|
def register(deps) -> None:
|
|
app = deps.app
|
|
login_required = deps.login_required
|
|
require_nav = deps.require_nav
|
|
get_db = deps.get_db
|
|
get_setting = deps.get_setting
|
|
set_setting = deps.set_setting
|
|
fetch_price = deps.fetch_price
|
|
send_wechat_msg = deps.send_wechat_msg
|
|
touch_stats_cache = deps.touch_stats_cache
|
|
get_stats_data = deps.get_stats_data
|
|
build_market_quote_payload = deps.build_market_quote_payload
|
|
today_str = deps.today_str
|
|
expire_old_plans = deps.expire_old_plans
|
|
TZ = deps.tz
|
|
DB_PATH = deps.db_path
|
|
UPLOAD_DIR = deps.upload_dir
|
|
OPEN_TYPES = deps.open_types
|
|
EXIT_TRIGGERS = deps.exit_triggers
|
|
BEHAVIOR_TAGS = deps.behavior_tags
|
|
KLINE_PERIODS = deps.kline_periods
|
|
KLINE_CUTOFFS = deps.kline_cutoffs
|
|
calc_holding_duration = deps.calc_holding_duration
|
|
holding_to_minutes = deps.holding_to_minutes
|
|
classify_close_result = deps.classify_close_result
|
|
calc_rr_ratio = deps.calc_rr_ratio
|
|
calc_theoretical_pnl = deps.calc_theoretical_pnl
|
|
parse_review_date_filter = deps.parse_review_date_filter
|
|
_trading_mode = deps.trading_mode
|
|
_ua_is_phone = deps.ua_is_phone
|
|
_static_asset_v = deps.static_asset_v
|
|
|
|
@app.route("/api/plan_prices")
|
|
@login_required
|
|
def api_plan_prices():
|
|
"""今日计划:批量现价与距决策区间上/下沿距离。"""
|
|
today = today_str()
|
|
conn = get_db()
|
|
rows = conn.execute(
|
|
"SELECT id, symbol, market_code, sina_code, zone_upper, zone_lower "
|
|
"FROM order_plans WHERE plan_date=? AND status IN ('planned', 'active')",
|
|
(today,),
|
|
).fetchall()
|
|
conn.close()
|
|
out = []
|
|
for r in rows:
|
|
sym = r["symbol"]
|
|
market = r["market_code"] or ""
|
|
sina = r["sina_code"] or ""
|
|
upper = float(r["zone_upper"])
|
|
lower = float(r["zone_lower"])
|
|
price = fetch_price(sym, market, sina)
|
|
dist_upper = None
|
|
dist_lower = None
|
|
in_zone = False
|
|
if price is not None:
|
|
dist_upper = round(upper - price, 2)
|
|
dist_lower = round(price - lower, 2)
|
|
in_zone = lower <= price <= upper
|
|
out.append({
|
|
"id": r["id"],
|
|
"price": price,
|
|
"dist_upper": dist_upper,
|
|
"dist_lower": dist_lower,
|
|
"in_zone": in_zone,
|
|
})
|
|
return jsonify(out)
|
|
@app.route("/plans")
|
|
@login_required
|
|
@require_nav("plans")
|
|
def plans():
|
|
today = today_str()
|
|
start = request.args.get("start", "")
|
|
end = request.args.get("end", "")
|
|
|
|
conn = get_db()
|
|
plan_list = conn.execute(
|
|
"SELECT * FROM order_plans WHERE plan_date=? AND status IN ('planned', 'active') ORDER BY id DESC",
|
|
(today,),
|
|
).fetchall()
|
|
|
|
sql = "SELECT * FROM order_plans WHERE plan_date < ? OR status IN ('closed', 'expired')"
|
|
params: list = [today]
|
|
if start:
|
|
sql += " AND plan_date >= ?"
|
|
params.append(start)
|
|
if end:
|
|
sql += " AND plan_date <= ?"
|
|
params.append(end)
|
|
sql += " ORDER BY plan_date DESC, id DESC LIMIT 200"
|
|
history = conn.execute(sql, params).fetchall()
|
|
conn.close()
|
|
return render_template(
|
|
"plans.html",
|
|
plans=plan_list,
|
|
history=history,
|
|
today=today,
|
|
start=start,
|
|
end=end,
|
|
)
|
|
|
|
|
|
@app.route("/add_plan", methods=["POST"])
|
|
@login_required
|
|
def add_plan():
|
|
d = request.form
|
|
direction = d.get("direction")
|
|
symbol = d.get("symbol", "").strip()
|
|
symbol_name = d.get("symbol_name", "").strip()
|
|
market_code = d.get("market_code", "").strip()
|
|
sina_code = d.get("sina_code", "").strip()
|
|
if not direction:
|
|
flash("请选择多空方向")
|
|
return redirect(url_for("plans"))
|
|
if not symbol or not market_code:
|
|
flash("请从下拉列表选择品种(同花顺合约代码)")
|
|
return redirect(url_for("plans"))
|
|
conn = get_db()
|
|
conn.execute(
|
|
"""INSERT INTO order_plans
|
|
(symbol, symbol_name, market_code, sina_code, direction,
|
|
zone_upper, zone_lower, stop_loss, take_profit, plan_date, decision_reason)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
|
|
(
|
|
symbol, symbol_name, market_code, sina_code, direction,
|
|
float(d["zone_upper"]), float(d["zone_lower"]),
|
|
float(d["stop_loss"]), float(d["take_profit"]),
|
|
today_str(),
|
|
d.get("decision_reason", "").strip(),
|
|
),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
flash("开单计划已添加")
|
|
return redirect(url_for("plans"))
|
|
|
|
|
|
@app.route("/del_plan/<int:pid>")
|
|
@login_required
|
|
def del_plan(pid):
|
|
conn = get_db()
|
|
conn.execute("DELETE FROM order_plans WHERE id=?", (pid,))
|
|
conn.commit()
|
|
conn.close()
|
|
flash("已删除")
|
|
return redirect(url_for("plans"))
|