182 lines
6.1 KiB
Python
182 lines
6.1 KiB
Python
# coding:utf-8
|
||
import time, datetime, traceback, sys, json
|
||
import redis
|
||
from xtquant import xtdata
|
||
from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback
|
||
from xtquant.xttype import StockAccount
|
||
from xtquant import xtconstant
|
||
|
||
# ================= 配置区域 =================
|
||
QMT_PATH = r'D:\qmt\投研\迅投极速交易终端睿智融科版\userdata'
|
||
ACCOUNT_ID = '2000128'
|
||
ACCOUNT_TYPE = 'STOCK'
|
||
|
||
REDIS_HOST = '127.0.0.1'
|
||
REDIS_PORT = 6379
|
||
REDIS_PASS = None
|
||
|
||
# 策略基础名称 (不需要加 _real,代码会自动加)
|
||
STRATEGY_BASE_NAME = 'default_strategy'
|
||
# ===========================================
|
||
|
||
# 定义监听的队列名称 (只监听实盘队列,物理屏蔽回测数据)
|
||
LISTEN_QUEUE = f"{STRATEGY_BASE_NAME}_real"
|
||
|
||
|
||
class MyXtQuantTraderCallback(XtQuantTraderCallback):
|
||
def on_disconnected(self):
|
||
print("连接断开")
|
||
|
||
def on_stock_order(self, order):
|
||
print(f"委托回报: {order.order_id} {order.order_remark}")
|
||
|
||
def on_stock_trade(self, trade):
|
||
print(f"成交: {trade.stock_code} {trade.traded_volume}")
|
||
|
||
def on_order_error(self, order_error):
|
||
print(f"下单失败: {order_error.error_msg}")
|
||
|
||
|
||
def init_redis():
|
||
try:
|
||
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASS, decode_responses=True)
|
||
r.ping()
|
||
return r
|
||
except Exception as e:
|
||
print(f"Redis连接失败: {e}")
|
||
return None
|
||
|
||
|
||
def is_msg_valid(data):
|
||
"""
|
||
【安全核心】校验消息时效性与合法性
|
||
"""
|
||
try:
|
||
# 1. 检查是否为回测标记 (防御性编程,虽然队列已物理隔离)
|
||
if data.get('is_backtest', False):
|
||
print(f"警报:拦截到回测数据,已丢弃!")
|
||
return False
|
||
|
||
# 2. 检查时间戳
|
||
msg_time_str = data.get('timestamp')
|
||
if not msg_time_str:
|
||
print("数据缺失时间戳,丢弃")
|
||
return False
|
||
|
||
# 解析消息时间
|
||
# 格式必须匹配策略端发送的 '%Y-%m-%d %H:%M:%S'
|
||
msg_dt = datetime.datetime.strptime(msg_time_str, '%Y-%m-%d %H:%M:%S')
|
||
msg_date = msg_dt.date()
|
||
|
||
# 获取当前服务器日期
|
||
today = datetime.date.today()
|
||
|
||
# 3. 【核心】判断是否为当天的消息
|
||
if msg_date != today:
|
||
print(f"拦截过期消息: 消息日期[{msg_date}] != 今日[{today}]")
|
||
return False
|
||
|
||
# 可选:如果你想更严格,可以判断时间差不能超过5分钟
|
||
# delta = datetime.datetime.now() - msg_dt
|
||
# if abs(delta.total_seconds()) > 300: ...
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"校验逻辑异常: {e}")
|
||
return False
|
||
|
||
|
||
def process_redis_signal(r_client, xt_trader, acc):
|
||
try:
|
||
msg_json = r_client.lpop(LISTEN_QUEUE)
|
||
if not msg_json: return
|
||
|
||
print(f"收到信号: {msg_json}")
|
||
data = json.loads(msg_json)
|
||
|
||
if not is_msg_valid(data): return # 之前的校验逻辑
|
||
|
||
stock_code = data['stock_code']
|
||
action = data['action']
|
||
price = float(data['price'])
|
||
|
||
# 获取切分份数
|
||
# 兼容性处理:如果redis里还是旧key 'weight',也可以尝试获取
|
||
div_count = float(data.get('div_count', data.get('weight', 1)))
|
||
|
||
# =========================================================
|
||
# 买入逻辑:资金切片法
|
||
# =========================================================
|
||
if action == 'BUY':
|
||
# 1. 必须查最新的可用资金 (Available Cash)
|
||
asset = xt_trader.query_stock_asset(acc)
|
||
if not asset:
|
||
print("错误:无法查询资产")
|
||
return
|
||
|
||
current_cash = asset.cash
|
||
|
||
# 2. 计算下单金额
|
||
# 逻辑:Amount = Cash / div_count
|
||
if div_count <= 0: div_count = 1 # 防止除0
|
||
|
||
target_amount = current_cash / div_count
|
||
|
||
# 3. 打印调试信息 (非常重要)
|
||
print(f"【资金分配】可用现金:{current_cash:.2f} / 切分份数:{div_count} = 下单金额:{target_amount:.2f}")
|
||
|
||
# 4. 计算股数
|
||
if price <= 0: price = 1.0
|
||
|
||
# 过滤小额杂单
|
||
if target_amount < 2000:
|
||
print(f"忽略:金额过小 ({target_amount:.2f})")
|
||
return
|
||
|
||
vol = int(target_amount / price / 100) * 100
|
||
|
||
if vol >= 100:
|
||
xt_trader.order_stock(acc, stock_code, xtconstant.STOCK_BUY, vol, xtconstant.FIX_PRICE, price,
|
||
STRATEGY_BASE_NAME, 'PyBuy')
|
||
print(f"买入下单: {stock_code} {vol}股")
|
||
else:
|
||
print(f"计算股数不足100股")
|
||
|
||
# =========================================================
|
||
# 卖出逻辑 (清仓)
|
||
# =========================================================
|
||
elif action == 'SELL':
|
||
positions = xt_trader.query_stock_positions(acc)
|
||
target_pos = next((p for p in positions if p.stock_code == stock_code), None)
|
||
|
||
if target_pos and target_pos.can_use_volume > 0:
|
||
xt_trader.order_stock(acc, stock_code, xtconstant.STOCK_SELL, target_pos.can_use_volume,
|
||
xtconstant.FIX_PRICE, price, STRATEGY_BASE_NAME, 'PySell')
|
||
print(f"卖出下单: {stock_code} {target_pos.can_use_volume}股")
|
||
else:
|
||
print(f"无可用持仓: {stock_code}")
|
||
|
||
except Exception as e:
|
||
print(f"处理异常: {e}")
|
||
traceback.print_exc()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
r_client = init_redis()
|
||
session_id = int(time.time())
|
||
xt_trader = XtQuantTrader(QMT_PATH, session_id)
|
||
acc = StockAccount(ACCOUNT_ID, ACCOUNT_TYPE)
|
||
callback = MyXtQuantTraderCallback()
|
||
xt_trader.register_callback(callback)
|
||
xt_trader.start()
|
||
xt_trader.connect()
|
||
xt_trader.subscribe(acc)
|
||
|
||
print(f"=== 启动监听: {LISTEN_QUEUE} ===")
|
||
print("只处理当日的实盘/模拟信号,自动过滤回测数据及历史遗留数据。")
|
||
|
||
while True:
|
||
if r_client:
|
||
process_redis_signal(r_client, xt_trader, acc)
|
||
time.sleep(60) |