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:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
国内期货交易监控复盘系统 — 软件使用许可与版权声明
|
国内期货 · 交易复盘系统 — 软件使用许可与版权声明
|
||||||
|
|
||||||
著作权人:马建军
|
著作权人:马建军
|
||||||
Copyright (c) 2025-2026 马建军. All rights reserved.
|
Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# 国内期货交易监控复盘系统
|
# 国内期货 · 交易复盘系统
|
||||||
|
|
||||||
基于 Flask 的国内期货 **CTP 下单 + 监控 + 复盘 + 统计** Web 应用。模拟盘连接 SimNow,实盘连接期货公司 CTP;支持关键位/计划提醒、交易记录同步、资金曲线、可开仓品种(仓位纪律)与企业微信推送。
|
基于 Flask 的国内期货 **CTP 下单 + 监控 + 复盘 + 统计** Web 应用。模拟盘连接 SimNow,实盘连接期货公司 CTP;支持关键位/计划提醒、交易记录同步、资金曲线、可开仓品种(仓位纪律)与企业微信推送。
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -1,6 +1,6 @@
|
|||||||
# 部署文档
|
# 部署文档
|
||||||
|
|
||||||
国内期货交易监控复盘系统 — Ubuntu 服务器部署、更新与运维说明。
|
国内期货 · 交易复盘系统 — Ubuntu 服务器部署、更新与运维说明。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# 功能说明文档
|
# 功能说明文档
|
||||||
|
|
||||||
国内期货交易监控复盘系统(Flask + SQLite + vnpy_ctp + PM2)。
|
国内期货 · 交易复盘系统(Flask + SQLite + vnpy_ctp + PM2)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
## 第一条 软件与交付内容
|
## 第一条 软件与交付内容
|
||||||
|
|
||||||
1.1 甲方向乙方提供的软件名称为 **「国内期货交易监控复盘系统」**(以下简称「本软件」),包括甲方交付时约定版本的源代码、部署说明及必要配置指导。
|
1.1 甲方向乙方提供的软件名称为 **「国内期货 · 交易复盘系统」**(以下简称「本软件」),包括甲方交付时约定版本的源代码、部署说明及必要配置指导。
|
||||||
|
|
||||||
1.2 **交付方式**(勾选适用项):
|
1.2 **交付方式**(勾选适用项):
|
||||||
|
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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{
|
||||||
|
|||||||
@@ -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
@@ -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>' +
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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">
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
@@ -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 }};
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user