K线数据首选mootdx通达信TCP源,板块数据增加mootdx降级

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-07-08 02:09:57 +08:00
co-authored by Claude Opus 4.7
parent 015aeb989a
commit 86535f5de2
4 changed files with 151 additions and 7 deletions
+128
View File
@@ -0,0 +1,128 @@
"""通达信数据源(mootdx TCP直连通达信服务器)
通过 TCP 协议直连通达信行情服务器,不走 HTTP,不会被限流。
主要用途:
- K线数据:主数据源(稳定可靠)
- 板块数据:东方财富 push2 的降级方案
"""
import asyncio
from typing import Optional, List
def _get_market(code: str) -> str:
return "sh" if code.startswith("6") else "sz"
def _create_client():
from mootdx.quotes import Quotes
return Quotes.factory(market="std", multithread=True, heartbeat=True)
def _sync_fetch_kline(code: str, days: int) -> Optional[list]:
client = _create_client()
klines = client.bars(symbol=code, frequency=9, offset=min(days, 800))
if klines is None or len(klines) == 0:
return None
result = []
prev_close = 0.0
for bar in reversed(klines):
close = float(bar.close)
change_pct = 0
if prev_close > 0:
change_pct = (close - prev_close) / prev_close * 100
result.append(
{
"date": (
bar.datetime.strftime("%Y-%m-%d")
if hasattr(bar.datetime, "strftime")
else str(bar.datetime)[:10]
),
"open": float(bar.open),
"close": close,
"high": float(bar.high),
"low": float(bar.low),
"volume": int(bar.vol) if hasattr(bar, "vol") else 0,
"turnover": (
float(bar.amount) if hasattr(bar, "amount") and bar.amount else 0
),
"changePercent": round(change_pct, 2),
}
)
prev_close = close
result.sort(key=lambda x: x["date"])
return result
async def fetch_kline_history(code: str, days: int = 90) -> Optional[List[dict]]:
"""获取日K线(TCP直连通达信,主数据源)"""
try:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, _sync_fetch_kline, code, days)
except ImportError:
return None
except Exception as e:
print(f"[mootdx] fetch_kline error: {e}")
return None
# ---- 板块数据(东方财富降级方案)----
def _sync_fetch_sectors(sector_type: str) -> Optional[list]:
from mootdx.consts import MARKET_SH, MARKET_SZ
client = _create_client()
# block() 返回 DataFrame,列:code, name 等
# 按板块类型过滤
block_df = client.block()
if block_df is None or block_df.empty:
return None
items = []
for _, row in block_df.iterrows():
name = str(row.get("name", "") or row.get("blockname", ""))
code = str(row.get("code", "") or row.get("blockcode", ""))
if not code or not name:
continue
items.append(
{
"code": code,
"name": name,
"level": None,
"changePercent": None,
"changeAmount": None,
"mainNetInflow": 0,
"mainNetInflowPercent": None,
"superLargeInflow": None,
"superLargeInflowPercent": None,
"largeInflow": None,
"largeInflowPercent": None,
"mediumInflow": None,
"mediumInflowPercent": None,
"smallInflow": None,
"smallInflowPercent": None,
"turnover": 0,
}
)
return items if items else None
async def fetch_sector_list(sector_type: str) -> Optional[List[dict]]:
"""获取板块列表(东方财富的降级方案,仅含代码和名称)"""
try:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, _sync_fetch_sectors, sector_type)
except ImportError:
return None
except Exception as e:
print(f"[mootdx] fetch_sectors error: {e}")
return None