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,230 @@
|
||||
# Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
"""HTTP routes for market 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.core.symbols import (
|
||||
list_main_contracts_grouped,
|
||||
list_recommended_symbols_grouped,
|
||||
search_symbols,
|
||||
)
|
||||
from modules.market.kline_chart import MARKET_PERIODS, fetch_market_klines
|
||||
from modules.market.kline_stream import kline_hub, sse_format
|
||||
from modules.market.market import get_quote_source_label
|
||||
from queue import Empty
|
||||
|
||||
@app.route("/api/symbols/search")
|
||||
@login_required
|
||||
def api_symbol_search():
|
||||
q = request.args.get("q", "")
|
||||
conn = get_db()
|
||||
try:
|
||||
from modules.core.trading_context import get_account_capital, is_ctp_connected
|
||||
capital = get_account_capital(conn, get_setting)
|
||||
ctp_connected = is_ctp_connected(get_setting)
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify(search_symbols(q, capital=capital, ctp_connected=ctp_connected))
|
||||
|
||||
|
||||
@app.route("/api/symbols/mains")
|
||||
@login_required
|
||||
def api_symbols_mains():
|
||||
return jsonify(list_main_contracts_grouped())
|
||||
|
||||
|
||||
@app.route("/api/symbols/recommended")
|
||||
@login_required
|
||||
def api_symbols_recommended():
|
||||
"""品种下拉:仅展示当前资金下可开仓品种(与下方可开仓品种表一致)。"""
|
||||
from modules.trading.recommend_store import recommend_payload
|
||||
from modules.core.trading_context import (
|
||||
get_fixed_lots,
|
||||
get_max_margin_pct,
|
||||
get_recommend_capital,
|
||||
get_sizing_mode,
|
||||
get_trading_mode,
|
||||
)
|
||||
|
||||
conn = get_db()
|
||||
try:
|
||||
capital = get_recommend_capital(conn, get_setting)
|
||||
payload = recommend_payload(
|
||||
conn,
|
||||
live_capital=capital,
|
||||
max_margin_pct=get_max_margin_pct(get_setting),
|
||||
trading_mode=get_trading_mode(get_setting),
|
||||
sizing_mode=get_sizing_mode(get_setting),
|
||||
fixed_lots=get_fixed_lots(get_setting),
|
||||
)
|
||||
return jsonify(list_recommended_symbols_grouped(payload.get("rows") or []))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route("/market")
|
||||
@login_required
|
||||
@require_nav("market")
|
||||
def market_page():
|
||||
symbol = request.args.get("symbol", "").strip()
|
||||
period = request.args.get("period", "15m").strip()
|
||||
valid = {p["key"] for p in MARKET_PERIODS}
|
||||
if period not in valid:
|
||||
period = "15m"
|
||||
ctp_st = {}
|
||||
try:
|
||||
from modules.ctp.vnpy_bridge import ctp_status
|
||||
from modules.core.trading_context import get_trading_mode
|
||||
|
||||
ctp_st = ctp_status(get_trading_mode(get_setting))
|
||||
except Exception:
|
||||
pass
|
||||
return render_template(
|
||||
"market.html",
|
||||
symbol=symbol,
|
||||
period=period,
|
||||
market_periods=MARKET_PERIODS,
|
||||
quote_label=get_quote_source_label(ctp_connected=bool(ctp_st.get("connected"))),
|
||||
ctp_connected=bool(ctp_st.get("connected")),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/kline")
|
||||
@login_required
|
||||
def api_kline():
|
||||
symbol = request.args.get("symbol", "").strip()
|
||||
period = request.args.get("period", "15m").strip()
|
||||
if not symbol:
|
||||
return jsonify({"error": "请提供合约代码"}), 400
|
||||
try:
|
||||
from modules.core.trading_context import get_trading_mode
|
||||
|
||||
data = fetch_market_klines(
|
||||
symbol, period, DB_PATH, prefer_ctp=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
app.logger.warning("kline api failed: %s", exc)
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
if not data.get("chart_symbol"):
|
||||
return jsonify({"error": "无法识别合约代码"}), 400
|
||||
if not data.get("bars"):
|
||||
return jsonify({"error": "未获取到K线数据,请稍后重试或更换合约"}), 404
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route("/api/kline/stream")
|
||||
@login_required
|
||||
def api_kline_stream():
|
||||
from queue import Empty
|
||||
|
||||
symbol = request.args.get("symbol", "").strip()
|
||||
period = request.args.get("period", "15m").strip()
|
||||
market_code = request.args.get("market_code", "").strip()
|
||||
sina_code = request.args.get("sina_code", "").strip()
|
||||
if not symbol:
|
||||
return jsonify({"error": "请提供合约代码"}), 400
|
||||
|
||||
def generate():
|
||||
sub = kline_hub.subscribe(symbol, period, market_code, sina_code)
|
||||
try:
|
||||
kline_data = fetch_market_klines(
|
||||
symbol, period, DB_PATH, prefer_ctp=False,
|
||||
)
|
||||
if kline_data.get("bars"):
|
||||
yield sse_format("kline", kline_data)
|
||||
yield sse_format(
|
||||
"quote",
|
||||
build_market_quote_payload(
|
||||
symbol, market_code, sina_code, prefer_sina=True,
|
||||
),
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
msg = sub.queue.get(timeout=20)
|
||||
yield sse_format(msg["event"], msg["data"])
|
||||
except Empty:
|
||||
yield ": heartbeat\n\n"
|
||||
finally:
|
||||
kline_hub.unsubscribe(sub)
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/market_quote")
|
||||
@login_required
|
||||
def api_market_quote():
|
||||
symbol = request.args.get("symbol", "").strip()
|
||||
market_code = request.args.get("market_code", "").strip()
|
||||
sina_code = request.args.get("sina_code", "").strip()
|
||||
if not symbol and not market_code:
|
||||
return jsonify({"error": "请提供合约"}), 400
|
||||
return jsonify(build_market_quote_payload(
|
||||
symbol, market_code, sina_code, prefer_sina=True,
|
||||
))
|
||||
|
||||
|
||||
@app.route("/contract")
|
||||
@login_required
|
||||
def contract_profile_page():
|
||||
return redirect(url_for("positions"))
|
||||
|
||||
|
||||
@app.route("/api/contract_profile")
|
||||
@login_required
|
||||
def api_contract_profile():
|
||||
return jsonify({"error": "品种简介功能已移除"}), 404
|
||||
Reference in New Issue
Block a user