Rebrand product and enhance tradable symbols table with spec columns and K-line links.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 03:18:42 +08:00
parent ab9987e4c7
commit 4eb5709d71
30 changed files with 178 additions and 57 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
国内期货交易监控复盘系统 — 软件使用许可与版权声明 国内期货 · 交易复盘系统 — 软件使用许可与版权声明
著作权人:马建军 著作权人:马建军
Copyright (c) 2025-2026 马建军. All rights reserved. Copyright (c) 2025-2026 马建军. All rights reserved.
+1 -1
View File
@@ -1,4 +1,4 @@
# 国内期货交易监控复盘系统 # 国内期货 · 交易复盘系统
基于 Flask 的国内期货 **CTP 下单 + 监控 + 复盘 + 统计** Web 应用。模拟盘连接 SimNow,实盘连接期货公司 CTP;支持关键位/计划提醒、交易记录同步、资金曲线、可开仓品种(仓位纪律)与企业微信推送。 基于 Flask 的国内期货 **CTP 下单 + 监控 + 复盘 + 统计** Web 应用。模拟盘连接 SimNow,实盘连接期货公司 CTP;支持关键位/计划提醒、交易记录同步、资金曲线、可开仓品种(仓位纪律)与企业微信推送。
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# 国内期货监控系统 - Ubuntu 一键部署 / 更新 # 国内期货 · 交易复盘系统 - Ubuntu 一键部署 / 更新
# root 用户 | 目录 /opt/qihuo | 端口 6600 | PM2 # root 用户 | 目录 /opt/qihuo | 端口 6600 | PM2
# #
# 已内置修复(避免重复踩坑): # 已内置修复(避免重复踩坑):
+1 -1
View File
@@ -1,6 +1,6 @@
# 部署文档 # 部署文档
国内期货交易监控复盘系统 — Ubuntu 服务器部署、更新与运维说明。 国内期货 · 交易复盘系统 — Ubuntu 服务器部署、更新与运维说明。
--- ---
+1 -1
View File
@@ -1,6 +1,6 @@
# 功能说明文档 # 功能说明文档
国内期货交易监控复盘系统(Flask + SQLite + vnpy_ctp + PM2)。 国内期货 · 交易复盘系统(Flask + SQLite + vnpy_ctp + PM2)。
--- ---
+1 -1
View File
@@ -26,7 +26,7 @@
## 第一条 软件与交付内容 ## 第一条 软件与交付内容
1.1 甲方向乙方提供的软件名称为 **「国内期货交易监控复盘系统」**(以下简称「本软件」),包括甲方交付时约定版本的源代码、部署说明及必要配置指导。 1.1 甲方向乙方提供的软件名称为 **「国内期货 · 交易复盘系统」**(以下简称「本软件」),包括甲方交付时约定版本的源代码、部署说明及必要配置指导。
1.2 **交付方式**(勾选适用项): 1.2 **交付方式**(勾选适用项):
+1
View File
@@ -19,6 +19,7 @@ NAV_TOGGLES: dict[str, str] = {
} }
DEFAULT_NAV: dict[str, bool] = {k: True for k in NAV_TOGGLES} 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]: def get_nav_items(get_setting: Callable[[str, str], str]) -> dict[str, bool]:
+5
View File
@@ -66,6 +66,8 @@ def assess_product_for_capital(
"name": name, "name": name,
"exchange": exchange, "exchange": exchange,
"category": category, "category": category,
"mult": mult,
"tick_size": tick,
"status": "no_price", "status": "no_price",
"status_label": "暂无行情", "status_label": "暂无行情",
"min_capital_one_lot": None, "min_capital_one_lot": None,
@@ -153,11 +155,14 @@ def list_product_recommendations(
return row return row
except Exception as exc: except Exception as exc:
logger.warning("recommend product failed %s: %s", ths, exc) logger.warning("recommend product failed %s: %s", ths, exc)
spec = get_contract_spec(ths + "8888")
return { return {
"ths": ths, "ths": ths,
"name": product.get("name") or ths, "name": product.get("name") or ths,
"exchange": product.get("exchange") or "", "exchange": product.get("exchange") or "",
"category": product.get("category") or product_category(ths), "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": "no_price",
"status_label": "计算失败", "status_label": "计算失败",
"main_code": "", "main_code": "",
+33 -4
View File
@@ -12,6 +12,7 @@ import math
from datetime import datetime from datetime import datetime
from typing import Callable, Optional from typing import Callable, Optional
from contract_specs import get_contract_spec
from fee_specs import ensure_fee_rates_schema from fee_specs import ensure_fee_rates_schema
from product_recommend import _attach_turnover, list_product_recommendations from product_recommend import _attach_turnover, list_product_recommendations
from recommend_trend import sort_recommend_by_trend 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) 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( def recommend_cache_needs_refresh(
cached: dict, cached: dict,
*, *,
@@ -90,6 +97,8 @@ def recommend_cache_needs_refresh(
return True return True
if rows_missing_turnover(rows): if rows_missing_turnover(rows):
return True return True
if rows_missing_contract_spec(rows):
return True
if float(capital or 0) > 0 and not rows: if float(capital or 0) > 0 and not rows:
return True return True
return False return False
@@ -107,23 +116,43 @@ def enrich_recommend_rows(
pct = max(1.0, min(100.0, float(max_margin_pct or 30.0))) 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 budget = cap * pct / 100.0 if cap > 0 else 0.0
ctp_connected = False ctp_connected = False
ctp_lookup_spec = None
ctp_estimate_margin_one_lot_fn = None
try: 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_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: except Exception:
pass pass
enriched: list[dict] = [] enriched: list[dict] = []
for raw in rows: for raw in rows:
row = dict(raw) 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 margin_one = 0.0
try: try:
margin_one = float(row.get("margin_one_lot") or 0) margin_one = float(row.get("margin_one_lot") or 0)
except (TypeError, ValueError): except (TypeError, ValueError):
margin_one = 0.0 margin_one = 0.0
price = float(row.get("price") or 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 and ctp_estimate_margin_one_lot_fn:
if ctp_connected and main_code and price > 0: ctp_margin = ctp_estimate_margin_one_lot_fn(trading_mode, main_code, price)
ctp_margin = ctp_estimate_margin_one_lot(trading_mode, main_code, price)
if ctp_margin and ctp_margin > 0: if ctp_margin and ctp_margin > 0:
margin_one = ctp_margin margin_one = ctp_margin
row["margin_one_lot"] = ctp_margin row["margin_one_lot"] = ctp_margin
+2 -2
View File
@@ -245,8 +245,8 @@ body {
} }
.site-title-sub { .site-title-sub {
font-size: .58rem; font-size: .68rem;
letter-spacing: .14em; letter-spacing: .04em;
} }
.site-nav { .site-nav {
+4 -3
View File
@@ -73,10 +73,11 @@
filter:drop-shadow(0 0 24px var(--title-glow)); filter:drop-shadow(0 0 24px var(--title-glow));
} }
.site-title-sub{ .site-title-sub{
display:block;font-size:.72rem;font-weight:500; display:block;font-size:.78rem;font-weight:400;
letter-spacing:.22em;text-transform:uppercase; letter-spacing:.06em;text-transform:none;
color:var(--text-muted);margin-top:.35rem; color:var(--text-muted);margin-top:.4rem;
-webkit-text-fill-color:var(--text-muted); -webkit-text-fill-color:var(--text-muted);
filter:none;
} }
.site-nav a{ .site-nav a{
+2
View File
@@ -63,6 +63,8 @@
} }
.rec-sort-dir-btn:hover{border-color:var(--accent);color:var(--accent)} .rec-sort-dir-btn:hover{border-color:var(--accent);color:var(--accent)}
.gap-badge{font-size:.72rem} .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-up{color:var(--profit)}
.rec-change-down{color:var(--loss)} .rec-change-down{color:var(--loss)}
#recommend .trade-table-wrap{max-height:min(70vh,520px)} #recommend .trade-table-wrap{max-height:min(70vh,520px)}
+34 -3
View File
@@ -40,7 +40,8 @@
var recIndustryFilter = ''; var recIndustryFilter = '';
var REC_SORT_CACHE = 'qihuo_rec_sort_v2'; var REC_SORT_CACHE = 'qihuo_rec_sort_v2';
var REC_INDUSTRY_CACHE = 'qihuo_rec_industry_v1'; 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 productCategories = window.PRODUCT_CATEGORIES || [];
var POS_CACHE_KEY = 'qihuo_trading_live_v3'; var POS_CACHE_KEY = 'qihuo_trading_live_v3';
@@ -1274,6 +1275,35 @@
return '<span class="badge gap-badge ' + cls + '"' + title + '>' + label + '</span>'; return '<span class="badge gap-badge ' + cls + '"' + title + '>' + label + '</span>';
} }
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' ? ' <span class="text-muted">(柜台)</span>' : '';
}
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 (
'<td><a href="' + href + '" class="rec-market-link" title="查看 K 线">' +
'<strong' + nameCls + '>' + name + '</strong> ' +
'<span class="text-accent">' + r.main_code + '</span></a></td>'
);
}
return (
'<td><strong' + nameCls + '>' + name + '</strong> ' +
'<span class="text-accent">' + code + '</span></td>'
);
}
function renderRecommendRows(rows) { function renderRecommendRows(rows) {
if (!recommendList) return; if (!recommendList) return;
if (!rows.length) { if (!rows.length) {
@@ -1286,10 +1316,9 @@
recommendList.innerHTML = rows.map(function (r) { recommendList.innerHTML = rows.map(function (r) {
var rowCls = 'rec-' + (r.status || ''); var rowCls = 'rec-' + (r.status || '');
if (r.trend_transition) rowCls += ' rec-trend-break'; if (r.trend_transition) rowCls += ' rec-trend-break';
var nameCls = r.trend_transition ? ' class="trend-name"' : '';
return ( return (
'<tr class="' + rowCls + '">' + '<tr class="' + rowCls + '">' +
'<td><strong' + nameCls + '>' + (r.name || '') + '</strong> <span class="text-accent">' + (r.main_code || r.ths || '') + '</span></td>' + recSymbolCellHtml(r) +
'<td>' + (r.exchange || '') + '</td>' + '<td>' + (r.exchange || '') + '</td>' +
'<td>' + (r.category || '—') + '</td>' + '<td>' + (r.category || '—') + '</td>' +
'<td>' + trendBadgeHtml(r) + '</td>' + '<td>' + trendBadgeHtml(r) + '</td>' +
@@ -1301,6 +1330,8 @@
'<td>' + (r.yesterday_amplitude_pct != null ? r.yesterday_amplitude_pct + '%' : '—') + '</td>' + '<td>' + (r.yesterday_amplitude_pct != null ? r.yesterday_amplitude_pct + '%' : '—') + '</td>' +
'<td>' + fmtRecVolume(r.volume) + '</td>' + '<td>' + fmtRecVolume(r.volume) + '</td>' +
'<td>' + fmtRecTurnover(r.turnover) + '</td>' + '<td>' + fmtRecTurnover(r.turnover) + '</td>' +
'<td>' + fmtRecNum(r.mult) + recSpecSuffix(r) + '</td>' +
'<td>' + fmtRecNum(r.tick_size) + recSpecSuffix(r) + '</td>' +
'<td>' + (r.margin_one_lot != null ? r.margin_one_lot + (r.margin_source === 'ctp' ? ' <span class="text-muted">(柜台)</span>' : '') : '—') + '</td>' + '<td>' + (r.margin_one_lot != null ? r.margin_one_lot + (r.margin_source === 'ctp' ? ' <span class="text-muted">(柜台)</span>' : '') : '—') + '</td>' +
'<td>' + (r.open_fee_one_lot != null ? r.open_fee_one_lot : '—') + '</td>' + '<td>' + (r.open_fee_one_lot != null ? r.open_fee_one_lot : '—') + '</td>' +
'<td>' + (r.max_lots != null && r.max_lots > 0 ? r.max_lots : '—') + '</td>' + '<td>' + (r.max_lots != null && r.max_lots > 0 ? r.max_lots : '—') + '</td>' +
+3 -3
View File
@@ -1,8 +1,8 @@
{ {
"id": "/", "id": "/",
"name": "国内期货交易监控复盘系统", "name": "国内期货 · 交易复盘系统",
"short_name": "期货监控", "short_name": "交易复盘",
"description": "期货交易监控、持仓管理、复盘与统计分析", "description": "专注于仓位管理和纪律执行",
"start_url": "/login", "start_url": "/login",
"scope": "/", "scope": "/",
"display": "standalone", "display": "standalone",
+3 -3
View File
@@ -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 #}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN" data-theme="dark"> <html lang="zh-CN" data-theme="dark">
<head> <head>
@@ -13,7 +13,7 @@
<link rel="manifest" href="{{ url_for('web_manifest') }}"> <link rel="manifest" href="{{ url_for('web_manifest') }}">
<link rel="icon" href="{{ url_for('static', filename='icons/icon.svg') }}" type="image/svg+xml"> <link rel="icon" href="{{ url_for('static', filename='icons/icon.svg') }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/icon-192.png') }}"> <link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/icon-192.png') }}">
<title>{% block title %}国内期货监控系统{% endblock %}</title> <title>{% block title %}国内期货 · 交易复盘系统{% endblock %}</title>
<script> <script>
try { try {
var _t = localStorage.getItem('qihuo-theme'); var _t = localStorage.getItem('qihuo-theme');
@@ -516,7 +516,7 @@
<div class="user-bar">{{ session.username or '用户' }}<a href="{{ url_for('logout') }}">退出</a></div> <div class="user-bar">{{ session.username or '用户' }}<a href="{{ url_for('logout') }}">退出</a></div>
</div> </div>
<p class="pwa-ios-hint" id="pwa-ios-hint">iOS 安装:Safari 浏览器点击底部分享按钮,选择「添加到主屏幕」。</p> <p class="pwa-ios-hint" id="pwa-ios-hint">iOS 安装:Safari 浏览器点击底部分享按钮,选择「添加到主屏幕」。</p>
<h1 class="site-title">国内期货 · 交易监控 + 复盘<span class="site-title-sub">FUTURES MONITOR SYSTEM</span></h1> <h1 class="site-title">国内期货 · 交易复盘系统<span class="site-title-sub">专注于仓位管理和纪律执行</span></h1>
<button type="button" class="nav-backdrop" id="nav-backdrop" aria-label="关闭菜单" hidden></button> <button type="button" class="nav-backdrop" id="nav-backdrop" aria-label="关闭菜单" hidden></button>
<nav class="site-nav" id="site-nav"> <nav class="site-nav" id="site-nav">
<a href="{{ url_for('positions') }}" class="{% if request.endpoint in ('positions', 'trade_page', 'recommend_page') %}active{% endif %}">下单监控</a> <a href="{{ url_for('positions') }}" class="{% if request.endpoint in ('positions', 'trade_page', 'recommend_page') %}active{% endif %}">下单监控</a>
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}品种简介 - 国内期货监控系统{% endblock %} {% block title %}品种简介 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="card profile-page"> <div class="card profile-page">
<h2>品种简介</h2> <h2>品种简介</h2>
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}手续费配置 - 国内期货监控系统{% endblock %} {% block title %}手续费配置 - 国内期货 · 交易复盘系统{% endblock %}
{% block extra_css %} {% block extra_css %}
<style> <style>
.fees-status-card .card-body{display:flex;flex-wrap:wrap;gap:.75rem 1.25rem;align-items:center} .fees-status-card .card-body{display:flex;flex-wrap:wrap;gap:.75rem 1.25rem;align-items:center}
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}关键位监控 - 国内期货监控系统{% endblock %} {% block title %}关键位监控 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="split-grid"> <div class="split-grid">
<div class="card"> <div class="card">
+4 -4
View File
@@ -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 #}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN" data-theme="dark"> <html lang="zh-CN" data-theme="dark">
<head> <head>
@@ -127,7 +127,7 @@
filter:drop-shadow(0 0 20px var(--focus-glow)); filter:drop-shadow(0 0 20px var(--focus-glow));
} }
.login-sub{ .login-sub{
text-align:center;font-size:.68rem;letter-spacing:.2em; text-align:center;font-size:.78rem;letter-spacing:.04em;
color:var(--text-muted);margin-bottom:2rem; color:var(--text-muted);margin-bottom:2rem;
} }
.form-group{margin-bottom:1.25rem} .form-group{margin-bottom:1.25rem}
@@ -183,8 +183,8 @@
</div> </div>
</div> </div>
<div class="login-box"> <div class="login-box">
<h2>期货监控系统</h2> <h2>国内期货 · 交易复盘系统</h2>
<p class="login-sub">FUTURES MONITOR</p> <p class="login-sub">专注于仓位管理和纪律执行</p>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %}<div class="flash">{{ messages[0] }}</div>{% endif %} {% if messages %}<div class="flash">{{ messages[0] }}</div>{% endif %}
{% endwith %} {% endwith %}
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}行情K线 - 国内期货监控系统{% endblock %} {% block title %}行情K线 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="card market-card"> <div class="card market-card">
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}开单计划 - 国内期货监控系统{% endblock %} {% block title %}开单计划 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="split-grid"> <div class="split-grid">
<div class="card"> <div class="card">
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}持仓监控 - 国内期货监控系统{% endblock %} {% block title %}持仓监控 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="split-grid"> <div class="split-grid">
<div class="card"> <div class="card">
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}可开仓品种 - 国内期货监控系统{% endblock %} {% block title %}可开仓品种 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="card"> <div class="card">
<h2>可开仓品种 · 按资金筛选</h2> <h2>可开仓品种 · 按资金筛选</h2>
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %} {% block title %}交易记录与复盘 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="card records-equity-card" style="margin-bottom:1.25rem"> <div class="card records-equity-card" style="margin-bottom:1.25rem">
<h2>资金曲线</h2> <h2>资金曲线</h2>
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}系统设置 - 国内期货监控系统{% endblock %} {% block title %}系统设置 - 国内期货 · 交易复盘系统{% endblock %}
{% block extra_css %} {% block extra_css %}
<style> <style>
.settings-page{display:flex;flex-direction:column;gap:1.25rem} .settings-page{display:flex;flex-direction:column;gap:1.25rem}
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}统计分析 - 国内期货监控系统{% endblock %} {% block title %}统计分析 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="card stats-summary-card"> <div class="card stats-summary-card">
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}策略交易 - 国内期货监控系统{% endblock %} {% block title %}策略交易 - 国内期货 · 交易复盘系统{% endblock %}
{% block extra_css %} {% block extra_css %}
<style> <style>
.strategy-page .split-grid .card{min-height:420px;display:flex;flex-direction:column} .strategy-page .split-grid .card{min-height:420px;display:flex;flex-direction:column}
+2 -2
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}策略记录 - 国内期货监控系统{% endblock %} {% block title %}策略记录 - 国内期货 · 交易复盘系统{% endblock %}
{% block content %} {% block content %}
<div class="split-grid"> <div class="split-grid">
<div class="card card-scroll"> <div class="card card-scroll">
+18 -4
View File
@@ -1,6 +1,6 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #} {# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}下单监控 - 国内期货监控系统{% endblock %} {% block title %}下单监控 - 国内期货 · 交易复盘系统{% endblock %}
{% block extra_css %} {% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/trade.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/trade.css') }}">
{% endblock %} {% endblock %}
@@ -148,6 +148,7 @@
<th>品种</th><th>交易所</th><th>行业</th><th>走势</th><th>是否跳空</th> <th>品种</th><th>交易所</th><th>行业</th><th>走势</th><th>是否跳空</th>
<th>参考价</th><th>昨日收盘</th><th>今日开盘</th> <th>参考价</th><th>昨日收盘</th><th>今日开盘</th>
<th>昨日涨跌</th><th>昨日振幅</th> <th>成交量(手)</th><th>成交额</th> <th>昨日涨跌</th><th>昨日振幅</th> <th>成交量(手)</th><th>成交额</th>
<th>乘数</th><th>最小变动</th>
<th>1手保证金</th><th>1手手续费</th><th>最大手数</th><th>状态</th> <th>1手保证金</th><th>1手手续费</th><th>最大手数</th><th>状态</th>
</tr> </tr>
</thead> </thead>
@@ -155,7 +156,17 @@
{% if recommend_rows %} {% if recommend_rows %}
{% for r in recommend_rows %} {% for r in recommend_rows %}
<tr class="rec-{{ r.status }}{% if r.trend_transition %} rec-trend-break{% endif %}"> <tr class="rec-{{ r.status }}{% if r.trend_transition %} rec-trend-break{% endif %}">
<td><strong class="{% if r.trend_transition %}trend-name{% endif %}">{{ r.name }}</strong> <span class="text-accent">{{ r.main_code or r.ths }}</span></td> <td>
{% if r.main_code and nav_items.market %}
<a href="{{ url_for('market_page', symbol=r.main_code) }}" class="rec-market-link" title="查看 K 线">
<strong class="{% if r.trend_transition %}trend-name{% endif %}">{{ r.name }}</strong>
<span class="text-accent">{{ r.main_code }}</span>
</a>
{% else %}
<strong class="{% if r.trend_transition %}trend-name{% endif %}">{{ r.name }}</strong>
<span class="text-accent">{{ r.main_code or r.ths }}</span>
{% endif %}
</td>
<td>{{ r.exchange }}</td> <td>{{ r.exchange }}</td>
<td>{{ r.category or '—' }}</td> <td>{{ r.category or '—' }}</td>
<td> <td>
@@ -183,6 +194,8 @@
<td>{% if r.yesterday_amplitude_pct is not none %}{{ '%.2f'|format(r.yesterday_amplitude_pct) }}%{% else %}—{% endif %}</td> <td>{% if r.yesterday_amplitude_pct is not none %}{{ '%.2f'|format(r.yesterday_amplitude_pct) }}%{% else %}—{% endif %}</td>
<td>{% if r.volume is not none %}{{ r.volume }}{% else %}—{% endif %}</td> <td>{% if r.volume is not none %}{{ r.volume }}{% else %}—{% endif %}</td>
<td>{% if r.turnover is not none %}{{ '%.0f'|format(r.turnover) }}{% else %}—{% endif %}</td> <td>{% if r.turnover is not none %}{{ '%.0f'|format(r.turnover) }}{% else %}—{% endif %}</td>
<td>{% if r.mult is not none %}{{ '%g'|format(r.mult) }}{% if r.spec_source == 'ctp' %} <span class="text-muted">(柜台)</span>{% endif %}{% else %}—{% endif %}</td>
<td>{% if r.tick_size is not none %}{{ '%g'|format(r.tick_size) }}{% if r.spec_source == 'ctp' %} <span class="text-muted">(柜台)</span>{% endif %}{% else %}—{% endif %}</td>
<td>{% if r.margin_one_lot %}{{ r.margin_one_lot }}{% if r.margin_source == 'ctp' %} <span class="text-muted">(柜台)</span>{% endif %}{% else %}—{% endif %}</td> <td>{% if r.margin_one_lot %}{{ r.margin_one_lot }}{% if r.margin_source == 'ctp' %} <span class="text-muted">(柜台)</span>{% endif %}{% else %}—{% endif %}</td>
<td>{% if r.open_fee_one_lot is defined and r.open_fee_one_lot is not none %}{{ r.open_fee_one_lot }}{% else %}—{% endif %}</td> <td>{% if r.open_fee_one_lot is defined and r.open_fee_one_lot is not none %}{{ r.open_fee_one_lot }}{% else %}—{% endif %}</td>
<td>{% if r.max_lots is not none and r.max_lots > 0 %}{{ r.max_lots }}{% else %}—{% endif %}</td> <td>{% if r.max_lots is not none and r.max_lots > 0 %}{{ r.max_lots }}{% else %}—{% endif %}</td>
@@ -190,7 +203,7 @@
</tr> </tr>
{% endfor %} {% endfor %}
{% else %} {% else %}
<tr><td colspan="16" class="empty-hint">等待今日后台刷新推荐…</td></tr> <tr><td colspan="18" class="empty-hint">等待今日后台刷新推荐…</td></tr>
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
@@ -202,6 +215,7 @@
{% block extra_js %} {% block extra_js %}
<script> <script>
window.TRADE_SIZING_MODE = {{ sizing_mode|tojson }}; window.TRADE_SIZING_MODE = {{ sizing_mode|tojson }};
window.MARKET_NAV_ENABLED = {{ nav_items.market|tojson }};
window.TRADE_FIXED_LOTS = {{ fixed_lots|tojson }}; window.TRADE_FIXED_LOTS = {{ fixed_lots|tojson }};
window.TRADE_FIXED_AMOUNT = {{ fixed_amount|tojson }}; window.TRADE_FIXED_AMOUNT = {{ fixed_amount|tojson }};
window.PRODUCT_CATEGORIES = {{ product_categories | default([]) | tojson }}; window.PRODUCT_CATEGORIES = {{ product_categories | default([]) | tojson }};
+38
View File
@@ -963,6 +963,33 @@ class CtpBridge:
logger.debug("estimate_margin_one_lot %s: %s", ths_code, exc) logger.debug("estimate_margin_one_lot %s: %s", ths_code, exc)
return None return None
def lookup_contract_spec(self, ths_code: str) -> Optional[dict]:
"""从 CTP 合约信息读取乘数与最小变动价位。"""
if not self._engine:
return None
try:
sym, ex_name = ths_to_vnpy_symbol(ths_code)
exchange = to_vnpy_exchange(ex_name)
vt_symbol = f"{sym}.{exchange.value}"
contract = self._engine.get_contract(vt_symbol)
if not contract:
return None
mult = float(getattr(contract, "size", 0) or 0)
tick = float(
getattr(contract, "pricetick", 0)
or getattr(contract, "price_tick", 0)
or 0
)
if mult <= 0:
return None
out: dict[str, Any] = {"mult": mult}
if tick > 0:
out["tick_size"] = tick
return out
except Exception as exc:
logger.debug("lookup_contract_spec %s: %s", ths_code, exc)
return None
def _collect_positions(self) -> list[dict[str, Any]]: def _collect_positions(self) -> list[dict[str, Any]]:
if not self._engine: if not self._engine:
return [] return []
@@ -1420,6 +1447,17 @@ def ctp_estimate_margin_one_lot(mode: str, ths_code: str, price: float) -> Optio
return None return None
def ctp_lookup_contract_spec(mode: str, ths_code: str) -> Optional[dict]:
b = get_bridge()
if b.connected_mode != mode or not b.ping():
return None
try:
return b.lookup_contract_spec(ths_code)
except Exception as exc:
logger.debug("ctp_lookup_contract_spec: %s", exc)
return None
def get_ctp_balance(mode: str) -> Optional[float]: def get_ctp_balance(mode: str) -> Optional[float]:
try: try:
acc = ctp_get_account(mode) acc = ctp_get_account(mode)