Use Sina-only market K-lines and editable admin login synced to .env.
Market page uses Sina for quotes and bars with an auto-follow toggle and incremental chart updates while panning. Settings lets users change username and password, persisting to the database and .env. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -51,6 +51,7 @@ from kline_stream import kline_hub, sse_format
|
||||
from kline_chart import generate_review_kline_chart, fetch_market_klines, MARKET_PERIODS
|
||||
from market import get_price as market_get_price, set_ths_refresh_token, get_quote_source_label
|
||||
from db_conn import connect_db
|
||||
from admin_settings import save_admin_credentials
|
||||
from db_backup import (
|
||||
backup_dir,
|
||||
backup_in_progress,
|
||||
@@ -470,6 +471,8 @@ def build_market_quote_payload(
|
||||
symbol: str,
|
||||
market_code: str = "",
|
||||
sina_code: str = "",
|
||||
*,
|
||||
prefer_sina: bool = False,
|
||||
) -> dict:
|
||||
if not market_code or not sina_code:
|
||||
codes = ths_to_codes(symbol)
|
||||
@@ -479,20 +482,21 @@ def build_market_quote_payload(
|
||||
quote_source = "sina"
|
||||
price = None
|
||||
prev_close = None
|
||||
try:
|
||||
from vnpy_bridge import ctp_status, ctp_get_tick_detail
|
||||
from trading_context import get_trading_mode
|
||||
if not prefer_sina:
|
||||
try:
|
||||
from vnpy_bridge import ctp_status, ctp_get_tick_detail
|
||||
from trading_context import get_trading_mode
|
||||
|
||||
mode = get_trading_mode(get_setting)
|
||||
if ctp_status(mode).get("connected"):
|
||||
detail = ctp_get_tick_detail(mode, symbol)
|
||||
if detail.get("price"):
|
||||
price = detail["price"]
|
||||
quote_source = "ctp"
|
||||
if detail.get("pre_close") is not None:
|
||||
prev_close = detail["pre_close"]
|
||||
except Exception:
|
||||
pass
|
||||
mode = get_trading_mode(get_setting)
|
||||
if ctp_status(mode).get("connected"):
|
||||
detail = ctp_get_tick_detail(mode, symbol)
|
||||
if detail.get("price"):
|
||||
price = detail["price"]
|
||||
quote_source = "ctp"
|
||||
if detail.get("pre_close") is not None:
|
||||
prev_close = detail["pre_close"]
|
||||
except Exception:
|
||||
pass
|
||||
if price is None:
|
||||
price = fetch_price(symbol, market_code, sina_code)
|
||||
name = symbol
|
||||
@@ -715,7 +719,9 @@ def start_background_threads():
|
||||
threading.Thread(
|
||||
target=lambda: kline_hub.worker_loop(
|
||||
DB_PATH,
|
||||
build_market_quote_payload,
|
||||
lambda sym, mc, sc: build_market_quote_payload(
|
||||
sym, mc, sc, prefer_sina=True,
|
||||
),
|
||||
get_mode_fn=lambda: get_trading_mode(get_setting),
|
||||
),
|
||||
daemon=True,
|
||||
@@ -1553,7 +1559,7 @@ def api_kline():
|
||||
from trading_context import get_trading_mode
|
||||
|
||||
data = fetch_market_klines(
|
||||
symbol, period, DB_PATH, trading_mode=get_trading_mode(get_setting),
|
||||
symbol, period, DB_PATH, prefer_ctp=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
app.logger.warning("kline api failed: %s", exc)
|
||||
@@ -1578,19 +1584,18 @@ def api_kline_stream():
|
||||
return jsonify({"error": "请提供合约代码"}), 400
|
||||
|
||||
def generate():
|
||||
from trading_context import get_trading_mode
|
||||
|
||||
mode = get_trading_mode(get_setting)
|
||||
sub = kline_hub.subscribe(symbol, period, market_code, sina_code)
|
||||
try:
|
||||
kline_data = fetch_market_klines(
|
||||
symbol, period, DB_PATH, trading_mode=mode,
|
||||
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),
|
||||
build_market_quote_payload(
|
||||
symbol, market_code, sina_code, prefer_sina=True,
|
||||
),
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
@@ -1620,7 +1625,9 @@ def api_market_quote():
|
||||
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))
|
||||
return jsonify(build_market_quote_payload(
|
||||
symbol, market_code, sina_code, prefer_sina=True,
|
||||
))
|
||||
|
||||
|
||||
@app.route("/contract")
|
||||
@@ -1834,19 +1841,17 @@ def settings():
|
||||
save_nav_items(set_setting, items)
|
||||
flash("导航显示已保存")
|
||||
elif action == "password":
|
||||
old_p = request.form.get("old_password", "")
|
||||
new_p = request.form.get("new_password", "")
|
||||
new_p2 = request.form.get("new_password2", "")
|
||||
admin_hash = get_setting("admin_password_hash")
|
||||
if not check_password_hash(admin_hash, old_p):
|
||||
flash("原密码错误")
|
||||
elif len(new_p) < 6:
|
||||
flash("新密码至少 6 位")
|
||||
elif new_p != new_p2:
|
||||
flash("两次新密码不一致")
|
||||
else:
|
||||
set_setting("admin_password_hash", generate_password_hash(new_p))
|
||||
flash("密码修改成功")
|
||||
ok, msg, _ = save_admin_credentials(
|
||||
username=request.form.get("admin_username", ""),
|
||||
old_password=request.form.get("old_password", ""),
|
||||
new_password=request.form.get("new_password", ""),
|
||||
new_password2=request.form.get("new_password2", ""),
|
||||
get_setting=get_setting,
|
||||
set_setting=set_setting,
|
||||
)
|
||||
if ok and session.get("logged_in"):
|
||||
session["username"] = (request.form.get("admin_username") or "").strip()
|
||||
flash(msg)
|
||||
return redirect(url_for("settings"))
|
||||
|
||||
webhook = get_setting("wechat_webhook")
|
||||
|
||||
Reference in New Issue
Block a user