Initial: MoFin 持仓分析与策略管理系统
核心模块: - 策略生命周期管理 (strategy_lifecycle.py) - 技术分析引擎 (technical_analysis.py) - 双维度策略评估 (strategy_evaluator.py) - 实时行情获取 (get_realtime_prices.py) - Web Dashboard (server.py, :8899) 提示词版本管理: - prompt_manager 模块 — 统一管理所有知微提示词 - 8个提示词共24个版本已录入 - 策略→提示词版本关联追踪 - Dashboard「提示词」Tab 数据源增强: - 服务端 POST /api/update/realtime 端点已就绪 - clients/tdx-relay/ — 小小莫在Windows上开发的通达信中继 - 解决港股15分钟延迟问题
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
# 通达信行情中继 — 开发指南
|
||||
|
||||
> 写给小小莫。在 Windows 上用 Python 直连招商证券的通达信行情主站,获取实时港股行情,推送到知微的 MoFin Dashboard。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
知微的 MoFin 系统目前用腾讯免费 API 获取行情。A 股是实时的,但港股有 **15 分钟延迟**。
|
||||
|
||||
你在 Windows 上的招商证券客户端(通达信内核)是买了港股实时数据的。我们可以通过 Python 直连招商证券的行情主站,拿到**真正实时的港股行情**,然后推送到知微的 Dashboard。
|
||||
|
||||
## 二、技术方案
|
||||
|
||||
```
|
||||
招商证券客户端 (通信设置里的IP:7709)
|
||||
│
|
||||
▼
|
||||
opentdx (Python库)
|
||||
│
|
||||
▼
|
||||
tdx_relay.py (你写)
|
||||
│ POST /api/update/realtime
|
||||
▼
|
||||
MoFin Dashboard (192.168.1.246:8899)
|
||||
```
|
||||
|
||||
推荐用 **opentdx**(pytdx 的升级版):
|
||||
|
||||
```bash
|
||||
pip install opentdx requests
|
||||
```
|
||||
|
||||
## 三、第一步:拿行情主站IP
|
||||
|
||||
打开招商证券 PC 客户端:
|
||||
|
||||
1. **菜单 → 选项 → 通信设置**
|
||||
2. 你会看到一列行情主站,类似:`招商证券深圳主站 113.105.73.88:7709`
|
||||
3. 记下来,填到 `src/relay/config.py` 里
|
||||
|
||||
或者在命令行找:
|
||||
|
||||
```cmd
|
||||
tasklist | findstr "zhsh" # 找招商证券进程PID
|
||||
netstat -ano | findstr "7709" # 看连接的IP
|
||||
```
|
||||
|
||||
## 四、CLI 验证连通性
|
||||
|
||||
装好 opentdx 后,先用命令行测试:
|
||||
|
||||
```bash
|
||||
# 测试 A 股
|
||||
opentdx quote "SZ 000001"
|
||||
|
||||
# 测试港股(用你的服务器IP替换)
|
||||
opentdx g-quote "HK_MAIN_BOARD 00700" --server 113.105.73.88:7709
|
||||
```
|
||||
|
||||
> opentdx 的完整命令列表:`opentdx doc`(交互式文档)
|
||||
|
||||
## 五、Python 代码示例
|
||||
|
||||
### 连接 + 获取港股行情
|
||||
|
||||
```python
|
||||
from opentdx.tdxClient import TdxClient
|
||||
from opentdx.const import EX_MARKET
|
||||
|
||||
# 方式一:自动选最快服务器
|
||||
with TdxClient() as client:
|
||||
# A股
|
||||
a_quotes = client.stock_quotes([(0, '000001')])
|
||||
|
||||
# 港股 ⭐
|
||||
hk_quotes = client.goods_quotes([
|
||||
(EX_MARKET.HK_MAIN_BOARD, '00700'), # 腾讯
|
||||
(EX_MARKET.HK_MAIN_BOARD, '09988'), # 阿里
|
||||
])
|
||||
for q in hk_quotes:
|
||||
print(f"{q['code']}: {q['price']} {q['change_pct']}%")
|
||||
```
|
||||
|
||||
> ⚠️ `opentdx` 的具体 API 以 `opentdx doc` 为准。
|
||||
> GitHub: https://github.com/acb6104/opentdx
|
||||
|
||||
### 推送到 MoFin
|
||||
|
||||
用现成的工具类:
|
||||
|
||||
```python
|
||||
from relay.pusher import MoFinPusher
|
||||
|
||||
pusher = MoFinPusher("http://192.168.1.246:8899")
|
||||
result = pusher.push([
|
||||
{
|
||||
"code": "00700",
|
||||
"name": "腾讯控股",
|
||||
"price": 463.6,
|
||||
"change_pct": 1.55,
|
||||
"high": 468.0,
|
||||
"low": 460.2,
|
||||
"open": 462.0,
|
||||
"volume": 25000000,
|
||||
"timestamp": "2026-06-12 14:30:00"
|
||||
}
|
||||
])
|
||||
print(result) # {"status": "ok", "updated": 1}
|
||||
```
|
||||
|
||||
## 六、你的任务清单
|
||||
|
||||
### 阶段一:环境 + 连通性
|
||||
- [ ] 装 Python 3.10+(如果没有的话)
|
||||
- [ ] `pip install opentdx requests`
|
||||
- [ ] 运行 `opentdx doc` 看看接口
|
||||
- [ ] 从招商证券通信设置拿到行情主站IP
|
||||
- [ ] 用 CLI 测试港股:`opentdx g-quote "HK_MAIN_BOARD 00700" --server <IP>:7709`
|
||||
|
||||
### 阶段二:港股行情获取
|
||||
- [ ] 实现 `tdx_client.py` 的连接和港股查询
|
||||
- [ ] 验证单只港股(腾讯00700)数据正确
|
||||
- [ ] 验证批量港股(持仓列表)
|
||||
- [ ] ⚠️ **对比招商证券客户端价格,确认数据准确**(红线)
|
||||
|
||||
### 阶段三:数据推送
|
||||
- [ ] 实现 `run_relay.py` 主循环
|
||||
- [ ] 测试推送一条数据到 MoFin Dashboard
|
||||
- [ ] 全量推送所有持仓港股
|
||||
|
||||
### 阶段四:自动化
|
||||
- [ ] 设置 Windows 定时任务(每15~30秒运行一次)
|
||||
|
||||
## 七、注意事项
|
||||
|
||||
### 数据准确性(⚠️红线)
|
||||
```python
|
||||
# 从通达信拿到的价格 和 招商证券客户端显示的现价
|
||||
# 两者必须一致!
|
||||
tdx_price = 463.6
|
||||
assert abs(tdx_price - 招商证券_显示价格) < 0.01
|
||||
```
|
||||
|
||||
### A股不要动
|
||||
A 股继续走腾讯 API,已经是实时的。**本项目只解决港股延迟。**
|
||||
|
||||
### 字段映射
|
||||
|
||||
| 含义 | 腾讯API索引 | 通达信字段 |
|
||||
|------|------------|-----------|
|
||||
| 当前价 | fields[3] | price |
|
||||
| 昨收 | fields[4] | last_close |
|
||||
| 今开 | fields[5] | open |
|
||||
| 最高 | fields[33] | high |
|
||||
| 最低 | fields[34] | low |
|
||||
| 涨跌幅 | fields[32] | change_pct |
|
||||
|
||||
### 回退方案
|
||||
通达信连不上时自动回退腾讯 API(当前方案),不中断行情更新。
|
||||
|
||||
```python
|
||||
try:
|
||||
data = tdx_client.get_quotes(codes)
|
||||
except Exception:
|
||||
data = tencent_api.get_quotes(codes) # 回退
|
||||
```
|
||||
|
||||
### 连接稳定性
|
||||
opentdx 内置心跳,但网络不稳时需要重连:
|
||||
|
||||
```python
|
||||
def safe_get(client, codes, retries=3):
|
||||
for i in range(retries):
|
||||
try:
|
||||
return client.goods_quotes(codes)
|
||||
except (ConnectionError, TimeoutError):
|
||||
client.disconnect()
|
||||
time.sleep(2)
|
||||
client.connect(ip, port)
|
||||
return None # 回退腾讯API
|
||||
```
|
||||
|
||||
## 八、参考
|
||||
|
||||
| 资源 | 地址 |
|
||||
|------|------|
|
||||
| opentdx GitHub | https://github.com/acb6104/opentdx |
|
||||
| opentdx PyPI | `pip install opentdx` |
|
||||
| MoFin Dashboard | http://192.168.1.246:8899 |
|
||||
| 架构文档 | 见本目录上级 `docs/` |
|
||||
@@ -0,0 +1,2 @@
|
||||
opentdx>=0.1.0
|
||||
requests>=2.25.0
|
||||
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
echo MoFin TDX Relay - 依赖安装
|
||||
pip install opentdx
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo ❌ opentdx 安装失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
pip install requests
|
||||
echo ✅ 安装完成
|
||||
echo 下一步:编辑 src/relay/config.py 填入招商证券IP
|
||||
echo 然后运行:python tests/test_tdx_connect.py
|
||||
pause
|
||||
@@ -0,0 +1,62 @@
|
||||
"""运行入口 — 循环获取通达信行情并推送 MoFin
|
||||
|
||||
小小莫:完成 tdx_client.py 后运行此脚本。
|
||||
"""
|
||||
|
||||
import time
|
||||
import logging
|
||||
from relay.tdx_client import TDXClient
|
||||
from relay.pusher import MoFinPusher
|
||||
from relay.config import MARKET_SERVERS, MOFIN_URL, PUSH_INTERVAL, HK_STOCKS
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def find_working_server() -> tuple:
|
||||
"""自动检测可用的通达信服务器"""
|
||||
for name, ip, port in MARKET_SERVERS:
|
||||
try:
|
||||
client = TDXClient()
|
||||
client.connect(ip, port)
|
||||
data = client.get_hk_quote(31, "00700")
|
||||
client.close()
|
||||
if data and data.get("price"):
|
||||
log.info(f"✅ {name} ({ip}:{port}) 可用")
|
||||
return (ip, port)
|
||||
except Exception as e:
|
||||
log.warning(f"❌ {name} ({ip}:{port}): {e}")
|
||||
return (None, None)
|
||||
|
||||
|
||||
def main():
|
||||
log.info("🚀 TDX Relay 启动")
|
||||
log.info(f"目标: {MOFIN_URL} | 港股: {len(HK_STOCKS)}只 | 间隔: {PUSH_INTERVAL}s")
|
||||
|
||||
ip, port = find_working_server()
|
||||
if not ip:
|
||||
log.error("没有可用服务器")
|
||||
return
|
||||
|
||||
pusher = MoFinPusher(MOFIN_URL)
|
||||
client = TDXClient()
|
||||
try:
|
||||
client.connect(ip, port)
|
||||
while True:
|
||||
try:
|
||||
hk_data = client.get_hk_quotes()
|
||||
if hk_data:
|
||||
r = pusher.push(hk_data)
|
||||
log.info(f"推送 {len(hk_data)} 只: {r.get('status')}")
|
||||
time.sleep(PUSH_INTERVAL)
|
||||
except (ConnectionError, TimeoutError):
|
||||
log.warning("断连,重试...")
|
||||
client.close()
|
||||
time.sleep(3)
|
||||
client.connect(ip, port)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
"""tdx-relay package — 通达信行情中继"""
|
||||
@@ -0,0 +1,39 @@
|
||||
"""配置管理 — 请在 Windows 上填好招商证券行情主站IP后运行"""
|
||||
|
||||
# 招商证券行情主站列表
|
||||
# 打开招商证券PC客户端 → 通信设置 查看
|
||||
# 格式: (名称, IP, 端口)
|
||||
MARKET_SERVERS = [
|
||||
# 示例(替换为你的实际IP):
|
||||
# ("招商证券深圳主站", "113.105.73.88", 7709),
|
||||
# ("招商证券上海主站", "211.154.53.106", 7709),
|
||||
# TODO: 小小莫替换为实际IP
|
||||
]
|
||||
|
||||
# MoFin Dashboard 地址
|
||||
MOFIN_URL = "http://192.168.1.246:8899"
|
||||
|
||||
# 推送频率(秒)
|
||||
PUSH_INTERVAL = 15
|
||||
|
||||
# 港股列表(市场代码, 股票代码)
|
||||
# 港股主板 market_code = 31
|
||||
HK_STOCKS = [
|
||||
(31, "00700"), # 腾讯控股
|
||||
(31, "09988"), # 阿里巴巴-W
|
||||
(31, "00981"), # 中芯国际
|
||||
(31, "01211"), # 比亚迪股份
|
||||
(31, "01888"), # 建滔积层板
|
||||
(31, "02318"), # 中国平安
|
||||
(31, "02359"), # 药明康德
|
||||
(31, "02388"), # 中银香港
|
||||
(31, "02628"), # 中国人寿
|
||||
(31, "06160"), # 百济神州
|
||||
(31, "06869"), # 长飞光纤
|
||||
(31, "09868"), # 小鹏汽车-W
|
||||
(31, "01070"), # TCL电子
|
||||
(31, "01088"), # 中国神华
|
||||
(31, "00968"), # 信义光能
|
||||
(31, "02202"), # 万科企业
|
||||
(31, "01478"), # 丘钛科技
|
||||
]
|
||||
@@ -0,0 +1,40 @@
|
||||
"""数据推送器 — 推送实时行情到 MoFin Dashboard"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
|
||||
class MoFinPusher:
|
||||
"""推送实时行情到 MoFin Dashboard API"""
|
||||
|
||||
def __init__(self, base_url: str = "http://192.168.1.246:8899"):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.endpoint = f"{self.base_url}/api/update/realtime"
|
||||
|
||||
def push(self, stocks: list) -> dict:
|
||||
"""推送一批实时行情
|
||||
|
||||
Args:
|
||||
stocks: [{"code", "price", "change_pct", ...}, ...]
|
||||
|
||||
Returns:
|
||||
{"status": "ok", "updated": N, "timestamp": "..."}
|
||||
"""
|
||||
payload = json.dumps({
|
||||
"stocks": stocks,
|
||||
"source": "tdx_relay",
|
||||
}).encode("utf-8")
|
||||
|
||||
req = urllib.request.Request(
|
||||
self.endpoint,
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
except (urllib.error.URLError, urllib.error.HTTPError, OSError) as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
@@ -0,0 +1,62 @@
|
||||
"""TDX 行情客户端 — 连接通达信行情服务器获取实时数据
|
||||
|
||||
小小莫:
|
||||
1. pip install opentdx
|
||||
2. 在 config.py 中填入招商证券行情主站IP
|
||||
3. 实现下面的方法
|
||||
4. 运行 test_tdx_connect.py 验证
|
||||
"""
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 配置区 — 请替换为你的招商证券行情主站IP
|
||||
# ═══════════════════════════════════════════
|
||||
from .config import MARKET_SERVERS, HK_STOCKS
|
||||
|
||||
|
||||
class TDXClient:
|
||||
"""通达信行情客户端封装
|
||||
|
||||
用法:
|
||||
client = TDXClient()
|
||||
client.connect("113.105.73.88", 7709)
|
||||
data = client.get_hk_quotes()
|
||||
client.close()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = None
|
||||
|
||||
def connect(self, ip: str, port: int = 7709):
|
||||
"""连接到通达信行情服务器"""
|
||||
# TODO: 小小莫实现
|
||||
# from opentdx.client.macExtendedClient import MacExtendedClient
|
||||
# self.client = MacExtendedClient()
|
||||
# self.client.connect(ip, port)
|
||||
raise NotImplementedError("由小小莫实现")
|
||||
|
||||
def get_hk_quote(self, market_code: int, code: str) -> dict:
|
||||
"""获取单只港股实时报价
|
||||
|
||||
Args:
|
||||
market_code: 港股主板=31
|
||||
code: 如 "00700"
|
||||
|
||||
Returns:
|
||||
{"code": "00700", "name": "腾讯控股", "price": 463.6,
|
||||
"change_pct": 1.55, "high": 468.0, "low": 460.2, ...}
|
||||
"""
|
||||
raise NotImplementedError("由小小莫实现")
|
||||
|
||||
def get_hk_quotes(self, codes: list = None) -> list:
|
||||
"""批量获取港股实时报价"""
|
||||
if codes is None:
|
||||
codes = [(31, code) for _, code in HK_STOCKS]
|
||||
raise NotImplementedError("由小小莫实现")
|
||||
|
||||
def close(self):
|
||||
if self.client:
|
||||
try:
|
||||
self.client.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
self.client = None
|
||||
@@ -0,0 +1,23 @@
|
||||
"""MoFin API 推送测试"""
|
||||
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||
from relay.pusher import MoFinPusher
|
||||
|
||||
pusher = MoFinPusher()
|
||||
result = pusher.push([{
|
||||
"code": "00700",
|
||||
"name": "腾讯控股",
|
||||
"price": 463.6,
|
||||
"change_pct": 1.55,
|
||||
"high": 468.0,
|
||||
"low": 460.2,
|
||||
"open": 462.0,
|
||||
"volume": 25000000,
|
||||
"timestamp": "2026-06-12 14:30:00",
|
||||
}])
|
||||
print(f"推送结果: {result}")
|
||||
if result.get("status") == "ok":
|
||||
print("✅ 推送成功")
|
||||
else:
|
||||
print(f"❌ 失败: {result.get('message')}")
|
||||
@@ -0,0 +1,49 @@
|
||||
"""通达信连接测试 — 验证能否连上招商证券服务器
|
||||
|
||||
用法:
|
||||
python tests/test_tdx_connect.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||
|
||||
try:
|
||||
import opentdx
|
||||
print(f"✅ opentdx {opentdx.__version__}")
|
||||
except ImportError:
|
||||
print("❌ 请先 pip install opentdx")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def test_server(ip, port, name="未知"):
|
||||
print(f"\n🔄 {name}: {ip}:{port}")
|
||||
try:
|
||||
from opentdx.tdxClient import TdxClient
|
||||
from opentdx.const import EX_MARKET
|
||||
|
||||
with TdxClient() as client:
|
||||
# A股
|
||||
aq = client.stock_quotes([(0, '000001')])
|
||||
if aq:
|
||||
print(f" ✅ A股 平安银行: {aq[0].get('price', '?')}")
|
||||
|
||||
# 港股
|
||||
hk = client.goods_quotes([(EX_MARKET.HK_MAIN_BOARD, '00700')])
|
||||
if hk:
|
||||
q = hk[0]
|
||||
print(f" ✅ 港股 腾讯: {q.get('price', '?')} ({q.get('change_pct', '?')}%)")
|
||||
else:
|
||||
print(" ❌ 港股无数据")
|
||||
except Exception as e:
|
||||
print(f" ❌ {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from relay.config import MARKET_SERVERS
|
||||
if MARKET_SERVERS:
|
||||
for name, ip, port in MARKET_SERVERS:
|
||||
test_server(ip, port, name)
|
||||
else:
|
||||
print("⚠️ config.py 中 MARKET_SERVERS 为空")
|
||||
print("请在招商证券客户端 → 通信设置 查看IP后填入")
|
||||
Reference in New Issue
Block a user