From 4eb5709d710f8e25ea483bd096a29ce6d286643d Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 26 Jun 2026 03:18:42 +0800 Subject: [PATCH] Rebrand product and enhance tradable symbols table with spec columns and K-line links. Co-authored-by: Cursor --- LICENSE.zh-CN.txt | 2 +- README.md | 2 +- deploy.sh | 2 +- docs/DEPLOY.md | 2 +- docs/FEATURES.md | 2 +- docs/软件购买与使用协议.md | 2 +- nav_settings.py | 1 + product_recommend.py | 5 +++++ recommend_store.py | 37 ++++++++++++++++++++++++++++---- static/css/responsive.css | 4 ++-- static/css/tech.css | 7 +++--- static/css/trade.css | 2 ++ static/js/trade.js | 37 +++++++++++++++++++++++++++++--- static/manifest.json | 6 +++--- templates/base.html | 6 +++--- templates/contract.html | 4 ++-- templates/fees.html | 4 ++-- templates/keys.html | 4 ++-- templates/login.html | 8 +++---- templates/market.html | 4 ++-- templates/plans.html | 4 ++-- templates/positions.html | 4 ++-- templates/recommend.html | 4 ++-- templates/records.html | 4 ++-- templates/settings.html | 4 ++-- templates/stats.html | 4 ++-- templates/strategy.html | 4 ++-- templates/strategy_records.html | 4 ++-- templates/trade.html | 24 ++++++++++++++++----- vnpy_bridge.py | 38 +++++++++++++++++++++++++++++++++ 30 files changed, 178 insertions(+), 57 deletions(-) diff --git a/LICENSE.zh-CN.txt b/LICENSE.zh-CN.txt index afafe44..8192528 100644 --- a/LICENSE.zh-CN.txt +++ b/LICENSE.zh-CN.txt @@ -1,4 +1,4 @@ -国内期货交易监控复盘系统 — 软件使用许可与版权声明 +国内期货 · 交易复盘系统 — 软件使用许可与版权声明 著作权人:马建军 Copyright (c) 2025-2026 马建军. All rights reserved. diff --git a/README.md b/README.md index f4855ad..bb126bf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 国内期货交易监控复盘系统 +# 国内期货 · 交易复盘系统 基于 Flask 的国内期货 **CTP 下单 + 监控 + 复盘 + 统计** Web 应用。模拟盘连接 SimNow,实盘连接期货公司 CTP;支持关键位/计划提醒、交易记录同步、资金曲线、可开仓品种(仓位纪律)与企业微信推送。 diff --git a/deploy.sh b/deploy.sh index 37b98e2..db6153b 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# 国内期货监控系统 - Ubuntu 一键部署 / 更新 +# 国内期货 · 交易复盘系统 - Ubuntu 一键部署 / 更新 # root 用户 | 目录 /opt/qihuo | 端口 6600 | PM2 # # 已内置修复(避免重复踩坑): diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 5db9d52..5864813 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -1,6 +1,6 @@ # 部署文档 -国内期货交易监控复盘系统 — Ubuntu 服务器部署、更新与运维说明。 +国内期货 · 交易复盘系统 — Ubuntu 服务器部署、更新与运维说明。 --- diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 431a856..dfddff3 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -1,6 +1,6 @@ # 功能说明文档 -国内期货交易监控复盘系统(Flask + SQLite + vnpy_ctp + PM2)。 +国内期货 · 交易复盘系统(Flask + SQLite + vnpy_ctp + PM2)。 --- diff --git a/docs/软件购买与使用协议.md b/docs/软件购买与使用协议.md index a2971df..62ba8d5 100644 --- a/docs/软件购买与使用协议.md +++ b/docs/软件购买与使用协议.md @@ -26,7 +26,7 @@ ## 第一条 软件与交付内容 -1.1 甲方向乙方提供的软件名称为 **「国内期货交易监控复盘系统」**(以下简称「本软件」),包括甲方交付时约定版本的源代码、部署说明及必要配置指导。 +1.1 甲方向乙方提供的软件名称为 **「国内期货 · 交易复盘系统」**(以下简称「本软件」),包括甲方交付时约定版本的源代码、部署说明及必要配置指导。 1.2 **交付方式**(勾选适用项): diff --git a/nav_settings.py b/nav_settings.py index 1a7efe2..1b98b9f 100644 --- a/nav_settings.py +++ b/nav_settings.py @@ -19,6 +19,7 @@ NAV_TOGGLES: dict[str, str] = { } DEFAULT_NAV: dict[str, bool] = {k: True for k in NAV_TOGGLES} +DEFAULT_NAV["contract"] = False def get_nav_items(get_setting: Callable[[str, str], str]) -> dict[str, bool]: diff --git a/product_recommend.py b/product_recommend.py index c7be387..398ccf5 100644 --- a/product_recommend.py +++ b/product_recommend.py @@ -66,6 +66,8 @@ def assess_product_for_capital( "name": name, "exchange": exchange, "category": category, + "mult": mult, + "tick_size": tick, "status": "no_price", "status_label": "暂无行情", "min_capital_one_lot": None, @@ -153,11 +155,14 @@ def list_product_recommendations( return row except Exception as exc: logger.warning("recommend product failed %s: %s", ths, exc) + spec = get_contract_spec(ths + "8888") return { "ths": ths, "name": product.get("name") or ths, "exchange": product.get("exchange") or "", "category": product.get("category") or product_category(ths), + "mult": spec["mult"], + "tick_size": float(spec.get("tick_size") or 1.0), "status": "no_price", "status_label": "计算失败", "main_code": "", diff --git a/recommend_store.py b/recommend_store.py index 3438e8b..14dde65 100644 --- a/recommend_store.py +++ b/recommend_store.py @@ -12,6 +12,7 @@ import math from datetime import datetime from typing import Callable, Optional +from contract_specs import get_contract_spec from fee_specs import ensure_fee_rates_schema from product_recommend import _attach_turnover, list_product_recommendations from recommend_trend import sort_recommend_by_trend @@ -71,6 +72,12 @@ def rows_missing_turnover(rows: list[dict]) -> bool: return any("turnover" not in r for r in rows) +def rows_missing_contract_spec(rows: list[dict]) -> bool: + if not rows: + return False + return any("mult" not in r or "tick_size" not in r for r in rows) + + def recommend_cache_needs_refresh( cached: dict, *, @@ -90,6 +97,8 @@ def recommend_cache_needs_refresh( return True if rows_missing_turnover(rows): return True + if rows_missing_contract_spec(rows): + return True if float(capital or 0) > 0 and not rows: return True return False @@ -107,23 +116,43 @@ def enrich_recommend_rows( pct = max(1.0, min(100.0, float(max_margin_pct or 30.0))) budget = cap * pct / 100.0 if cap > 0 else 0.0 ctp_connected = False + ctp_lookup_spec = None + ctp_estimate_margin_one_lot_fn = None try: - from vnpy_bridge import ctp_estimate_margin_one_lot, ctp_status + from vnpy_bridge import ctp_estimate_margin_one_lot, ctp_lookup_contract_spec, ctp_status ctp_connected = bool(ctp_status(trading_mode).get("connected")) + ctp_lookup_spec = ctp_lookup_contract_spec + ctp_estimate_margin_one_lot_fn = ctp_estimate_margin_one_lot except Exception: pass enriched: list[dict] = [] for raw in rows: row = dict(raw) + ths = (row.get("ths") or "").strip() + main_code = (row.get("main_code") or "").strip() + spec_code = main_code or (ths + "8888" if ths else "") + if spec_code: + spec = get_contract_spec(spec_code) + if row.get("mult") in (None, ""): + row["mult"] = spec["mult"] + if row.get("tick_size") in (None, ""): + row["tick_size"] = float(spec.get("tick_size") or 1.0) + if ctp_connected and main_code and ctp_lookup_spec: + ctp_spec = ctp_lookup_spec(trading_mode, main_code) + if ctp_spec: + if ctp_spec.get("mult"): + row["mult"] = ctp_spec["mult"] + if ctp_spec.get("tick_size"): + row["tick_size"] = ctp_spec["tick_size"] + row["spec_source"] = "ctp" margin_one = 0.0 try: margin_one = float(row.get("margin_one_lot") or 0) except (TypeError, ValueError): margin_one = 0.0 price = float(row.get("price") or 0) - main_code = (row.get("main_code") or "").strip() - if ctp_connected and main_code and price > 0: - ctp_margin = ctp_estimate_margin_one_lot(trading_mode, main_code, price) + if ctp_connected and main_code and price > 0 and ctp_estimate_margin_one_lot_fn: + ctp_margin = ctp_estimate_margin_one_lot_fn(trading_mode, main_code, price) if ctp_margin and ctp_margin > 0: margin_one = ctp_margin row["margin_one_lot"] = ctp_margin diff --git a/static/css/responsive.css b/static/css/responsive.css index 59475b3..b004e15 100644 --- a/static/css/responsive.css +++ b/static/css/responsive.css @@ -245,8 +245,8 @@ body { } .site-title-sub { - font-size: .58rem; - letter-spacing: .14em; + font-size: .68rem; + letter-spacing: .04em; } .site-nav { diff --git a/static/css/tech.css b/static/css/tech.css index f0333f1..dc3eb8c 100644 --- a/static/css/tech.css +++ b/static/css/tech.css @@ -73,10 +73,11 @@ filter:drop-shadow(0 0 24px var(--title-glow)); } .site-title-sub{ - display:block;font-size:.72rem;font-weight:500; - letter-spacing:.22em;text-transform:uppercase; - color:var(--text-muted);margin-top:.35rem; + display:block;font-size:.78rem;font-weight:400; + letter-spacing:.06em;text-transform:none; + color:var(--text-muted);margin-top:.4rem; -webkit-text-fill-color:var(--text-muted); + filter:none; } .site-nav a{ diff --git a/static/css/trade.css b/static/css/trade.css index da67f57..e1c1d07 100644 --- a/static/css/trade.css +++ b/static/css/trade.css @@ -63,6 +63,8 @@ } .rec-sort-dir-btn:hover{border-color:var(--accent);color:var(--accent)} .gap-badge{font-size:.72rem} +.rec-market-link{color:inherit;text-decoration:none;display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:.2rem .35rem} +.rec-market-link:hover strong,.rec-market-link:hover .text-accent{color:var(--accent);text-decoration:underline} .rec-change-up{color:var(--profit)} .rec-change-down{color:var(--loss)} #recommend .trade-table-wrap{max-height:min(70vh,520px)} diff --git a/static/js/trade.js b/static/js/trade.js index 25383f0..049e379 100644 --- a/static/js/trade.js +++ b/static/js/trade.js @@ -40,7 +40,8 @@ var recIndustryFilter = ''; var REC_SORT_CACHE = 'qihuo_rec_sort_v2'; var REC_INDUSTRY_CACHE = 'qihuo_rec_industry_v1'; - var REC_COLSPAN = 16; + var REC_COLSPAN = 18; + var marketNavEnabled = !!window.MARKET_NAV_ENABLED; var productCategories = window.PRODUCT_CATEGORIES || []; var POS_CACHE_KEY = 'qihuo_trading_live_v3'; @@ -1274,6 +1275,35 @@ return '' + label + ''; } + function fmtRecNum(v) { + if (v == null || v === '') return '—'; + var n = Number(v); + if (!isFinite(n)) return '—'; + return String(n); + } + + function recSpecSuffix(r) { + return r.spec_source === 'ctp' ? ' (柜台)' : ''; + } + + function recSymbolCellHtml(r) { + var code = r.main_code || r.ths || ''; + var nameCls = r.trend_transition ? ' class="trend-name"' : ''; + var name = r.name || ''; + if (marketNavEnabled && r.main_code) { + var href = '/market?symbol=' + encodeURIComponent(r.main_code); + return ( + '' + + '' + name + ' ' + + '' + r.main_code + '' + ); + } + return ( + '' + name + ' ' + + '' + code + '' + ); + } + function renderRecommendRows(rows) { if (!recommendList) return; if (!rows.length) { @@ -1286,10 +1316,9 @@ recommendList.innerHTML = rows.map(function (r) { var rowCls = 'rec-' + (r.status || ''); if (r.trend_transition) rowCls += ' rec-trend-break'; - var nameCls = r.trend_transition ? ' class="trend-name"' : ''; return ( '' + - '' + (r.name || '') + ' ' + (r.main_code || r.ths || '') + '' + + recSymbolCellHtml(r) + '' + (r.exchange || '') + '' + '' + (r.category || '—') + '' + '' + trendBadgeHtml(r) + '' + @@ -1301,6 +1330,8 @@ '' + (r.yesterday_amplitude_pct != null ? r.yesterday_amplitude_pct + '%' : '—') + '' + '' + fmtRecVolume(r.volume) + '' + '' + fmtRecTurnover(r.turnover) + '' + + '' + fmtRecNum(r.mult) + recSpecSuffix(r) + '' + + '' + fmtRecNum(r.tick_size) + recSpecSuffix(r) + '' + '' + (r.margin_one_lot != null ? r.margin_one_lot + (r.margin_source === 'ctp' ? ' (柜台)' : '') : '—') + '' + '' + (r.open_fee_one_lot != null ? r.open_fee_one_lot : '—') + '' + '' + (r.max_lots != null && r.max_lots > 0 ? r.max_lots : '—') + '' + diff --git a/static/manifest.json b/static/manifest.json index 7bef58d..805329c 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -1,8 +1,8 @@ { "id": "/", - "name": "国内期货交易监控复盘系统", - "short_name": "期货监控", - "description": "期货交易监控、持仓管理、复盘与统计分析", + "name": "国内期货 · 交易复盘系统", + "short_name": "交易复盘", + "description": "专注于仓位管理和纪律执行", "start_url": "/login", "scope": "/", "display": "standalone", diff --git a/templates/base.html b/templates/base.html index 876fa87..fdd9d60 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,4 +1,4 @@ -{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} +{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} @@ -13,7 +13,7 @@ - {% block title %}国内期货监控系统{% endblock %} + {% block title %}国内期货 · 交易复盘系统{% endblock %}