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:
dekun
2026-06-26 13:53:12 +08:00
parent 6905373401
commit 382a9a0e14
9 changed files with 324 additions and 75 deletions
+39 -34
View File
@@ -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")