diff --git a/analyze_spread.py b/analyze_spread.py
deleted file mode 100644
index 857782c..0000000
--- a/analyze_spread.py
+++ /dev/null
@@ -1,226 +0,0 @@
-"""
-跨期价差均值回归回测:支持任意合约月份组合。
-"""
-import sqlite3
-from collections import defaultdict
-from statistics import mean, stdev
-
-DB_PATH = "/Users/vipg/Documents/futures-data-warehouse/db/futures.db"
-
-conn = sqlite3.connect(DB_PATH)
-daily = conn.execute(
- "SELECT ts_code, trade_date, close FROM daily WHERE close IS NOT NULL AND close != '' ORDER BY trade_date"
-).fetchall()
-conn.close()
-
-by_date = defaultdict(dict)
-for ts_code, d, close in daily:
- by_date[d][ts_code] = float(close)
-
-dates = sorted(by_date.keys())
-
-
-def build_spread(month_a, month_b, cross_year=False):
- """
- 构建两合约月份的价差序列。
- month_a, month_b: '01','05','09'
- cross_year: True 表示 year_b = year_a + 1(如 09-01 跨年)
- 返回 [(date, ts_a, ts_b, p_a, p_b, spread)]
- """
- data = []
- for d in dates:
- items = by_date[d]
- for ts_code in list(items.keys()):
- if not ts_code.endswith(".ZCE") or not ts_code.startswith("FG"):
- continue
- yr, mon = ts_code[2:4], ts_code[4:6]
- if mon == month_a:
- yr_b = str(int(yr) + 1) if cross_year else yr
- ts_b = f"FG{yr_b}{month_b}.ZCE"
- if ts_b in items:
- p_a = items[ts_code]
- p_b = items[ts_b]
- spread = p_b - p_a
- data.append((d, ts_code, ts_b, p_a, p_b, spread))
- return data
-
-
-def backtest_spread(spread_data, window=60, entry_z=2.0, exit_z=0.5, max_hold=20):
- equity = 1.0
- position = 0
- entry_spread = 0
- entry_p_a = 0
- entry_date = ""
- hold_days = 0
- trades = []
- daily_eq = []
-
- for i, (d, ts_a, ts_b, p_a, p_b, spread_val) in enumerate(spread_data):
- if i < window:
- daily_eq.append((d, equity))
- continue
-
- hist = [spread_data[j][5] for j in range(i - window, i)]
- roll_mean = mean(hist)
- roll_std = stdev(hist)
-
- signal = 0
- if spread_val > roll_mean + entry_z * roll_std:
- signal = -1 # 价差过大 → 空价差(空远月、多近月)
- elif spread_val < roll_mean - entry_z * roll_std:
- signal = 1
-
- should_exit = False
- if position != 0:
- hold_days += 1
- if hold_days >= max_hold:
- should_exit = True
- if position == 1 and spread_val >= roll_mean - exit_z * roll_std:
- should_exit = True
- elif position == -1 and spread_val <= roll_mean + exit_z * roll_std:
- should_exit = True
-
- if position != 0 and should_exit:
- if position == 1:
- pnl = (spread_val - entry_spread) / entry_p_a
- else:
- pnl = (entry_spread - spread_val) / entry_p_a
- equity *= (1 + pnl)
- trades.append((entry_date, d, entry_spread, spread_val, pnl, position))
- position = 0
- hold_days = 0
-
- if position == 0 and signal != 0:
- position = signal
- entry_spread = spread_val
- entry_p_a = p_a
- entry_date = d
- hold_days = 0
-
- daily_eq.append((d, equity))
-
- if position != 0 and spread_data:
- d, _, _, p_a, _, spread_val = spread_data[-1]
- if position == 1:
- pnl = (spread_val - entry_spread) / entry_p_a
- else:
- pnl = (entry_spread - spread_val) / entry_p_a
- equity *= (1 + pnl)
- trades.append((entry_date, d, entry_spread, spread_val, pnl, position))
-
- return trades, daily_eq
-
-
-def print_results(trades, daily_eq, label):
- if not trades:
- print(f"\n{label}: 无交易")
- return
- wins = [t for t in trades if t[4] > 0]
- losses = [t for t in trades if t[4] <= 0]
- total_ret = daily_eq[-1][1] - 1.0 if daily_eq else 0
- wr = len(wins) / len(trades) if trades else 0
-
- returns = []
- for i in range(1, len(daily_eq)):
- if daily_eq[i-1][1] > 0:
- returns.append(daily_eq[i][1] / daily_eq[i-1][1] - 1)
- avg_ret = mean(returns) if returns else 0
- std_ret = stdev(returns) if len(returns) > 1 else 1
- sharpe = (avg_ret / std_ret) * (252 ** 0.5) if std_ret > 0 else 0
-
- peak = 1.0
- mdd = 0.0
- for _, e in daily_eq:
- if e > peak: peak = e
- dd = (peak - e) / peak
- if dd > mdd: mdd = dd
-
- print(f"\n{label}")
- print(f" {'' if trades else '无'}交易次数: {len(trades)}")
- print(f" 总收益率: {total_ret:+.2%}")
- print(f" 夏普比率: {sharpe:.2f}")
- print(f" 最大回撤: {mdd:.2%}")
- print(f" 胜率: {wr:.0%}")
- if losses:
- avg_w = mean(t[4] for t in wins)
- avg_l = mean(t[4] for t in losses)
- print(f" 盈亏比: {abs(avg_w/avg_l):.2f}")
-
-
-# ── 分析所有有效组合 ──────────────────────────
-
-pairs = [
- ("01", "05", False, "同一年 01-05"),
- ("05", "09", False, "同一年 05-09"),
- ("09", "01", True, "跨年 09-01"),
-]
-
-# 先看各组合的统计特征
-print("=" * 60)
-print("各合约组合价差统计")
-print("=" * 60)
-for ma, mb, cross, label in pairs:
- data = build_spread(ma, mb, cross)
- n = len(data)
- vals = [r[5] for r in data]
- pos = sum(1 for v in vals if v > 0)
- avg_s = mean(vals)
- std_s = stdev(vals)
- print(f"\n{label:>12} {n:>5}天 均值{avg_s:>+7.1f} σ{std_s:>6.1f} 正{pos:>4}({pos/n:.0%})")
-
-# 回测各组合
-print("\n" + "=" * 60)
-print("均值回归回测 (window=60 entry=2σ exit=0.5σ max_hold=20)")
-print("=" * 60)
-for ma, mb, cross, label in pairs:
- data = build_spread(ma, mb, cross)
- trades, eq = backtest_spread(data, window=60, entry_z=2.0, exit_z=0.5, max_hold=20)
- print_results(trades, eq, label)
-
-# 当前可交易组合 (202607)
-print("\n" + "=" * 60)
-print("当前可交易组合分析 (FG2609, FG2701, FG2705)")
-print("=" * 60)
-
-today = "20260717"
-items = by_date.get(today, {})
-for ts in ["FG2609.ZCE", "FG2701.ZCE", "FG2705.ZCE"]:
- if ts in items:
- print(f" {ts}: {items[ts]}")
- else:
- print(f" {ts}: 无当日数据")
-
-# 当前配对及统计
-pairs_now = [
- ("FG2609.ZCE", "FG2701.ZCE", "09-01(跨年)"),
- ("FG2701.ZCE", "FG2705.ZCE", "01-05(同一年)"),
- ("FG2609.ZCE", "FG2705.ZCE", "09-05(跨年)"),
-]
-
-for ts_a, ts_b, label in pairs_now:
- if ts_a not in items or ts_b not in items:
- print(f"\n{label}: {ts_a} 或 {ts_b} 无数据")
- continue
-
- spread_now = items[ts_b] - items[ts_a]
- print(f"\n{label}: {ts_a} vs {ts_b}")
- print(f" 当前价差: {spread_now:+.1f}")
-
- # 找历史均值
- for ma, mb, cross, _ in pairs:
- if (ts_a[4:6] == ma and ts_b[4:6] == mb) or \
- (ts_a[4:6] == mb and ts_b[4:6] == ma and cross):
- data = build_spread(ma, mb, cross)
- if len(data) < 60:
- continue
- vals = [r[5] for r in data]
- # 近 60 天
- recent = vals[-60:]
- avg_r = mean(recent)
- std_r = stdev(recent)
- z = (spread_now - avg_r) / std_r if std_r else 0
- print(f" 近60日均值: {avg_r:.1f} σ: {std_r:.1f} z值: {z:.2f}")
- if abs(z) >= 2:
- print(f" → 偏离 {z:.0f}σ,触发信号!")
- else:
- print(f" → 未触发 (需 |z|>=2)")
diff --git a/chart.html b/chart.html
deleted file mode 100644
index 4cb0bc7..0000000
--- a/chart.html
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
-
-
-
-玻璃期货 spread 走势
-
-
-
-
-玻璃期货 — 合约链差价 & 主力合约日内涨跌
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/chart.py b/chart.py
deleted file mode 100644
index 62d4cd1..0000000
--- a/chart.py
+++ /dev/null
@@ -1,149 +0,0 @@
-"""
-生成 spread 数据图表(HTML)。
-用法: python3 chart.py
-"""
-import sqlite3
-import json
-
-DB_PATH = "/Users/vipg/Documents/futures-data-warehouse/db/futures.db"
-
-def main():
- conn = sqlite3.connect(DB_PATH)
- rows = conn.execute(
- "SELECT trade_date, spread, main_chg FROM spread ORDER BY trade_date"
- ).fetchall()
- conn.close()
-
- dates = [r[0] for r in rows]
- spreads = [r[1] for r in rows]
- chgs = [r[2] if r[2] is not None else None for r in rows]
-
- html = f"""
-
-
-
-
-玻璃期货 spread 走势
-
-
-
-
-玻璃期货 — 合约链差价 & 主力合约日内涨跌
-
-
-
-
-
-
-
-
-
-"""
-
- out_path = "/Users/vipg/Documents/futures-data-warehouse/chart.html"
- with open(out_path, "w") as f:
- f.write(html)
- print(f"已生成: {out_path}")
-
-if __name__ == "__main__":
- main()
diff --git a/macro.py b/macro.py
deleted file mode 100644
index cb5b6fb..0000000
--- a/macro.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""
-宏观数据管理:记录和管理月频地产景气度数据。
-"""
-import sqlite3
-import sys
-from datetime import datetime
-
-DB_PATH = "/Users/vipg/Documents/futures-data-warehouse/db/futures.db"
-
-TABLE_SQL = """
-CREATE TABLE IF NOT EXISTS macro (
- month TEXT PRIMARY KEY,
- housing_start_yoy REAL,
- note TEXT
-)
-"""
-
-
-def init():
- conn = sqlite3.connect(DB_PATH)
- conn.execute(TABLE_SQL)
- conn.commit()
- conn.close()
-
-
-def add(month, yoy, note=""):
- """添加/更新一个月的地产数据"""
- conn = sqlite3.connect(DB_PATH)
- conn.execute(
- "INSERT OR REPLACE INTO macro (month, housing_start_yoy, note) VALUES (?, ?, ?)",
- (month, yoy, note),
- )
- conn.commit()
- conn.close()
- print(f"macro: {month} 新房开工同比 {yoy:+.1f}% {note}")
-
-
-def list_all():
- conn = sqlite3.connect(DB_PATH)
- rows = conn.execute(
- "SELECT month, housing_start_yoy, note FROM macro ORDER BY month DESC"
- ).fetchall()
- conn.close()
- if not rows:
- print("暂无宏观数据")
- return
- print(f"{'月份':>8} {'新房开工同比':>10} {'备注'}")
- print("-" * 40)
- for r in rows:
- print(f"{r[0]:>8} {r[1]:>+9.1f}% {r[2] or ''}")
-
-
-def direction():
- """根据最新宏观数据返回当月方向偏好"""
- conn = sqlite3.connect(DB_PATH)
- row = conn.execute(
- "SELECT month, housing_start_yoy FROM macro ORDER BY month DESC LIMIT 1"
- ).fetchone()
- conn.close()
- if not row:
- return "观望", "无宏观数据"
-
- month, yoy = row
- if yoy > 5:
- return "做多", f"{month} 开工同比 {yoy:+.1f}%,景气回暖"
- elif yoy > -5:
- return "观望", f"{month} 开工同比 {yoy:+.1f}%,方向不明"
- else:
- return "做空", f"{month} 开工同比 {yoy:+.1f}%,持续低迷"
-
-
-if __name__ == "__main__":
- init()
-
- if len(sys.argv) >= 3 and sys.argv[1] == "add":
- add(sys.argv[2], float(sys.argv[3]), " ".join(sys.argv[4:]))
- elif len(sys.argv) >= 2 and sys.argv[1] == "list":
- list_all()
- elif len(sys.argv) >= 2 and sys.argv[1] == "direction":
- d, reason = direction()
- print(f"当前方向: {d}")
- print(f"依据: {reason}")
- else:
- print("用法:")
- print(" python3 macro.py add 202606 -26.0 6月数据")
- print(" python3 macro.py list 查看所有")
- print(" python3 macro.py direction 查看当月方向")
diff --git a/quote.py b/quote.py
deleted file mode 100644
index 1e28ac4..0000000
--- a/quote.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""
-查询指定交易日玻璃期货主力合约行情。
-用法: python3 quote.py 20260717
-"""
-import sqlite3
-import sys
-from datetime import datetime
-
-DB_PATH = "/Users/vipg/Documents/futures-data-warehouse/db/futures.db"
-
-def main():
- if len(sys.argv) < 2:
- print("用法: python3 quote.py YYYYMMDD")
- sys.exit(1)
-
- date_str = sys.argv[1]
- try:
- datetime.strptime(date_str, "%Y%m%d")
- except ValueError:
- print(f"日期格式错误: {date_str},应为 YYYYMMDD")
- sys.exit(1)
-
- conn = sqlite3.connect(DB_PATH)
-
- count = conn.execute(
- "SELECT COUNT(*) FROM daily WHERE trade_date=?", (date_str,)
- ).fetchone()[0]
- if count == 0:
- import urllib.request, json
- req_data = json.dumps({
- "api_name": "trade_cal",
- "token": "76efd8465f9f2591aa42a385268e06acf6b80b7a15be2267ad2281b7",
- "params": {"exchange": "CZCE", "start_date": date_str, "end_date": date_str},
- "fields": "cal_date,is_open",
- }).encode()
- resp = json.loads(urllib.request.urlopen(
- urllib.request.Request("https://api.tushare.pro", data=req_data,
- headers={"Content-Type": "application/json"})
- ).read())
- is_open = resp.get("data", {}).get("items", [[None, None]])[0][1]
- if is_open == 1:
- print(f"{date_str} 是交易日,但数据库尚无数据(需先运行 update.py 拉取)")
- elif is_open == 0:
- print(f"{date_str} 非交易日")
- else:
- print(f"{date_str} 无数据")
- conn.close()
- sys.exit(1)
-
- rows = conn.execute("""
- SELECT d.ts_code, d.open, d.high, d.low, d.close
- FROM daily d
- JOIN contracts c ON d.ts_code = c.ts_code
- WHERE d.trade_date = ?
- AND c.delist_date > ?
- AND d.oi > 0
- ORDER BY c.delist_date
- """, (date_str, date_str)).fetchall()
-
- if not rows:
- print(f"{date_str} 无活跃合约数据")
- conn.close()
- sys.exit(1)
-
- print(f"\n{date_str} 玻璃期货")
- print(f"{'合约':>12} 收盘价 差价")
- print("-" * 34)
- prev = None
- total_diff = 0.0
- for r in rows:
- close = float(r[4]) if r[4] is not None and r[4] != "" else None
- close_str = f"{close:.1f}" if close else "-"
- diff = ""
- if close is not None and prev is not None:
- d = close - prev
- diff = f"{d:+.1f}"
- total_diff += d
- print(f"{r[0]:>12} {close_str:>7} {diff:>6}")
- if close is not None:
- prev = close
- print("-" * 34)
- print(f"{'差价合计':>20} {total_diff:+.1f}")
-
- conn.close()
-
-if __name__ == "__main__":
- main()
diff --git a/spread_chart.html b/spread_chart.html
deleted file mode 100644
index 9020b19..0000000
--- a/spread_chart.html
+++ /dev/null
@@ -1,163 +0,0 @@
-
-
-
-
-
-FG 主力合约价差走势
-
-
-
-
-
-玻璃期货 — 主力合约价差分析
-1月 / 5月 / 9月 两两价差(同年配对,远月减近月) | 均值回归信号(60日滚动,2σ 入场)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/spread_chart.py b/spread_chart.py
deleted file mode 100644
index 54c1baa..0000000
--- a/spread_chart.py
+++ /dev/null
@@ -1,402 +0,0 @@
-"""
-主力合约 1月/5月/9月 两两价差走势图 + 均值回归交易信号。
-输出 HTML 图表,浏览器打开即可查看。
-
-用法: python3 spread_chart.py
-"""
-import sqlite3
-import json
-import os
-from collections import defaultdict
-from statistics import mean, stdev
-
-DB_PATH = os.path.join(os.path.dirname(__file__), "db", "futures.db")
-OUT_PATH = os.path.join(os.path.dirname(__file__), "spread_chart.html")
-
-WINDOW = 60
-ENTRY_Z = 2.0
-
-
-def build_spreads():
- """构建三组价差序列:01-05, 05-09, 01-09(同年配对)。"""
- conn = sqlite3.connect(DB_PATH)
- daily = conn.execute(
- "SELECT ts_code, trade_date, close FROM daily WHERE close IS NOT NULL AND close != '' ORDER BY trade_date"
- ).fetchall()
- conn.close()
-
- by_date = defaultdict(dict)
- for ts_code, d, close in daily:
- by_date[d][ts_code] = float(close)
- dates = sorted(by_date.keys())
-
- def build(month_a, month_b, cross_year=False):
- per_date = {}
- for d in dates:
- items = by_date[d]
- best_yr = -1
- best = None
- for ts_code in items:
- if not ts_code.endswith(".ZCE") or not ts_code.startswith("FG"):
- continue
- yr_str, mon = ts_code[2:4], ts_code[4:6]
- if mon == month_a:
- yr = int(yr_str)
- yr_b_str = str(yr + 1) if cross_year else yr_str
- ts_b = f"FG{yr_b_str}{month_b}.ZCE"
- if ts_b in items and yr > best_yr:
- best_yr = yr
- spread = items[ts_b] - items[ts_code]
- best = (d, spread)
- if best:
- per_date[best[0]] = best[1]
-
- sorted_dates = sorted(per_date.keys())
- return sorted_dates, [per_date[d] for d in sorted_dates]
-
- return {
- "01-05": build("01", "05"),
- "05-09": build("05", "09"),
- "01-09": build("01", "09"),
- }
-
-
-def compute_signals(spread_data):
- """计算每个价差的滚动 z-score 和交易信号。"""
- month_map = {
- "01-05": ("01", "05"),
- "05-09": ("05", "09"),
- "01-09": ("01", "09"),
- }
-
- signals = {}
- for label, (dates, values) in spread_data.items():
- if len(values) < WINDOW + 1:
- signals[label] = None
- continue
-
- ma, mb = month_map[label]
- recent = values[-WINDOW:]
- roll_mean = mean(recent)
- roll_std = stdev(recent)
- latest = values[-1]
- z = (latest - roll_mean) / roll_std if roll_std > 0 else 0
-
- if z > ENTRY_Z:
- action = f"做空{mb},做多{ma}"
- elif z < -ENTRY_Z:
- action = f"做多{mb},做空{ma}"
- else:
- action = "观望"
-
- z_dates = dates[WINDOW:]
- z_values = []
- for i in range(WINDOW, len(values)):
- h = values[i - WINDOW : i]
- m = mean(h)
- s = stdev(h)
- z_values.append((values[i] - m) / s if s > 0 else 0)
-
- signals[label] = {
- "latest": latest,
- "latest_date": dates[-1],
- "mean": round(roll_mean, 1),
- "std": round(roll_std, 1),
- "z_score": round(z, 2),
- "action": action,
- "count": len(values),
- "z_dates": z_dates,
- "z_values": z_values,
- }
-
- return signals
-
-
-def build_chart_data(spread_data, signals):
- """构建前端所需的数据结构。"""
- all_dates_set = set()
- for dates, _ in spread_data.values():
- all_dates_set.update(dates)
- all_dates = sorted(all_dates_set)
-
- colors = {
- "01-05": {"line": "#06b6d4", "area": "rgba(6,182,212,0.08)"},
- "05-09": {"line": "#22c55e", "area": "rgba(34,197,94,0.08)"},
- "01-09": {"line": "#f59e0b", "area": "rgba(245,158,11,0.08)"},
- }
-
- # 主图数据集
- datasets = []
- for label, (dates, values) in spread_data.items():
- date_map = dict(zip(dates, values))
- series = [date_map.get(d) for d in all_dates]
- c = colors[label]
- datasets.append({
- "label": label,
- "data": series,
- "borderColor": c["line"],
- "backgroundColor": c["area"],
- "borderWidth": 1.5,
- "pointRadius": 0,
- "fill": True,
- "tension": 0.1,
- })
-
- # z-score 数据集
- z_datasets = []
- for label, sig in signals.items():
- if sig is None:
- continue
- date_map = dict(zip(sig["z_dates"], sig["z_values"]))
- series = [date_map.get(d) for d in all_dates]
- c = colors[label]
- z_datasets.append({
- "label": label,
- "data": series,
- "borderColor": c["line"],
- "backgroundColor": "transparent",
- "borderWidth": 1,
- "pointRadius": 0,
- "tension": 0.1,
- })
-
- # 统计卡片
- stat_cards = []
- for label, (dates, values) in spread_data.items():
- if not values:
- continue
- pos = sum(1 for v in values if v > 0)
- neg = sum(1 for v in values if v < 0)
- stat_cards.append({
- "label": label,
- "color": colors[label]["line"],
- "count": len(values),
- "avg": round(sum(values) / len(values), 1),
- "max": max(values),
- "min": min(values),
- "pos": pos,
- "neg": neg,
- })
-
- # 信号卡片
- sig_cards = []
- for label, sig in signals.items():
- if sig is None:
- continue
- if "做空" in sig["action"]:
- sig_type = "bear"
- sig_color = "#ef4444"
- elif "做多" in sig["action"]:
- sig_type = "bull"
- sig_color = "#22c55e"
- else:
- sig_type = "neutral"
- sig_color = "#94a3b8"
- sig_cards.append({
- "label": label,
- "color": colors[label]["line"],
- "sig_type": sig_type,
- "sig_color": sig_color,
- "action": sig["action"],
- "latest": sig["latest"],
- "mean": sig["mean"],
- "std": sig["std"],
- "z_score": sig["z_score"],
- "latest_date": sig["latest_date"],
- })
-
- return {
- "all_dates": all_dates,
- "datasets": datasets,
- "z_datasets": z_datasets,
- "stat_cards": stat_cards,
- "sig_cards": sig_cards,
- }
-
-
-TEMPLATE = r"""
-
-
-
-
-FG 主力合约价差走势
-
-
-
-
-
-玻璃期货 — 主力合约价差分析
-1月 / 5月 / 9月 两两价差(同年配对,远月减近月) | 均值回归信号(60日滚动,2σ 入场)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-
-
-def generate_html(chart_data):
- """渲染 HTML。"""
- payload = json.dumps({
- "allDates": chart_data["all_dates"],
- "datasets": chart_data["datasets"],
- "zDatasets": chart_data["z_datasets"],
- "statCards": chart_data["stat_cards"],
- "sigCards": chart_data["sig_cards"],
- })
- html = TEMPLATE.replace("__DATA__", payload)
-
- with open(OUT_PATH, "w") as f:
- f.write(html)
- print(f"已生成: {OUT_PATH}")
-
-
-if __name__ == "__main__":
- data = build_spreads()
- sigs = compute_signals(data)
- chart_data = build_chart_data(data, sigs)
- generate_html(chart_data)
diff --git a/trade.py b/trade.py
deleted file mode 100644
index 8ee479c..0000000
--- a/trade.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""
-每日交易信号汇总。
-开盘前跑一下,输出当天方向建议。
-"""
-import sqlite3
-from datetime import datetime
-from macro import direction as macro_direction, init as macro_init
-
-DB_PATH = "/Users/vipg/Documents/futures-data-warehouse/db/futures.db"
-
-def main():
- macro_init()
-
- today = datetime.now().strftime("%Y%m%d")
-
- # 1. 宏观方向
- dir, reason = macro_direction()
- print(f"日期: {today}")
- print(f"宏观方向: {dir}")
- print(f"依据: {reason}")
-
- if dir == "观望":
- print("\n建议: 今天不交易")
- return
-
- # 2. 持仓量最大的主力合约
- conn = sqlite3.connect(DB_PATH)
- main_row = conn.execute("""
- SELECT d.ts_code, d.open, d.close, d.high, d.low, d.oi
- FROM daily d
- JOIN contracts c ON d.ts_code = c.ts_code
- WHERE d.trade_date = ? AND c.delist_date > ? AND d.oi > 0
- ORDER BY d.oi DESC LIMIT 1
- """, (today, today)).fetchone()
- conn.close()
-
- if main_row:
- ts, open_p, close_p, high, low, oi = main_row
- print(f"\n主力合约: {ts}")
- print(f"昨收: {close_p} 持仓: {int(oi):,}")
- else:
- print("\n注意: 今日行情数据尚未拉取,请先运行 update.py")
- print("或使用历史近似的合约数据")
-
- # 3. 交易提醒
- print(f"\n{'='*40}")
- print(f"今日计划: {dir}")
- print(f" 开盘后确认方向后入场")
- print(f" 止损: 5跳")
- print(f" 收盘前5分钟平仓")
- print(f"{'='*40}")
- print(f"\n风控提醒:")
- print(f" - 单笔止损严格执行 5 跳")
- print(f" - 连续亏损 3 天暂停交易")
- print(f" - 累计盈利达标后买入期权做保护")
-
-
-if __name__ == "__main__":
- main()
diff --git a/update.py b/update.py
deleted file mode 100644
index 1902089..0000000
--- a/update.py
+++ /dev/null
@@ -1,351 +0,0 @@
-import csv
-import os
-import sqlite3
-import sys
-import time
-from datetime import datetime
-
-import requests
-
-TOKEN = "76efd8465f9f2591aa42a385268e06acf6b80b7a15be2267ad2281b7"
-API_URL = "https://api.tushare.pro"
-DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
-DB_PATH = os.path.join(os.path.dirname(__file__), "db", "futures.db")
-
-FUT_DAILY_FIELDS = [
- "ts_code", "trade_date", "pre_close", "pre_settle",
- "open", "high", "low", "close", "settle",
- "change1", "change2", "vol", "amount", "oi", "oi_chg",
- "delv_settle",
-]
-
-
-def tushare_query(api_name, params=None, fields=None):
- req = {"api_name": api_name, "token": TOKEN}
- if params:
- req["params"] = params
- if fields:
- req["fields"] = fields
- resp = requests.post(API_URL, json=req)
- data = resp.json()
- if data["code"] != 0:
- raise Exception(f"API error ({data['code']}): {data['msg']}")
- fields_list = data["data"]["fields"]
- items = data["data"]["items"]
- return [dict(zip(fields_list, item)) for item in items]
-
-
-def is_trading_day(exchange="CZCE"):
- today = datetime.now().strftime("%Y%m%d")
- rows = tushare_query(
- "trade_cal",
- params={"exchange": exchange, "start_date": today, "end_date": today},
- fields="cal_date,is_open",
- )
- if rows:
- return rows[0]["is_open"] == "1"
- return True
-
-
-# ── 合约列表 ──────────────────────────────────
-
-def get_active_contracts(exchange, fut_code):
- contracts = tushare_query(
- "fut_basic",
- params={"exchange": exchange, "fut_code": fut_code, "fut_type": "1"},
- fields="ts_code,symbol,name,list_date,delist_date",
- )
- today = datetime.now().strftime("%Y%m%d")
- return [c for c in contracts if not c.get("delist_date") or c["delist_date"] > today]
-
-
-def get_all_contracts(exchange, fut_code):
- return tushare_query(
- "fut_basic",
- params={"exchange": exchange, "fut_code": fut_code, "fut_type": "1"},
- fields="ts_code,symbol,name,list_date,delist_date",
- )
-
-
-# ── 增量更新 CSV ──────────────────────────────
-
-def update_contract_csv(fut_code, contract):
- ts_code = contract["ts_code"]
- csv_name = ts_code.split(".")[0] + ".csv"
- out_dir = os.path.join(DATA_DIR, fut_code)
- csv_path = os.path.join(out_dir, csv_name)
- os.makedirs(out_dir, exist_ok=True)
-
- existing = []
- if os.path.exists(csv_path):
- with open(csv_path) as f:
- reader = csv.DictReader(f)
- existing = list(reader)
-
- last_date = existing[-1]["trade_date"] if existing else None
-
- params = {"ts_code": ts_code}
- if last_date:
- params["start_date"] = last_date
-
- rows = tushare_query("fut_daily", params=params, fields=",".join(FUT_DAILY_FIELDS))
- if not rows:
- return None
-
- rows.sort(key=lambda r: r["trade_date"])
-
- if not existing:
- with open(csv_path, "w", newline="") as f:
- w = csv.DictWriter(f, fieldnames=FUT_DAILY_FIELDS)
- w.writeheader()
- w.writerows(rows)
- return rows
-
- if rows[0]["trade_date"] == last_date:
- existing = existing[:-1]
- merged = existing + rows
- else:
- merged = existing + rows
-
- with open(csv_path, "w", newline="") as f:
- w = csv.DictWriter(f, fieldnames=FUT_DAILY_FIELDS)
- w.writeheader()
- w.writerows(merged)
-
- return rows
-
-
-def update_all_contracts(fut_code, contracts):
- all_new = []
- for c in contracts:
- code = c["ts_code"].split(".")[0]
- new_rows = update_contract_csv(fut_code, c)
- if new_rows is None:
- print(f" {code:12s} 无新数据")
- else:
- print(f" {code:12s} +{len(new_rows)} 条 ({new_rows[0]['trade_date']} ~ {new_rows[-1]['trade_date']})")
- all_new.extend(new_rows)
- time.sleep(0.3)
- return all_new
-
-
-# ── 全量拉取(初始化用) ─────────────────────
-
-def fetch_all_csv(fut_code, contracts):
- out_dir = os.path.join(DATA_DIR, fut_code)
- os.makedirs(out_dir, exist_ok=True)
- for c in contracts:
- ts_code = c["ts_code"]
- csv_name = ts_code.split(".")[0] + ".csv"
- csv_path = os.path.join(out_dir, csv_name)
- rows = tushare_query(
- "fut_daily", params={"ts_code": ts_code}, fields=",".join(FUT_DAILY_FIELDS),
- )
- if not rows:
- print(f" {csv_name:12s} 无数据,跳过")
- continue
- rows.sort(key=lambda r: r["trade_date"])
- with open(csv_path, "w", newline="") as f:
- w = csv.DictWriter(f, fieldnames=FUT_DAILY_FIELDS)
- w.writeheader()
- w.writerows(rows)
- print(f" {csv_name:12s} {len(rows)} 条 ({rows[0]['trade_date']} ~ {rows[-1]['trade_date']})")
- time.sleep(0.3)
-
-
-# ── 数据库 ────────────────────────────────────
-
-def init_db():
- conn = sqlite3.connect(DB_PATH)
- conn.execute("""
- CREATE TABLE IF NOT EXISTS daily (
- ts_code TEXT NOT NULL, trade_date TEXT NOT NULL,
- pre_close REAL, pre_settle REAL,
- open REAL, high REAL, low REAL, close REAL, settle REAL,
- change1 REAL, change2 REAL,
- vol REAL, amount REAL, oi REAL, oi_chg REAL,
- delv_settle REAL,
- PRIMARY KEY (ts_code, trade_date)
- )
- """)
- conn.execute("""
- CREATE TABLE IF NOT EXISTS contracts (
- ts_code TEXT PRIMARY KEY,
- symbol TEXT, name TEXT, exchange TEXT,
- fut_code TEXT, list_date TEXT, delist_date TEXT
- )
- """)
- conn.execute("""
- CREATE TABLE IF NOT EXISTS spread (
- trade_date TEXT PRIMARY KEY,
- spread REAL,
- main_ts_code TEXT,
- main_chg REAL
- )
- """)
- try:
- conn.execute("ALTER TABLE spread ADD COLUMN main_chg REAL")
- except sqlite3.OperationalError:
- pass
- conn.commit()
- conn.close()
-
-
-def sync_contracts_to_db(fut_code, exchange, contracts):
- conn = sqlite3.connect(DB_PATH)
- cursor = conn.cursor()
- for c in contracts:
- cursor.execute(
- "INSERT OR REPLACE INTO contracts VALUES (?, ?, ?, ?, ?, ?, ?)",
- (c["ts_code"], c["symbol"], c["name"], exchange, fut_code,
- c["list_date"], c.get("delist_date")),
- )
- conn.commit()
- conn.close()
- print(f" contracts 表同步 {len(contracts)} 条")
-
-
-def sync_rows_to_db(rows):
- if not rows:
- return
- conn = sqlite3.connect(DB_PATH)
- placeholders = ",".join("?" for _ in FUT_DAILY_FIELDS)
- cols = ",".join(FUT_DAILY_FIELDS)
- sql = f"INSERT OR REPLACE INTO daily ({cols}) VALUES ({placeholders})"
- vals = [[r.get(c) for c in FUT_DAILY_FIELDS] for r in rows]
- conn.executemany(sql, vals)
- conn.commit()
- conn.close()
- print(f" daily 表更新 {len(rows)} 条")
-
-
-def sync_all_csv_to_db():
- conn = sqlite3.connect(DB_PATH)
- cursor = conn.cursor()
- for root, _, files in os.walk(DATA_DIR):
- for fname in sorted(files):
- if not fname.endswith(".csv"):
- continue
- fpath = os.path.join(root, fname)
- with open(fpath) as f:
- reader = csv.DictReader(f)
- rows = list(reader)
- if not rows:
- continue
- placeholders = ",".join("?" for _ in FUT_DAILY_FIELDS)
- cols = ",".join(FUT_DAILY_FIELDS)
- sql = f"INSERT OR REPLACE INTO daily ({cols}) VALUES ({placeholders})"
- vals = [[r.get(c) for c in FUT_DAILY_FIELDS] for r in rows]
- cursor.executemany(sql, vals)
- print(f" {fname:12s} {len(rows)} 条")
- conn.commit()
- conn.close()
-
-
-def sync_spread():
- conn = sqlite3.connect(DB_PATH)
- conn.row_factory = sqlite3.Row
- dates = conn.execute("SELECT DISTINCT trade_date FROM daily ORDER BY trade_date").fetchall()
-
- count = 0
- for d in dates:
- date_str = d["trade_date"]
- rows = conn.execute("""
- SELECT d.ts_code, d.close, d.open, d.vol
- FROM daily d
- JOIN contracts c ON d.ts_code = c.ts_code
- WHERE d.trade_date = ?
- AND c.delist_date > ?
- AND d.oi > 0
- ORDER BY c.delist_date
- """, (date_str, date_str)).fetchall()
- if len(rows) < 2:
- continue
-
- closes = []
- main_ts = None
- main_vol = -1
- main_chg = None
- for r in rows:
- try:
- closes.append(float(r["close"]))
- except (TypeError, ValueError):
- closes.append(None)
- if r["ts_code"][4:6] in ("01", "05", "09"):
- v = r["vol"]
- if v is not None and v > main_vol:
- main_vol = v
- main_ts = r["ts_code"]
- try:
- c = float(r["close"])
- o = float(r["open"])
- main_chg = c - o
- except (TypeError, ValueError):
- main_chg = None
-
- closes = [c for c in closes if c is not None]
- if len(closes) < 2:
- continue
-
- spread = closes[-1] - closes[0]
- conn.execute(
- "INSERT OR REPLACE INTO spread VALUES (?, ?, ?, ?)",
- (date_str, spread, main_ts, main_chg),
- )
- count += 1
-
- conn.commit()
- conn.close()
- print(f" spread 表更新 {count} 个交易日")
-
-
-# ── 主入口 ────────────────────────────────────
-
-FG_EXTRA_CODES = {f"FG{suffix}.ZCE" for suffix in
- [f"14{i:02d}" for i in range(1, 13)] +
- [f"15{i:02d}" for i in range(1, 13)] +
- [f"16{i:02d}" for i in range(1, 13)] +
- [f"17{i:02d}" for i in range(1, 13)] +
- [f"18{i:02d}" for i in range(1, 13)] +
- [f"19{i:02d}" for i in range(1, 13)] +
- [f"20{i:02d}" for i in range(1, 13)] +
- [f"21{i:02d}" for i in range(1, 13)] +
- [f"22{i:02d}" for i in range(1, 13)] +
- [f"23{i:02d}" for i in range(1, 13)] +
- [f"24{i:02d}" for i in range(1, 13)] +
- [f"25{i:02d}" for i in range(1, 13)] +
- [f"26{i:02d}" for i in range(1, 8)]}
-
-
-if __name__ == "__main__":
- full_init = "--init" in sys.argv
- init_db()
- active = get_active_contracts("CZCE", "FG")
-
- if full_init:
- all_fg = get_all_contracts("CZCE", "FG")
- active_codes = {c["ts_code"] for c in active}
- extra = [c for c in all_fg if c["ts_code"] in FG_EXTRA_CODES and c["ts_code"] not in active_codes]
- contracts = active + extra
- print(f"全量初始化: {len(contracts)} 个合约")
- for c in contracts:
- print(f" {c['ts_code'].split('.')[0]:8s} 上市:{c['list_date']} 退市:{c.get('delist_date','-')}")
- fetch_all_csv("FG", contracts)
- sync_contracts_to_db("FG", "CZCE", contracts)
- sync_all_csv_to_db()
- sync_spread()
- else:
- if not is_trading_day():
- print("今天非交易日,无需更新")
- print("完成")
- sys.exit(0)
- print(f"增量更新: {len(active)} 个活跃合约")
- new_rows = update_all_contracts("FG", active)
- if new_rows:
- sync_contracts_to_db("FG", "CZCE", active)
- sync_rows_to_db(new_rows)
- else:
- print(" 无新数据")
-
- sync_spread()
- print("完成")