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,174 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
"""HTTP routes for stats 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
|
||||
|
||||
from modules.stats.stats_engine import (
|
||||
STATS_VIEWS,
|
||||
get_calendar_day,
|
||||
get_calendar_month,
|
||||
refresh_stats_cache,
|
||||
)
|
||||
from modules.settings.nav_settings import nav_enabled
|
||||
from modules.stats.dashboard_lib import build_dashboard_payload
|
||||
from modules.core.doc_render import read_doc, render_markdown
|
||||
|
||||
_dashboard_sync_tick = {"n": 0}
|
||||
|
||||
@app.route("/stats")
|
||||
@login_required
|
||||
def stats():
|
||||
return render_template("stats.html")
|
||||
|
||||
|
||||
@app.route("/calendar")
|
||||
@login_required
|
||||
def trade_calendar():
|
||||
return render_template("calendar.html")
|
||||
|
||||
|
||||
@app.route("/api/stats")
|
||||
@login_required
|
||||
def api_stats():
|
||||
return jsonify(get_stats_data())
|
||||
|
||||
|
||||
@app.route("/api/stats/views")
|
||||
@login_required
|
||||
def api_stats_views():
|
||||
return jsonify({"views": STATS_VIEWS})
|
||||
|
||||
|
||||
@app.route("/api/stats/refresh", methods=["POST"])
|
||||
@login_required
|
||||
def api_stats_refresh():
|
||||
conn = get_db()
|
||||
capital = float(get_setting("live_capital", "0") or 0)
|
||||
data = refresh_stats_cache(conn, capital)
|
||||
conn.close()
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route("/api/stats/calendar")
|
||||
@login_required
|
||||
def api_stats_calendar():
|
||||
now = datetime.now(TZ)
|
||||
year = request.args.get("year", type=int) or now.year
|
||||
month = request.args.get("month", type=int) or now.month
|
||||
if month < 1 or month > 12:
|
||||
return jsonify({"error": "invalid month"}), 400
|
||||
conn = get_db()
|
||||
try:
|
||||
data = get_calendar_month(conn, year, month)
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route("/api/stats/calendar/day")
|
||||
@login_required
|
||||
def api_stats_calendar_day():
|
||||
day = (request.args.get("date") or "").strip()
|
||||
if not day:
|
||||
return jsonify({"error": "date required"}), 400
|
||||
try:
|
||||
date.fromisoformat(day)
|
||||
except ValueError:
|
||||
return jsonify({"error": "invalid date"}), 400
|
||||
conn = get_db()
|
||||
try:
|
||||
data = get_calendar_day(conn, day)
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route("/dashboard")
|
||||
@login_required
|
||||
@require_nav("dashboard")
|
||||
def dashboard():
|
||||
return render_template("dashboard.html")
|
||||
|
||||
|
||||
@app.route("/risk-guide")
|
||||
@login_required
|
||||
@require_nav("risk_guide")
|
||||
def risk_guide():
|
||||
from modules.core.doc_render import read_doc, render_markdown
|
||||
|
||||
try:
|
||||
_title, raw = read_doc("risk-guide")
|
||||
except FileNotFoundError:
|
||||
flash("文档不存在")
|
||||
return redirect(url_for("positions"))
|
||||
return render_template("risk_guide.html", doc_html=render_markdown(raw))
|
||||
|
||||
|
||||
@app.route("/api/dashboard/live")
|
||||
@login_required
|
||||
def api_dashboard_live():
|
||||
if not nav_enabled(get_setting, "dashboard"):
|
||||
return jsonify({"ok": False, "error": "数据看板已在系统设置中关闭"}), 403
|
||||
from modules.stats.dashboard_lib import build_dashboard_payload
|
||||
|
||||
_dashboard_sync_tick["n"] += 1
|
||||
sync_trades = _dashboard_sync_tick["n"] % 15 == 0
|
||||
try:
|
||||
payload = build_dashboard_payload(
|
||||
get_db=get_db,
|
||||
get_setting=get_setting,
|
||||
fetch_price=fetch_price,
|
||||
sync_ctp_trades=sync_trades,
|
||||
)
|
||||
return jsonify(payload)
|
||||
except Exception as exc:
|
||||
app.logger.exception("dashboard live: %s", exc)
|
||||
return jsonify({"ok": False, "error": "看板数据暂时不可用"}), 503
|
||||
Reference in New Issue
Block a user