- stale_push_wlin归档(price_monitor每2分钟监控所有持仓+自选的买入/止损/止盈区间,已完全覆盖) - market_config/mofin_health/signal_ledger注释引用更新 - jobs.json禁用stale_push_wlin cron job
200 lines
7.3 KiB
Python
200 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""market_config.py — 市场抽象层(阶段1:港股接入核心结构件)
|
||
|
||
市场判断 / 行情符号 / 市场配置的**唯一事实源**,收敛散落在
|
||
market_data / price_monitor / technical_analysis /
|
||
strategy_lab 中的 6 处重复实现。
|
||
|
||
铁律:
|
||
- 市场判断一律走 market_for_code(委托 mo_models.is_hk_stock,5位0/1开头=港股)
|
||
- 行情符号一律走 kline_symbol(统一 sh/sz/hk 前缀规则)
|
||
- 市场时段/币种/涨跌停配置一律从 MARKETS 读取
|
||
- mo_models 不依赖本模块(无循环导入);本模块只依赖 mo_models
|
||
"""
|
||
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
from mo_models import is_hk_stock
|
||
|
||
|
||
# ── 市场判断(唯一事实源:mo_models.is_hk_stock)────────────────────────
|
||
|
||
def market_for_code(code):
|
||
"""统一市场判断:返回 'a'(A股)| 'hk'(港股)。
|
||
|
||
规则与 mo_models.is_hk_stock 完全一致:5位数字且0/1开头 → hk,其余 → a。
|
||
6位A股、带前缀代码(sh/sz/hk/...)均正确归一。
|
||
"""
|
||
return 'hk' if is_hk_stock(code) else 'a'
|
||
|
||
|
||
# ── 行情符号映射(收敛 market_data/price_monitor/technical_analysis 三处)──
|
||
|
||
def _normalize_code(code):
|
||
"""归一化代码:去市场前缀(sh/sz/hk/SH...)与 '_' 后缀,返回大写纯代码"""
|
||
s = str(code or '').strip().upper()
|
||
for p in ('HK', 'SH', 'SZ', 'BJ'):
|
||
if s.startswith(p):
|
||
s = s[len(p):]
|
||
if '_' in s:
|
||
s = s.split('_')[0]
|
||
return s
|
||
|
||
|
||
def kline_symbol(code):
|
||
"""腾讯行情符号映射:'600519'→'sh600519', '000001'→'sz000001', '00700'→'hk00700'。
|
||
|
||
统一规则(收敛两个来源的规则差异):
|
||
- 5位数字 → hk
|
||
- 6位数字:5/6/9开头 → sh;0/1/2/3开头 → sz
|
||
(4/7/8开头按 sz 兜底——现实中无此类A股代码,price_monitor/technical_analysis
|
||
原实现即按 sz 处理)
|
||
非5/6位数字(空、字母、超长)→ None。
|
||
"""
|
||
raw = _normalize_code(code)
|
||
if not raw or not raw.isdigit():
|
||
return None
|
||
if len(raw) == 5:
|
||
return f"hk{raw}"
|
||
if len(raw) == 6:
|
||
if raw.startswith(('5', '6', '9')):
|
||
return f"sh{raw}"
|
||
return f"sz{raw}"
|
||
return None
|
||
|
||
|
||
# ── 市场配置 ────────────────────────────────────────────────────────────
|
||
|
||
MARKETS = {
|
||
'a': {
|
||
'index_code': 'sh000001', # A股大盘:上证指数
|
||
'currency': 'CNY',
|
||
'has_price_limit': True,
|
||
'trading_hours': [('09:30', '11:30'), ('13:00', '15:00')],
|
||
},
|
||
'hk': {
|
||
'index_code': 'hkHSI', # 港股大盘:恒生指数
|
||
'currency': 'HKD',
|
||
'has_price_limit': False,
|
||
'trading_hours': [('09:30', '12:00'), ('13:00', '16:00')], # 港股午休12:00-13:00
|
||
},
|
||
}
|
||
|
||
|
||
def market_config(market):
|
||
"""市场配置 dict:MARKETS[market](market 为 'a' 或 'hk')"""
|
||
return MARKETS[market]
|
||
|
||
|
||
def is_trading_now(market, dt=None):
|
||
"""是否在交易时段内:工作日(weekday<5)且当前时间落在任一交易时段内。
|
||
|
||
dt: datetime 对象,缺省取当前时间。
|
||
"""
|
||
if market not in MARKETS:
|
||
return False
|
||
dt = dt or datetime.now()
|
||
if dt.weekday() >= 5:
|
||
return False
|
||
t = dt.strftime('%H:%M')
|
||
for start, end in MARKETS[market]['trading_hours']:
|
||
if start <= t <= end:
|
||
return True
|
||
return False
|
||
|
||
|
||
def get_regime_temp(code):
|
||
"""按标的市场读取温区+温度(信号溯源用,2026-08-14 阶段4)。
|
||
|
||
A股:smoothed 顶层(get_current_regime,K=5 平滑温区)+ get_market_temp('a')
|
||
—— 与既有单市场行为完全一致。
|
||
港股:smoothed markets.hk(K=5 平滑温区)+ get_market_temp('hk')。
|
||
|
||
返回 (regime, temp_band),异常时 ("", "")。延迟 import 避免循环依赖。
|
||
"""
|
||
mkt = market_for_code(code)
|
||
regime, temp = "", ""
|
||
try:
|
||
if mkt == 'hk':
|
||
import json as _json
|
||
sp = Path(__file__).resolve().parent.parent.parent / "data" / "market_regime_smoothed.json"
|
||
if sp.exists():
|
||
d = _json.loads(sp.read_text(encoding="utf-8"))
|
||
mk = (d.get("markets") or {}).get("hk") or {}
|
||
regime = mk.get("current_regime", "")
|
||
temp = (mk.get("temp") or {}).get("band", "")
|
||
if not regime: # 回退:market_regime 表港股最新温区
|
||
import sqlite3 as _sq
|
||
from market_regime import DB_PATH as _DB
|
||
conn = _sq.connect(str(_DB), timeout=5)
|
||
row = conn.execute(
|
||
"SELECT regime FROM market_regime WHERE market='hk' ORDER BY date DESC LIMIT 1"
|
||
).fetchone()
|
||
conn.close()
|
||
regime = row[0] if row else ""
|
||
else:
|
||
from regime_gate import get_current_regime
|
||
regime = get_current_regime().get("regime", "")
|
||
from temp_band import get_market_temp
|
||
temp = get_market_temp(market='a').get("band", "")
|
||
except Exception:
|
||
pass
|
||
return regime, temp
|
||
|
||
|
||
# ── 模块自检 ────────────────────────────────────────────────────────────
|
||
|
||
if __name__ == '__main__':
|
||
cases = [
|
||
# (code, expected_market, expected_symbol)
|
||
('600519', 'a', 'sh600519'),
|
||
('000001', 'a', 'sz000001'),
|
||
('300750', 'a', 'sz300750'),
|
||
('688111', 'a', 'sh688111'),
|
||
('510050', 'a', 'sh510050'),
|
||
('002594', 'a', 'sz002594'),
|
||
('00700', 'hk', 'hk00700'),
|
||
('09988', 'hk', 'hk09988'),
|
||
('01810', 'hk', 'hk01810'),
|
||
('sh600519', 'a', 'sh600519'),
|
||
('hk00700', 'hk', 'hk00700'),
|
||
('AAPL', 'a', None),
|
||
('', 'a', None),
|
||
]
|
||
print("=== market_for_code / kline_symbol 测试 ===")
|
||
ok = True
|
||
for code, exp_m, exp_s in cases:
|
||
m = market_for_code(code)
|
||
s = kline_symbol(code)
|
||
m_ok = m == exp_m
|
||
s_ok = s == exp_s
|
||
if not (m_ok and s_ok):
|
||
ok = False
|
||
print(f" {'✅' if m_ok and s_ok else '❌'} {code!r:>10} → market={m!r}({exp_m!r}) symbol={s!r}({exp_s!r})")
|
||
|
||
print("\n=== is_trading_now 测试 ===")
|
||
from datetime import datetime as _dt
|
||
# 2026-08-14 是周五,10:00 在 A 股/港股早盘时段内
|
||
t1 = _dt(2026, 8, 14, 10, 0)
|
||
# 11:45 在港股早盘(9:30-12:00),但 A 股午休
|
||
t2 = _dt(2026, 8, 14, 11, 45)
|
||
# 12:30 港股午休
|
||
t3 = _dt(2026, 8, 14, 12, 30)
|
||
# 周六
|
||
t4 = _dt(2026, 8, 15, 10, 0)
|
||
for market, dt, exp in [
|
||
('a', t1, True), ('hk', t1, True),
|
||
('a', t2, False), ('hk', t2, True),
|
||
('hk', t3, False),
|
||
('a', t4, False),
|
||
]:
|
||
got = is_trading_now(market, dt)
|
||
status = '✅' if got == exp else '❌'
|
||
if got != exp:
|
||
ok = False
|
||
print(f" {status} is_trading_now({market!r}, {dt.strftime('%F %H:%M')}) = {got} (expected {exp})")
|
||
|
||
print(f"\n{'全部通过 ✅' if ok else '有失败 ❌'}")
|