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,185 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
"""HTTP routes for keys 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/key_prices")
|
||||
@login_required
|
||||
def api_key_prices():
|
||||
"""关键位监控列表:批量现价与距上/下沿距离。"""
|
||||
conn = get_db()
|
||||
rows = conn.execute(
|
||||
"SELECT id, symbol, market_code, sina_code, upper, lower "
|
||||
"FROM key_monitors WHERE status='active' OR status IS NULL"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
out = []
|
||||
for r in rows:
|
||||
sym = r["symbol"]
|
||||
market = r["market_code"] or ""
|
||||
sina = r["sina_code"] or ""
|
||||
upper = float(r["upper"])
|
||||
lower = float(r["lower"])
|
||||
price = fetch_price(sym, market, sina)
|
||||
dist_upper = None
|
||||
dist_lower = None
|
||||
if price is not None:
|
||||
dist_upper = round(upper - price, 2)
|
||||
dist_lower = round(price - lower, 2)
|
||||
out.append({
|
||||
"id": r["id"],
|
||||
"price": price,
|
||||
"dist_upper": dist_upper,
|
||||
"dist_lower": dist_lower,
|
||||
})
|
||||
return jsonify(out)
|
||||
@app.route("/keys")
|
||||
@login_required
|
||||
def keys():
|
||||
from modules.keys.key_monitor_lib import key_monitor_periods
|
||||
|
||||
conn = get_db()
|
||||
key_list = conn.execute(
|
||||
"SELECT * FROM key_monitors WHERE status='active' OR status IS NULL ORDER BY id DESC"
|
||||
).fetchall()
|
||||
history = conn.execute(
|
||||
"SELECT * FROM key_monitors WHERE status='archived' ORDER BY archived_at DESC LIMIT 100"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return render_template(
|
||||
"keys.html",
|
||||
keys=key_list,
|
||||
history=history,
|
||||
key_periods=key_monitor_periods(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@app.route("/add_key", methods=["POST"])
|
||||
@login_required
|
||||
def add_key():
|
||||
d = request.form
|
||||
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()
|
||||
monitor_type = (d.get("type") or "").strip()
|
||||
if not symbol or not market_code:
|
||||
flash("请从下拉列表选择品种(同花顺合约代码)")
|
||||
return redirect(url_for("keys"))
|
||||
try:
|
||||
upper = float(d.get("upper") or 0)
|
||||
lower = float(d.get("lower") or 0)
|
||||
except (TypeError, ValueError):
|
||||
flash("上沿/下沿价格无效")
|
||||
return redirect(url_for("keys"))
|
||||
if upper <= lower:
|
||||
flash("上沿必须大于下沿")
|
||||
return redirect(url_for("keys"))
|
||||
|
||||
trade_mode = (d.get("trade_mode") or "顺势").strip()
|
||||
if trade_mode not in ("顺势", "反转"):
|
||||
trade_mode = "顺势"
|
||||
try:
|
||||
risk_reward = float(d.get("risk_reward") or 2)
|
||||
except (TypeError, ValueError):
|
||||
risk_reward = 2.0
|
||||
risk_reward = max(0.5, min(10.0, risk_reward))
|
||||
trailing_be = 1 if d.get("trailing_be") else 0
|
||||
if trailing_be and risk_reward < 3:
|
||||
risk_reward = 3.0
|
||||
|
||||
from modules.keys.key_monitor_lib import normalize_bar_period
|
||||
|
||||
bar_period = normalize_bar_period(d.get("bar_period") or "5m")
|
||||
direction = (d.get("direction") or "").strip().lower()
|
||||
if monitor_type == "箱体突破":
|
||||
if direction not in ("long", "short"):
|
||||
flash("箱体突破须选择上方向(做多/做空)")
|
||||
return redirect(url_for("keys"))
|
||||
else:
|
||||
direction = ""
|
||||
|
||||
conn = get_db()
|
||||
conn.execute(
|
||||
"""INSERT INTO key_monitors
|
||||
(symbol, symbol_name, market_code, sina_code, monitor_type, direction,
|
||||
upper, lower, trade_mode, risk_reward, trailing_be, bar_period)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(
|
||||
symbol, symbol_name, market_code, sina_code, monitor_type, direction,
|
||||
upper, lower, trade_mode, risk_reward, trailing_be, bar_period,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash("关键位监控已添加")
|
||||
return redirect(url_for("keys"))
|
||||
|
||||
|
||||
@app.route("/add_position", methods=["POST"])
|
||||
@login_required
|
||||
def add_position():
|
||||
flash("持仓由策略交易或 CTP 自动同步,无需手工录入")
|
||||
return redirect(url_for("positions"))
|
||||
@app.route("/del_key/<int:pid>")
|
||||
@login_required
|
||||
def del_key(pid):
|
||||
conn = get_db()
|
||||
conn.execute(
|
||||
"UPDATE key_monitors SET status='archived', archived_at=? WHERE id=?",
|
||||
(datetime.now(TZ).isoformat(), pid),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash("已移入监控历史")
|
||||
return redirect(url_for("keys"))
|
||||
|
||||
Reference in New Issue
Block a user