101 lines
3.9 KiB
Python
101 lines
3.9 KiB
Python
|
|
import redis
|
|||
|
|
import json
|
|||
|
|
import datetime
|
|||
|
|
|
|||
|
|
# --- 模块级全局变量 ---
|
|||
|
|
_BACKTEST_SEND_COUNT = 0
|
|||
|
|
|
|||
|
|
def send_qmt_signal(code, target_total_slots, price, context, redis_config):
|
|||
|
|
"""
|
|||
|
|
发送信号到 Redis (基于槽位状态判断买卖意图)
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
- code: 股票代码 (聚宽格式: 000001.XSHE)
|
|||
|
|
- target_total_slots:
|
|||
|
|
* 意向持仓时: 传入策略设定的总槽位数 (例如 5)。此时 action 判定为 BUY。
|
|||
|
|
* 意向清仓时: 传入 0。此时 action 判定为 SELL。
|
|||
|
|
- price: 当前最新价格 (用于实盘限价单参考)
|
|||
|
|
- context: 聚宽上下文对象
|
|||
|
|
- redis_config: Redis配置字典
|
|||
|
|
"""
|
|||
|
|
global _BACKTEST_SEND_COUNT
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
# 1. 环境判断与流量控制
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
run_type = context.run_params.type
|
|||
|
|
is_backtest = run_type in ['simple_backtest', 'full_backtest']
|
|||
|
|
|
|||
|
|
if is_backtest:
|
|||
|
|
if _BACKTEST_SEND_COUNT >= 10:
|
|||
|
|
return
|
|||
|
|
_BACKTEST_SEND_COUNT += 1
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
# 2. 建立 Redis 连接
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
r = redis.Redis(
|
|||
|
|
host=redis_config['host'],
|
|||
|
|
port=redis_config['port'],
|
|||
|
|
password=redis_config.get('password'),
|
|||
|
|
db=redis_config.get('db', 0),
|
|||
|
|
decode_responses=True,
|
|||
|
|
socket_timeout=1
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
# 3. 数据转换与规范化
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
# 股票代码格式转换: 聚宽(.XSHE/.XSHG) -> QMT(.SZ/.SH)
|
|||
|
|
qmt_code = code
|
|||
|
|
if code.endswith('.XSHE'):
|
|||
|
|
qmt_code = code.replace('.XSHE', '.SZ')
|
|||
|
|
elif code.endswith('.XSHG'):
|
|||
|
|
qmt_code = code.replace('.XSHG', '.SH')
|
|||
|
|
|
|||
|
|
# 【核心逻辑修改】:根据 target_total_slots 判断动作
|
|||
|
|
# 不再通过函数名判断,而是看目标状态
|
|||
|
|
if target_total_slots > 0:
|
|||
|
|
action = 'BUY'
|
|||
|
|
slots_val = int(target_total_slots) # 告知后端:我是基于“N只模型”中的一只
|
|||
|
|
else:
|
|||
|
|
action = 'SELL'
|
|||
|
|
slots_val = 0 # 清仓
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
# 4. 构建消息体
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
base_strategy_name = redis_config.get('strategy_name', 'default_strategy')
|
|||
|
|
ts_str = context.current_dt.strftime('%Y-%m-%d %H:%M:%S')
|
|||
|
|
|
|||
|
|
msg = {
|
|||
|
|
'strategy_name': base_strategy_name,
|
|||
|
|
'stock_code': qmt_code,
|
|||
|
|
'action': action,
|
|||
|
|
'price': price,
|
|||
|
|
'total_slots': slots_val,
|
|||
|
|
'timestamp': ts_str,
|
|||
|
|
'is_backtest': is_backtest
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
json_payload = json.dumps(msg)
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
# 5. 队列路由
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
queue_key = f"{base_strategy_name}_backtest" if is_backtest else f"{base_strategy_name}_real"
|
|||
|
|
expire_seconds = 3600 if is_backtest else 604800
|
|||
|
|
|
|||
|
|
r.rpush(queue_key, json_payload)
|
|||
|
|
r.expire(queue_key, expire_seconds)
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
# 6. 控制台输出
|
|||
|
|
# ---------------------------------------------------------
|
|||
|
|
log_prefix = "【回测】" if is_backtest else "【实盘】"
|
|||
|
|
desc = f"目标总持仓:{slots_val}只" if action == 'BUY' else "清仓释放槽位"
|
|||
|
|
print(f"{log_prefix} 信号同步 -> {qmt_code} | 动作:{action} | {desc} | 时间:{ts_str}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"[Error] 发送QMT信号失败: {e}")
|