a68cf4905e
把聚宽"全天候轮动"(post48819)搬到 BulletTrade。融合=pip+扩展点注入 (SanguoMiniQmtProvider 继承 MiniQMTProvider 只 override get_fundamentals, set_data_provider 公开 API 注入, BulletTrade 源码 0 改动)。 - providers: SanguoMiniQmtProvider 补 get_fundamentals(PershareIndex+自算PE/PS/PB/PCF/市值/ROIC) - strategies/all_weather: 4选股函数+大小盘轮动+ETF兜底+涨停止损(聚宽风格翻译) - factors(估值/ROIC自算) + filters(ST/涨跌停/次新/停牌) - 88/88 测试 Mac+VPS 双过; VPS 回测 pipeline 跑通(修9bug:Capital单位/日期格式/百分数口径/11字段alias) - 实盘 runner_live+runbook 就绪等交易日; DEFAULT_DATA_PROVIDER=miniqmt env 不装 jqdatasdk - 文档: sanguo_portfolio_plan / portfolio_backtest_result / portfolio_live_runbook
111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
"""全天候策略实盘入口(VPS Windows / miniQMT 直连)。
|
|
|
|
**实盘就绪,但需在交易日+miniQMT 连接下首次跑**。
|
|
|
|
用法:
|
|
set DEFAULT_DATA_PROVIDER=miniqmt
|
|
set MINIQMT_MARKET=SH
|
|
python -m sanguo_portfolio.runner_live
|
|
|
|
不在 Mac 跑(Mac 无 xtquant/miniQMT 客户端)。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
# ENV GUARD 必须早于任何 bullet_trade import
|
|
import os
|
|
os.environ.setdefault("DEFAULT_DATA_PROVIDER", "miniqmt")
|
|
|
|
import logging
|
|
from typing import Any, Dict
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def build_provider(provider_config: Dict[str, Any] | None = None) -> Any:
|
|
"""构造 live 模式的 SanguoMiniQmtProvider。"""
|
|
from .providers import SanguoMiniQmtProvider
|
|
|
|
cfg = dict(provider_config or {})
|
|
cfg.setdefault("mode", "live")
|
|
cfg.setdefault("auto_download", True)
|
|
return SanguoMiniQmtProvider(cfg)
|
|
|
|
|
|
def build_broker_facade() -> Any:
|
|
"""实盘 BrokerFacade:委托 bullet_trade 顶层聚宽风格 API + QmtBroker。"""
|
|
from .strategies.all_weather import BrokerFacade
|
|
from bullet_trade.core.api import ( # type: ignore
|
|
order_target_value as bt_otv,
|
|
order_value as bt_ov,
|
|
set_benchmark, set_option, set_slippage, set_order_cost,
|
|
run_daily, run_monthly,
|
|
)
|
|
|
|
return BrokerFacade(
|
|
order_target_value=lambda c, v: bt_otv(c, v),
|
|
order_value=lambda c, v: bt_ov(c, v),
|
|
set_benchmark=set_benchmark,
|
|
set_option=set_option,
|
|
set_slippage=set_slippage,
|
|
set_order_cost=set_order_cost,
|
|
run_daily=run_daily,
|
|
run_monthly=run_monthly,
|
|
)
|
|
|
|
|
|
def run_live(provider_config: Dict[str, Any] | None = None) -> None:
|
|
"""启动 LiveEngine + AllWeatherStrategy。
|
|
|
|
LiveEngine 负责驱动 scheduler(context.current_dt 推进)和下单路由。
|
|
我们负责把 provider/broker 注入 AllWeatherStrategy。
|
|
"""
|
|
from bullet_trade.core.live_engine import LiveEngine # type: ignore
|
|
from bullet_trade.data.api import set_data_provider # type: ignore
|
|
from bullet_trade.broker.qmt import QmtBroker # type: ignore
|
|
|
|
from .strategies import AllWeatherConfig, AllWeatherStrategy
|
|
|
|
provider = build_provider(provider_config)
|
|
set_data_provider(provider)
|
|
|
|
broker_facade = build_broker_facade()
|
|
strategy = AllWeatherStrategy(
|
|
provider=provider,
|
|
broker=broker_facade,
|
|
config=AllWeatherConfig(),
|
|
)
|
|
|
|
def initialize(context):
|
|
strategy.initialize(context)
|
|
|
|
# QmtBroker 需要 account_id + data_path(miniQMT userdata_mini)。
|
|
# 从 env 读(与 sanguo_live 约定一致),缺 account 拒绝启动避免误下单。
|
|
account = os.environ.get("SANGUO_QMT_ACCOUNT", "")
|
|
mini_path = (os.environ.get("SANGUO_QMT_PATH")
|
|
or r"C:\国金QMT交易端模拟\userdata_mini")
|
|
if not account:
|
|
raise RuntimeError(
|
|
"缺 SANGUO_QMT_ACCOUNT(miniQMT 交易账号),实盘无法启动。"
|
|
"设 set SANGUO_QMT_ACCOUNT=66639661 后重试。"
|
|
)
|
|
broker = QmtBroker(account_id=account, data_path=mini_path)
|
|
logger.info("QmtBroker 装配 account=%s data_path=%s", account, mini_path)
|
|
|
|
# 小资金 1e6 起步,等交易日观察(9:05 prepare / 月初 9:30 monthly / 14:00 stop_loss)
|
|
engine = LiveEngine(
|
|
initialize=initialize,
|
|
initial_cash=1_000_000.0,
|
|
broker=broker,
|
|
)
|
|
logger.info("AllWeather live engine 启动,等交易日触发 monthly_adjustment")
|
|
engine.run()
|
|
|
|
|
|
def main() -> None:
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
run_live()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|