Files
2025-05-09 21:02:48 +08:00

2377 lines
111 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'''
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
该代码的主要目的是处理Tick数据并生成交易信号。代码中定义了一个tickcome函数它接收到Tick数据后会进行一系列的处理包括构建Tick字典、更新上一个Tick的成交量、保存Tick数据、生成K线数据等。其中涉及到的一些函数有
on_tick(tick): 处理单个Tick数据根据Tick数据生成K线数据。
tickdata(df, symbol): 处理Tick数据生成K线数据。
orderflow_df_new(df_tick, df_min, symbol): 处理Tick和K线数据生成订单流数据。
GetOrderFlow_dj(kData): 计算订单流的信号指标。
除此之外代码中还定义了一个MyTrader类继承自TraderApiBase用于实现交易相关的功能。
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
'''
from multiprocessing import Process, Queue
from AlgoPlus.CTP.MdApi import run_tick_engine
from AlgoPlus.CTP.FutureAccount import get_simulate_account
from AlgoPlus.CTP.FutureAccount import FutureAccount
from AlgoPlus.CTP.TraderApiBase import TraderApiBase
from AlgoPlus.ta.time_bar import tick_to_bar
import pandas as pd
from datetime import datetime
from datetime import time as s_time
import operator
import time
import numpy as np
import os
import re
import json
import requests # 添加requests库用于API调用
from openai import OpenAI # 导入OpenAI客户端
import threading
from queue import Queue as ThreadQueue
import warnings
warnings.filterwarnings("ignore", category=FutureWarning)
import copy
import math
import re # 新增
import logging # 新增
from logging.handlers import RotatingFileHandler # 新增
# 在文件顶部定义全局变量
tickdatadict = {} # 存储Tick数据的字典
quotedict = {} # 存储行情数据的字典
ofdatadict = {} # 存储K线数据的字典
trader_df = pd.DataFrame({}) # 存储交易数据的DataFrame对象
previous_volume = {} # 上一个Tick的成交量
tsymbollist={}
# 全局LLM配置变量
GLOBAL_LLM_CONFIG = {} # 全局大模型配置参数
# 全局交易信号队列用于存储AI模型生成的交易信号
AI_SIGNAL_QUEUE = ThreadQueue()
# 标记是否有AI线程正在运行
AI_THREAD_RUNNING = False
# 不再需要单独的K线时间粒度全局变量它已经被整合到GLOBAL_LLM_CONFIG中
# 设置日志系统
def setup_logging(log_folder='logs'):
# 确保日志文件夹存在
if not os.path.exists(log_folder):
os.makedirs(log_folder)
# 创建日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 设置AI分析日志
ai_logger = logging.getLogger('ai_analysis')
ai_logger.setLevel(logging.INFO)
# 设置带有文件循环的文件处理器
ai_file_handler = RotatingFileHandler(
os.path.join(log_folder, 'ai_analysis.log'),
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
ai_file_handler.setFormatter(formatter)
ai_logger.addHandler(ai_file_handler)
# 设置交易信号日志
signal_logger = logging.getLogger('trading_signals')
signal_logger.setLevel(logging.INFO)
signal_file_handler = RotatingFileHandler(
os.path.join(log_folder, 'trading_signals.log'),
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
signal_file_handler.setFormatter(formatter)
signal_logger.addHandler(signal_file_handler)
# 设置止损止盈日志
stoploss_logger = logging.getLogger('stoploss')
stoploss_logger.setLevel(logging.INFO)
stoploss_file_handler = RotatingFileHandler(
os.path.join(log_folder, 'stoploss.log'),
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
stoploss_file_handler.setFormatter(formatter)
stoploss_logger.addHandler(stoploss_file_handler)
# 返回三个日志记录器
return ai_logger, signal_logger, stoploss_logger
# 全局日志记录器
AI_LOGGER, SIGNAL_LOGGER, STOPLOSS_LOGGER = setup_logging()
def tickcome(md_queue):
global previous_volume
data=md_queue
instrument_id = data['InstrumentID'].decode() # 品种代码
ActionDay = data['ActionDay'].decode() # 交易日日期
update_time = data['UpdateTime'].decode() # 更新时间
#zzg_quant
update_millisec = str(data['UpdateMillisec']) # 更新毫秒数
created_at = ActionDay[:4] + '-' + ActionDay[4:6] + '-' + ActionDay[6:] + ' ' + update_time + '.' + update_millisec #创建时间
# 构建tick字典
tick = {
'symbol': instrument_id, # 品种代码和交易所ID
'created_at':datetime.strptime(created_at, "%Y-%m-%d %H:%M:%S.%f"),
#'created_at': created_at, # 创建时间
'price': float(data['LastPrice']), # 最新价
'last_volume': int(data['Volume']) - previous_volume.get(instrument_id, 0) if previous_volume.get(instrument_id, 0) != 0 else 0, # 瞬时成交量
'bid_p': float(data['BidPrice1']), # 买价
'bid_v': int(data['BidVolume1']), # 买量
'ask_p': float(data['AskPrice1']), # 卖价
'ask_v': int(data['AskVolume1']), # 卖量
'UpperLimitPrice': float(data['UpperLimitPrice']), # 涨停价
'LowerLimitPrice': float(data['LowerLimitPrice']), # 跌停价
'TradingDay': data['TradingDay'].decode(), # 交易日日期
'cum_volume': int(data['Volume']), # 最新总成交量
'cum_amount': float(data['Turnover']), # 最新总成交额
'cum_position': int(data['OpenInterest']), # 合约持仓量
}
# 更新上一个Tick的成交量
previous_volume[instrument_id] = int(data['Volume'])
if tick['last_volume']>0:
#print(tick['created_at'],'vol:',tick['last_volume'])
# 处理Tick数据
on_tick(tick)
def can_time(hour, minute):
hour = str(hour)
minute = str(minute)
if len(minute) == 1:
minute = "0" + minute
return int(hour + minute)
def on_tick(tick):
tm=can_time(tick['created_at'].hour,tick['created_at'].minute)
#print(tick['symbol'])
#print(1)
#if tm>1500 and tm<2100 :
# return
if tick['last_volume']==0:
return
quotes = tick
timetick=str(tick['created_at']).replace('+08:00', '')
tsymbol=tick['symbol']
if tsymbol not in tsymbollist.keys():
# 获取tick的买卖价和买卖量
#zzg_quant
tsymbollist[tsymbol]=tick
bid_p=quotes['bid_p']
ask_p=quotes['ask_p']
bid_v=quotes['bid_v']
ask_v=quotes['ask_v']
else:
# 获取上一个tick的买卖价和买卖量
rquotes =tsymbollist[tsymbol]
bid_p=rquotes['bid_p']
ask_p=rquotes['ask_p']
bid_v=rquotes['bid_v']
ask_v=rquotes['ask_v']
tsymbollist[tsymbol]=tick
tick_dt=pd.DataFrame({'datetime':timetick,'symbol':tick['symbol'],'mainsym':tick['symbol'].rstrip('0123456789').upper(),'lastprice':tick['price'],
'vol':tick['last_volume'],
'bid_p':bid_p,'ask_p':ask_p,'bid_v':bid_v,'ask_v':ask_v},index=[0])
sym = tick_dt['symbol'][0]
#print(tick_dt)
# 直接调用tickdata不传递resample_rule参数让其自行从全局配置获取
tickdata(tick_dt, sym)
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
def data_of(df):
global trader_df
# 将df数据合并到trader_df中
trader_df = pd.concat([trader_df, df], ignore_index=True)
#print('trader_df: ', len(trader_df))
#print(trader_df)
def process(bidDict, askDict, symbol):
try:
# 尝试从quotedict中获取对应品种的报价数据
dic = quotedict[symbol]
bidDictResult = dic['bidDictResult']
askDictResult = dic['askDictResult']
except:
# 如果获取失败则初始化bidDictResult和askDictResult为空字典
bidDictResult, askDictResult = {}, {}
# 将所有买盘字典和卖盘字典的key合并并按升序排序
sList = sorted(set(list(bidDict.keys()) + list(askDict.keys())))
# 遍历所有的key将相同key的值进行累加
for s in sList:
if s in bidDict:
if s in bidDictResult:
bidDictResult[s] = int(bidDict[s]) + bidDictResult[s]
else:
bidDictResult[s] = int(bidDict[s])
if s not in askDictResult:
askDictResult[s] = 0
else:
if s in askDictResult:
askDictResult[s] = int(askDict[s]) + askDictResult[s]
else:
askDictResult[s] = int(askDict[s])
if s not in bidDictResult:
bidDictResult[s] = 0
# 构建包含bidDictResult和askDictResult的字典并存入quotedict中
df = {'bidDictResult': bidDictResult, 'askDictResult': askDictResult}
quotedict[symbol] = df
return bidDictResult, askDictResult
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
def tickdata(df, symbol):
tickdata =pd.DataFrame({'datetime':df['datetime'],'symbol':df['symbol'],'lastprice':df['lastprice'],
'volume':df['vol'],'bid_p':df['bid_p'],'bid_v':df['bid_v'],'ask_p':df['ask_p'],'ask_v':df['ask_v']})
try:
if symbol in tickdatadict.keys():
rdf=tickdatadict[symbol]
rdftm=pd.to_datetime(rdf['bartime'][0]).strftime('%Y-%m-%d %H:%M:%S')
now=str(tickdata['datetime'][0])
if now>rdftm:
try:
oo=ofdatadict[symbol]
data_of(oo)
#print('oo',oo)
if symbol in quotedict.keys():
quotedict.pop(symbol)
if symbol in tickdatadict.keys():
tickdatadict.pop(symbol)
if symbol in ofdatadict.keys():
ofdatadict.pop(symbol)
except IOError as e:
print('rdftm捕获到异常',e)
tickdata['bartime'] = pd.to_datetime(tickdata['datetime'])
tickdata['open'] = tickdata['lastprice']
tickdata['high'] = tickdata['lastprice']
tickdata['low'] = tickdata['lastprice']
tickdata['close'] = tickdata['lastprice']
tickdata['starttime'] = tickdata['datetime']
else:
tickdata['bartime'] = rdf['bartime']
tickdata['open'] = rdf['open']
tickdata['high'] = max(tickdata['lastprice'].values,rdf['high'].values)
tickdata['low'] = min(tickdata['lastprice'].values,rdf['low'].values)
tickdata['close'] = tickdata['lastprice']
tickdata['volume']=df['vol']+rdf['volume'].values
tickdata['starttime'] = rdf['starttime']
else :
print('新bar的第一个tick进入')
tickdata['bartime'] = pd.to_datetime(tickdata['datetime'])
tickdata['open'] = tickdata['lastprice']
tickdata['high'] = tickdata['lastprice']
tickdata['low'] = tickdata['lastprice']
tickdata['close'] = tickdata['lastprice']
tickdata['starttime'] = tickdata['datetime']
except IOError as e:
print('捕获到异常',e)
tickdata['bartime'] = pd.to_datetime(tickdata['bartime'])
# 直接从全局配置获取resample_rule
resample_rule = GLOBAL_LLM_CONFIG.get('bar_resample_rule')
# 使用resample_rule生成K线
bardata = tickdata.resample(on = 'bartime', rule = resample_rule, label = 'right', closed = 'right').agg({'starttime':'first','symbol':'last','open':'first','high':'max','low':'min','close':'last','volume':'sum'}).reset_index(drop = False)
bardata =bardata.dropna().reset_index(drop = True)
bardata['bartime'] = pd.to_datetime(bardata['bartime'][0]).strftime('%Y-%m-%d %H:%M:%S')
tickdatadict[symbol]=bardata
tickdata['volume']=df['vol'].values
#print(bardata['symbol'].values,bardata['bartime'].values)
orderflow_df_new(tickdata,bardata,symbol)
# time.sleep(0.5)
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
def orderflow_df_new(df_tick,df_min,symbol):
startArray = pd.to_datetime(df_min['starttime']).values
voluememin= df_min['volume'].values
highs=df_min['high'].values
lows=df_min['low'].values
opens=df_min['open'].values
closes=df_min['close'].values
#endArray = pd.to_datetime(df_min['bartime']).values
endArray = df_min['bartime'].values
#print(endArray)
deltaArray = np.zeros((len(endArray),))
tTickArray = pd.to_datetime(df_tick['datetime']).values
bp1TickArray = df_tick['bid_p'].values
ap1TickArray = df_tick['ask_p'].values
lastTickArray = df_tick['lastprice'].values
volumeTickArray = df_tick['volume'].values
symbolarray = df_tick['symbol'].values
indexFinal = 0
for index,tEnd in enumerate(endArray):
dt=endArray[index]
start = startArray[index]
bidDict = {}
askDict = {}
bar_vol=voluememin[index]
bar_close=closes[index]
bar_open=opens[index]
bar_low=lows[index]
bar_high=highs[index]
bar_symbol=symbolarray[index]
# for indexTick in range(indexFinal,len(df_tick)):
# if tTickArray[indexTick] >= tEnd:
# break
# elif (tTickArray[indexTick] >= start) & (tTickArray[indexTick] < tEnd):
Bp = round(bp1TickArray[0],4)
Ap = round(ap1TickArray[0],4)
LastPrice = round(lastTickArray[0],4)
Volume = volumeTickArray[0]
if LastPrice >= Ap:
if str(LastPrice) in askDict.keys():
askDict[str(LastPrice)] += Volume
else:
askDict[str(LastPrice)] = Volume
if LastPrice <= Bp:
if str(LastPrice) in bidDict.keys():
bidDict[str(LastPrice)] += Volume
else:
bidDict[str(LastPrice)] = Volume
# indexFinal = indexTick
bidDictResult,askDictResult = process(bidDict,askDict,symbol)
bidDictResult=dict(sorted(bidDictResult.items(),key=operator.itemgetter(0)))
askDictResult=dict(sorted(askDictResult.items(),key=operator.itemgetter(0)))
prinslist=list(bidDictResult.keys())
asklist=list(askDictResult.values())
bidlist=list(bidDictResult.values())
delta=(sum(askDictResult.values()) - sum(bidDictResult.values()))
#print(prinslist,asklist,bidlist)
#print(len(prinslist),len(bidDictResult),len(askDictResult))
df=pd.DataFrame({'price':pd.Series([prinslist]),'Ask':pd.Series([asklist]),'Bid':pd.Series([bidlist])})
#df=pd.DataFrame({'price':pd.Series(bidDictResult.keys()),'Ask':pd.Series(askDictResult.values()),'Bid':pd.Series(bidDictResult.values())})
df['symbol']=bar_symbol
df['datetime']=dt
df['delta']=str(delta)
df['close']=bar_close
df['open']=bar_open
df['high']=bar_high
df['low']=bar_low
df['volume']=bar_vol
#df['ticktime']=tTickArray[0]
df['dj'] = GetOrderFlow_dj(df)
ofdatadict[symbol]=df
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
def GetOrderFlow_dj(kData):
Config = {
'Value1': 3,
'Value2': 3,
'Value3': 3,
'Value4': True,
}
aryData = kData
djcout = 0
# 遍历kData中的每一行计算djcout指标
for index, row in aryData.iterrows():
kItem = aryData.iloc[index]
high = kItem['high']
low = kItem['low']
close = kItem['close']
open = kItem['open']
dtime = kItem['datetime']
price_s = kItem['price']
Ask_s = kItem['Ask']
Bid_s = kItem['Bid']
delta = kItem['delta']
price_s = price_s
Ask_s = Ask_s
Bid_s = Bid_s
gj = 0
xq = 0
gxx = 0
xxx = 0
# 遍历price_s中的每一个元素计算相关指标
for i in np.arange(0, len(price_s), 1):
duiji = {
'price': 0,
'time': 0,
'longshort': 0,
}
if i == 0:
delta = delta
order= {
"Price":price_s[i],
"Bid":{ "Value":Bid_s[i]},
"Ask":{ "Value":Ask_s[i]}
}
#空头堆积
if i >= 0 and i < len(price_s) - 1:
if (order["Bid"]["Value"] > Ask_s[i + 1] * int(Config['Value1'])):
gxx += 1
gj += 1
if gj >= int(Config['Value2']) and Config['Value4'] == True:
duiji['price'] = price_s[i]
duiji['time'] = dtime
duiji['longshort'] = -1
if float(duiji['price']) > 0:
djcout += -1
else:
gj = 0
#多头堆积
if i >= 1 and i <= len(price_s) - 1:
if (order["Ask"]["Value"] > Bid_s[i - 1] * int(Config['Value1'])):
xq += 1
xxx += 1
if xq >= int(Config['Value2']) and Config['Value4'] == True:
duiji['price'] = price_s[i]
duiji['time'] = dtime
duiji['longshort'] = 1
if float(duiji['price']) > 0:
djcout += 1
else:
xq = 0
# 返回计算得到的djcout值
return djcout
#交易程序---------------------------------------------------------------------------------------------------------------------------------------------------------------------
class MyTrader(TraderApiBase):
def __init__(self, broker_id, td_server, investor_id, password, app_id, auth_code, md_queue=None, page_dir='', private_resume_type=2, public_resume_type=2):
# Cython类不使用super().__init__()方式调用父类初始化方法
# TraderApiBase.__init__会由Cython自动处理
self.py=30 #设置委托价格的偏移,更加容易促成成交。仅螺纹,其他品种根据最小点波动,自己设置
self.cont_df=0
self.trailing_stop_percent = 0.005 #跟踪出场参数从0.02减小到0.005
self.fixed_stop_loss_percent = 0.01 #固定出场参数
self.dj_X=1 #开仓的堆积参数
self.pos=0
self.Lots=1 #下单手数
self.short_trailing_stop_price = 0
self.long_trailing_stop_price = 0
self.sl_long_price=0
self.sl_shor_price=0
self.out_long=0
self.out_short=0
self.clearing_executed=False
self.day_closed = False # 添加日内平仓标志
self.kgdata = True #历史数据加载一次
self.holddata=True #持仓数据加载一次
self.use_ai_model = True # 是否使用AI模型来分析
# 新增止盈止损字典按合约ID索引
self.stop_order_dict = {}
# 新增历史数据加载相关参数
self.load_history = False # 是否加载历史数据
self.history_rows = 30 # 默认加载30行历史数据
self.trader_rows = 10 # 当tader_df里的数据大于10行时开始计算指标及触发AI模型
def load_history_data(self, instrument_id):
"""
加载历史数据
Args:
instrument_id: 合约ID
"""
if not self.load_history:
return
try:
# 不再只提取英文字母,使用完整的合约代码
symbol = str(instrument_id)
json_file_path = f"traderdata/{symbol}_ofdata.json"
# 检查traderdata目录是否存在
if not os.path.exists("traderdata"):
print(f"traderdata目录不存在创建目录")
os.makedirs("traderdata")
if os.path.exists(json_file_path):
try:
# 读取JSON文件使用lines=True确保正确读取每行JSON
df = pd.read_json(json_file_path, lines=True)
if len(df) == 0:
print(f"历史数据文件为空: {json_file_path}")
print("将使用实时数据开始交易")
return False
# 如果数据行数超过设定的历史行数只取最后N行
if len(df) > self.history_rows:
df = df.tail(self.history_rows)
print(f"历史数据超过设定行数,仅加载最后{self.history_rows}")
# 更新全局trader_df
global trader_df
trader_df = df
print(f"\n===== 历史数据加载成功 =====")
print(f"合约: {instrument_id}")
print(f"数据行数: {len(df)}")
print(f"数据时间范围: {df['datetime'].iloc[0]}{df['datetime'].iloc[-1]}")
print(f"最新价格: {df['close'].iloc[-1]}")
print(f"最新成交量: {df['volume'].iloc[-1]}")
print("===========================\n")
# 更新cont_df
self.cont_df = len(df)
# 计算日均线
if len(df) > 0:
df['dayma'] = df['close'].mean()
# 计算累积的delta值
df['delta'] = df['delta'].astype(float)
df['delta累计'] = df['delta'].cumsum()
return True
except Exception as e:
print(f"\n===== 历史数据加载错误 =====")
print(f"合约: {instrument_id}")
print(f"读取JSON错误: {e}")
print("将使用实时数据开始交易")
print("===========================\n")
return False
else:
print(f"\n===== 历史数据加载提示 =====")
print(f"合约: {instrument_id}")
print(f"未找到历史数据文件: {json_file_path}")
print("将使用实时数据开始交易")
print("===========================\n")
return False
except Exception as e:
print(f"\n===== 历史数据加载错误 =====")
print(f"合约: {instrument_id}")
print(f"错误信息: {e}")
print("将使用实时数据开始交易")
print("===========================\n")
return False
#读取保存的数据
def read_to_csv(self,symbol):
# 文件夹路径和文件路径
# 使用完整的合约代码
symbol = str(symbol)
folder_path = "traderdata"
file_path = os.path.join(folder_path, f"{symbol}traderdata.csv")
# 如果文件夹不存在则创建
if not os.path.exists(folder_path):
os.makedirs(folder_path)
# 读取保留的模型数据CSV文件
if os.path.exists(file_path):
df = pd.read_csv(file_path)
if not df.empty and self.holddata==True:
# 选择最后一行数据
row = df.iloc[-1]
# 根据CSV文件的列名将数据赋值给相应的属性
self.pos = int(row['pos'])
self.short_trailing_stop_price = float(row['short_trailing_stop_price'])
self.long_trailing_stop_price = float(row['long_trailing_stop_price'])
self.sl_long_price = float(row['sl_long_price'])
self.sl_shor_price = float(row['sl_shor_price'])
# self.out_long = int(row['out_long'])
# self.out_short = int(row['out_short'])
print("找到历史交易数据文件,已经更新持仓,止损止盈数据", df.iloc[-1])
self.holddata=False
else:
pass
#print("没有找到历史交易数据文件", file_path)
#如果没有找到CSV则初始化变量
pass
#保存数据
def save_to_csv(self,symbol):
# 使用完整的合约代码
symbol = str(symbol)
# 创建DataFrame
data = {
'datetime': [datetime.now().strftime('%Y-%m-%d %H:%M:%S')], # 使用当前时间
'pos': [self.pos],
'short_trailing_stop_price': [self.short_trailing_stop_price],
'long_trailing_stop_price': [self.long_trailing_stop_price],
'sl_long_price': [self.sl_long_price],
'sl_shor_price': [self.sl_shor_price],
}
df = pd.DataFrame(data)
# 将DataFrame保存到CSV文件
df.to_csv(f"traderdata/{symbol}traderdata.csv", index=False)
#每日收盘重置数据
def day_data_reset(self,data):
# 获取当前时间
current_time = datetime.now().time()
# 第一时间范围
clearing_time1_start = s_time(14,55)
clearing_time1_end = s_time(15,00)
# 第二时间范围
clearing_time2_start = s_time(2,25)
clearing_time2_end = s_time(2,30)
current_bid = float(data['BidPrice1']) # 当前买价
current_ask = float(data['AskPrice1']) # 当前卖价
# 创建一个标志变量,用于记录是否已经执行过
self.clearing_executed = False
# 检查当前时间第一个操作的时间范围内
if clearing_time1_start <= current_time <= clearing_time1_end and not self.clearing_executed :
self.clearing_executed = True # 设置标志变量为已执行
# 如果有持仓,强制平仓
if self.pos != 0:
print("交易日结束,开始强制平仓")
# 遍历所有合约发送平仓指令
for instrument_id in list(self.stop_order_dict.keys()):
stops = self.stop_order_dict[instrument_id]
# 平多仓
if stops['long']['position'] > 0:
print(f"发送平多仓指令: {instrument_id}, 手数: {stops['long']['position']}, 价格: {current_bid}")
self.insert_order(data['ExchangeID'], data['InstrumentID'],
current_bid - self.py,
stops['long']['position'],
b'1', b'3')
# 平空仓
if stops['short']['position'] > 0:
print(f"发送平空仓指令: {instrument_id}, 手数: {stops['short']['position']}, 价格: {current_ask}")
self.insert_order(data['ExchangeID'], data['InstrumentID'],
current_ask + self.py,
stops['short']['position'],
b'0', b'3')
# 清空所有合约的持仓信息
for instrument_id in list(self.stop_order_dict.keys()):
self.clear_position_info(instrument_id, 'all')
self.day_closed = True # 设置日内平仓标志
print("日内交易已结束,禁止开新仓")
# 检查当前时间是否在第二个操作的时间范围内
elif clearing_time2_start <= current_time <= clearing_time2_end and not self.clearing_executed :
self.clearing_executed = True # 设置标志变量为已执行
# 如果有持仓,强制平仓
if self.pos != 0:
print("交易日结束,开始强制平仓")
# 遍历所有合约发送平仓指令
for instrument_id in list(self.stop_order_dict.keys()):
stops = self.stop_order_dict[instrument_id]
# 平多仓
if stops['long']['position'] > 0:
print(f"发送平多仓指令: {instrument_id}, 手数: {stops['long']['position']}, 价格: {current_bid}")
self.insert_order(data['ExchangeID'], data['InstrumentID'],
current_bid - self.py,
stops['long']['position'],
b'1', b'3')
# 平空仓
if stops['short']['position'] > 0:
print(f"发送平空仓指令: {instrument_id}, 手数: {stops['short']['position']}, 价格: {current_ask}")
self.insert_order(data['ExchangeID'], data['InstrumentID'],
current_ask + self.py,
stops['short']['position'],
b'0', b'3')
# 清空所有合约的持仓信息
for instrument_id in list(self.stop_order_dict.keys()):
self.clear_position_info(instrument_id, 'all')
self.day_closed = True # 设置日内平仓标志
print("日内交易已结束,禁止开新仓")
else:
self.clearing_executed = False
# 在新交易日开始时重置日内平仓标志
if current_time < clearing_time1_start or (current_time > clearing_time1_end and current_time < clearing_time2_start):
self.day_closed = False
return self.clearing_executed
def OnRtnTrade(self, pTrade):
print("||成交回报||", pTrade)
# 获取成交信息
instrument_id = pTrade['InstrumentID'].decode()
direction = pTrade['Direction'].decode() # '0'为买入,'1'为卖出
offset_flag = pTrade['OffsetFlag'].decode() # '0'为开仓,'1'为平仓,'3'为平今
volume = pTrade['Volume']
price = pTrade['Price']
# 根据成交类型更新持仓信息
if offset_flag in ['1', '3']: # 平仓或平今
# 判断平的是多头还是空头
if direction == '1': # 卖出,平多头
print(f"平多头成交: {instrument_id}, 价格: {price}, 数量: {volume}")
# 清空多头持仓信息
self.clear_position_info(instrument_id, 'long')
else: # 买入,平空头
print(f"平空头成交: {instrument_id}, 价格: {price}, 数量: {volume}")
# 清空空头持仓信息
self.clear_position_info(instrument_id, 'short')
elif offset_flag == '0': # 开仓
if direction == '0': # 买入,开多头
print(f"开多头成交: {instrument_id}, 价格: {price}, 数量: {volume}")
# 如果有空头持仓,先清空
if instrument_id in self.stop_order_dict and self.stop_order_dict[instrument_id]['short']['position'] > 0:
self.clear_position_info(instrument_id, 'short')
# 设置多头持仓信息
sl_price = price * (1 - self.fixed_stop_loss_percent) # 默认止损价
tp_price = price * (1 + self.trailing_stop_percent) # 默认止盈价
# 设置初始跟踪止损价,与开仓价保持一定距离
initial_trailing_stop = price * (1 - self.trailing_stop_percent)
log_message = f"设置止损价: {sl_price}, 止盈价: {tp_price}, 跟踪止损价: {initial_trailing_stop}, 跟踪百分比: {self.trailing_stop_percent*100:.3f}%"
STOPLOSS_LOGGER.info(log_message)
# 更新多头持仓信息,设置合理的跟踪止损初始值
self.update_stop_order_dict(instrument_id, 'long', volume, price, sl_price, tp_price, initial_trailing_stop)
self.pos = volume # 更新全局持仓状态
# 兼容旧代码
self.long_trailing_stop_price = initial_trailing_stop
self.sl_long_price = sl_price
self.save_to_csv(instrument_id)
else: # 卖出,开空头
print(f"开空头成交: {instrument_id}, 价格: {price}, 数量: {volume}")
# 如果有多头持仓,先清空
if instrument_id in self.stop_order_dict and self.stop_order_dict[instrument_id]['long']['position'] > 0:
self.clear_position_info(instrument_id, 'long')
# 设置空头持仓信息
sl_price = price * (1 + self.fixed_stop_loss_percent) # 默认止损价
tp_price = price * (1 - self.trailing_stop_percent) # 默认止盈价
# 设置初始跟踪止损价,与开仓价保持一定距离
initial_trailing_stop = price * (1 + self.trailing_stop_percent)
log_message = f"设置止损价: {sl_price}, 止盈价: {tp_price}, 跟踪止损价: {initial_trailing_stop}, 跟踪百分比: {self.trailing_stop_percent*100:.3f}%"
STOPLOSS_LOGGER.info(log_message)
# 更新空头持仓信息,设置合理的跟踪止损初始值
self.update_stop_order_dict(instrument_id, 'short', self.Lots, price, sl_price, tp_price, initial_trailing_stop)
self.pos = -1 # 更新全局持仓状态
# 兼容旧代码
self.short_trailing_stop_price = initial_trailing_stop
self.sl_shor_price = sl_price
self.save_to_csv(instrument_id)
def OnRspOrderInsert(self, pInputOrder, pRspInfo, nRequestID, bIsLast):
print("||OnRspOrderInsert||", pInputOrder, pRspInfo, nRequestID, bIsLast)
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
#注意运行前请先安装好algoplus,
# pip install AlgoPlus
#http://www.algo.plus/ctp/python/0103001.html
# 订单状态通知
def OnRtnOrder(self, pOrder):
print("||订单回报||", pOrder)
def Join(self):
data = None
# 记录上次加载止盈止损信息的时间和合约
last_load_time = {}
while True:
if self.status == 0:
while not self.md_queue.empty():
data = self.md_queue.get(block=False)
instrument_id = data['InstrumentID'].decode() # 品种代码
# 首次运行时加载历史数据
if self.kgdata:
self.load_history_data(instrument_id)
self.kgdata = False
# 加载该合约的止盈止损信息,避免频繁加载
current_time = time.time()
if instrument_id not in last_load_time or current_time - last_load_time.get(instrument_id, 0) > 60: # 每60秒才重新加载一次
self.load_stop_orders_from_file(instrument_id)
last_load_time[instrument_id] = current_time
# 检查止盈止损条件
self.check_stop_conditions(data)
# 在每个tick都检查和处理AI交易信号 - 移到这里以提高执行速度
if self.use_ai_model:
self.check_and_process_ai_signals(data)
# 原有代码...
self.read_to_csv(instrument_id)
self.day_data_reset(data)
tickcome(data)
#新K线开始启动交易程序 and 保存行情数据
if len(trader_df)>self.cont_df and len(trader_df)>0:
# 计算日均线
trader_df['dayma'] = trader_df['close'].mean()
# 计算累积的delta值
trader_df['delta'] = trader_df['delta'].astype(float)
trader_df['delta累计'] = trader_df['delta'].cumsum()
# 检查文件是否存在
json_file_path = f"traderdata/{instrument_id}_ofdata.json"
# 确保待保存的新数据中datetime字段是格式化的日期时间字符串
if 'datetime' in trader_df.columns:
# 无论什么类型,都统一转换为字符串格式
trader_df['datetime'] = trader_df['datetime'].astype(str)
if os.path.exists(json_file_path):
try:
# 读取现有数据
existing_df = pd.read_json(json_file_path, lines=True)
# 确保现有数据中的datetime字段是字符串类型
if 'datetime' in existing_df.columns:
existing_df['datetime'] = existing_df['datetime'].astype(str)
# 合并新数据
combined_df = pd.concat([existing_df, trader_df.tail(1)], ignore_index=True)
# 保存合并后的数据使用lines=True确保每行是独立的JSON对象
combined_df.to_json(json_file_path, orient='records', force_ascii=False, lines=True)
except Exception as e:
print(f"读取或保存JSON文件时出错: {e}")
# 如果读取出错,直接保存当前数据
trader_df.to_json(json_file_path, orient='records', force_ascii=False, lines=True)
else:
# 创建新文件并保存整个DataFrame
trader_df.to_json(json_file_path, orient='records', force_ascii=False, lines=True)
# 更新跟踪止损价格 - 兼容旧版本代码
if self.long_trailing_stop_price >0 and self.pos>0:
self.long_trailing_stop_price = trader_df['low'].iloc[-1] if self.long_trailing_stop_price<trader_df['low'].iloc[-1] else self.long_trailing_stop_price
self.save_to_csv(instrument_id)
if self.short_trailing_stop_price >0 and self.pos<0:
self.short_trailing_stop_price = trader_df['high'].iloc[-1] if trader_df['high'].iloc[-1] <self.short_trailing_stop_price else self.short_trailing_stop_price
self.save_to_csv(instrument_id)
# 使用AI模型进行交易决策 - 只在bar完成时触发分析不再立即执行信号
if self.use_ai_model:
# 检查是否在日内平仓后
if self.day_closed:
print("日内交易已结束,禁止开新仓")
return
# 仅在K线完成时启动AI分析线程但不立即执行信号
global AI_THREAD_RUNNING
if not AI_THREAD_RUNNING and len(trader_df) > self.trader_rows:
AI_THREAD_RUNNING = True
ai_thread = threading.Thread(
target=self.background_model_call,
args=(trader_df, instrument_id)
)
ai_thread.daemon = True # 设置为守护线程,主程序退出时自动结束
ai_thread.start()
print("启动AI分析线程...")
print(trader_df['datetime'])
self.cont_df=len(trader_df)
else:
time.sleep(1)
def background_model_call(self, data_df, instrument_id):
"""
在后台线程中调用大模型,并将交易信号放入队列
Args:
data_df: 包含订单流数据的DataFrame
instrument_id: 合约ID
"""
global AI_THREAD_RUNNING
start_time = datetime.now()
log_message = f"\n===== 开始AI分析 [{start_time.strftime('%Y-%m-%d %H:%M:%S')}] ====="
print(log_message)
AI_LOGGER.info(log_message)
log_message = f"正在分析合约: {instrument_id}"
print(log_message)
AI_LOGGER.info(log_message)
try:
# 复制DataFrame以避免在不同线程间共享数据可能导致的问题
df_copy = data_df.copy()
# 输出分析的数据行数
log_message = f"分析数据行数: {len(df_copy)}"
print(log_message)
AI_LOGGER.info(log_message)
log_message = f"最新价格: {df_copy['close'].iloc[-1] if len(df_copy) > 0 else 'N/A'}"
print(log_message)
AI_LOGGER.info(log_message)
# 调用大模型获取交易信号
trading_signal = call_deepseek_model(df_copy, self) # 传递self参数
# 计算分析耗时
end_time = datetime.now()
elapsed = (end_time - start_time).total_seconds()
# 将交易信号和合约ID一起放入队列
signal_data = {
'signal': trading_signal,
'instrument_id': instrument_id,
'timestamp': end_time # 使用结束时间作为时间戳
}
AI_SIGNAL_QUEUE.put(signal_data)
log_message = f"AI模型分析完成结果已放入队列耗时: {elapsed:.2f}"
print(log_message)
AI_LOGGER.info(log_message)
log_message = f"分析结果: {trading_signal}"
print(log_message)
AI_LOGGER.info(log_message)
log_message = "===== AI分析完成 ====="
print(log_message + "\n")
AI_LOGGER.info(log_message)
except Exception as e:
end_time = datetime.now()
elapsed = (end_time - start_time).total_seconds()
log_message = f"AI模型分析线程出现异常: {e}"
print(log_message)
AI_LOGGER.error(log_message)
print(f"异常详情:")
import traceback
error_traceback = traceback.format_exc()
AI_LOGGER.error(f"异常详情:\n{error_traceback}")
traceback.print_exc()
log_message = f"分析失败,耗时: {elapsed:.2f}"
print(log_message)
AI_LOGGER.error(log_message)
log_message = "===== AI分析异常结束 ====="
print(log_message + "\n")
AI_LOGGER.error(log_message)
finally:
# 标记线程已完成
AI_THREAD_RUNNING = False
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
#注意运行前请先安装好algoplus,
# pip install AlgoPlus
#http://www.algo.plus/ctp/python/0103001.html
def check_and_process_ai_signals(self, data):
"""
检查并处理AI产生的交易信号
每个tick都会调用此函数实现更快的交易信号响应
"""
if AI_SIGNAL_QUEUE.empty():
return
# 从队列中获取信号
signal_data = AI_SIGNAL_QUEUE.get()
trading_signal = signal_data['signal']
instrument_id = signal_data['instrument_id']
signal_time = signal_data['timestamp']
# 检查信号是否过期超过15秒
if (datetime.now() - signal_time).total_seconds() > 15:
log_message = f"AI信号已过期忽略: {trading_signal}"
print(log_message)
SIGNAL_LOGGER.warning(log_message)
return
# 验证合约ID是否匹配
if instrument_id != data['InstrumentID'].decode():
# 如果合约不匹配,将信号放回队列以便后续处理
AI_SIGNAL_QUEUE.put(signal_data)
return
log_message = f"\n===== 执行AI模型交易信号 [{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ====="
print(log_message)
SIGNAL_LOGGER.info(log_message)
log_message = f"信号生成时间: {signal_time.strftime('%Y-%m-%d %H:%M:%S')}"
print(log_message)
SIGNAL_LOGGER.info(log_message)
log_message = f"信号类型: {trading_signal.get('action', '不操作')}"
print(log_message)
SIGNAL_LOGGER.info(log_message)
log_message = f"置信度: {trading_signal.get('confidence', 0)}"
print(log_message)
SIGNAL_LOGGER.info(log_message)
log_message = f"理由: {trading_signal.get('reason', '')}"
print(log_message)
SIGNAL_LOGGER.info(log_message)
# 根据AI模型返回的交易信号执行交易
action = trading_signal.get('action', '不操作')
confidence = trading_signal.get('confidence', 0)
reason = trading_signal.get('reason', '')
stop_loss = trading_signal.get('stop_loss', 0)
take_profit = trading_signal.get('take_profit', 0)
trailing_percent = trading_signal.get('trailing_percent', 0)
# 如果AI建议了有效的跟踪止损百分比则更新参数
if 0.0001 <= trailing_percent <= 0.001:
old_percent = self.trailing_stop_percent
self.trailing_stop_percent = trailing_percent
log_message = f"更新跟踪止损百分比参数: {old_percent*100:.3f}% -> {trailing_percent*100:.3f}%"
STOPLOSS_LOGGER.info(log_message)
# 获取现有持仓信息
if instrument_id not in self.stop_order_dict:
self.stop_order_dict[instrument_id] = {
'long': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0},
'short': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0}
}
current_stops = self.stop_order_dict[instrument_id]
# 如果持有多头或空头头寸,更新跟踪止损价
if current_stops['long']['position'] > 0:
entry_price = current_stops['long']['entry_price']
new_trailing_stop = float(data['BidPrice1']) * (1 - self.trailing_stop_percent)
old_trailing_stop = current_stops['long']['trailing_stop']
# 只有当新计算的跟踪止损价更高时才更新
if new_trailing_stop > old_trailing_stop:
self.update_stop_order_dict(instrument_id, 'long', None, None, None, None, new_trailing_stop)
# 兼容旧代码
self.long_trailing_stop_price = new_trailing_stop
self.save_to_csv(instrument_id)
elif current_stops['short']['position'] > 0:
entry_price = current_stops['short']['entry_price']
new_trailing_stop = float(data['AskPrice1']) * (1 + self.trailing_stop_percent)
old_trailing_stop = current_stops['short']['trailing_stop']
# 只有当新计算的跟踪止损价更低或原来为0时才更新
if new_trailing_stop < old_trailing_stop or old_trailing_stop == 0:
self.update_stop_order_dict(instrument_id, 'short', None, None, None, None, new_trailing_stop)
# 兼容旧代码
self.short_trailing_stop_price = new_trailing_stop
self.save_to_csv(instrument_id)
# 只有当置信度大于等于5时才执行交易
if confidence >= 6:
print(f"开始执行交易,当前价格: 买一{data['BidPrice1']} / 卖一{data['AskPrice1']}")
# 获取现有持仓信息
if instrument_id not in self.stop_order_dict:
self.stop_order_dict[instrument_id] = {
'long': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0},
'short': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0}
}
current_stops = self.stop_order_dict[instrument_id]
if action == '开多' and current_stops['long']['position'] <= 0:
# 如果持有空头头寸,先平空
if current_stops['short']['position'] > 0:
print('执行平空操作')
self.insert_order(data['ExchangeID'], data['InstrumentID'],
data['AskPrice1']+self.py,
current_stops['short']['position'],
b'0', b'3')
# 清空空头持仓信息
self.clear_position_info(instrument_id, 'short')
# 开多
print('执行开多操作')
entry_price = float(data['AskPrice1'])
self.insert_order(data['ExchangeID'], data['InstrumentID'],
entry_price+self.py, self.Lots, b'0', b'0')
# 使用AI建议的止损止盈价格
sl_price = stop_loss if stop_loss > 0 else entry_price * (1 - self.fixed_stop_loss_percent)
tp_price = take_profit if take_profit > 0 else entry_price * (1 + self.trailing_stop_percent)
# 设置初始跟踪止损价,与开仓价保持一定距离
initial_trailing_stop = entry_price * (1 - self.trailing_stop_percent)
log_message = f"设置止损价: {sl_price}, 止盈价: {tp_price}, 跟踪止损价: {initial_trailing_stop}, 跟踪百分比: {self.trailing_stop_percent*100:.3f}%"
STOPLOSS_LOGGER.info(log_message)
# 更新多头持仓信息,设置合理的跟踪止损初始值
self.update_stop_order_dict(instrument_id, 'long', self.Lots, entry_price, sl_price, tp_price, initial_trailing_stop)
self.pos = 1 # 更新全局持仓状态
# 兼容旧代码
self.long_trailing_stop_price = initial_trailing_stop
self.sl_long_price = sl_price
self.save_to_csv(instrument_id)
elif action == '开空' and current_stops['short']['position'] <= 0:
# 如果持有多头头寸,先平多
if current_stops['long']['position'] > 0:
print('执行平多操作')
self.insert_order(data['ExchangeID'], data['InstrumentID'],
data['BidPrice1']-self.py,
current_stops['long']['position'],
b'1', b'3')
# 清空多头持仓信息
self.clear_position_info(instrument_id, 'long')
# 开空
print('执行开空操作')
entry_price = float(data['BidPrice1'])
self.insert_order(data['ExchangeID'], data['InstrumentID'],
entry_price-self.py, self.Lots, b'1', b'0')
# 使用AI建议的止损止盈价格
sl_price = stop_loss if stop_loss > 0 else entry_price * (1 + self.fixed_stop_loss_percent)
tp_price = take_profit if take_profit > 0 else entry_price * (1 - self.trailing_stop_percent)
# 设置初始跟踪止损价,与开仓价保持一定距离
initial_trailing_stop = entry_price * (1 + self.trailing_stop_percent)
log_message = f"设置止损价: {sl_price}, 止盈价: {tp_price}, 跟踪止损价: {initial_trailing_stop}, 跟踪百分比: {self.trailing_stop_percent*100:.3f}%"
STOPLOSS_LOGGER.info(log_message)
# 更新空头持仓信息,设置合理的跟踪止损初始值
self.update_stop_order_dict(instrument_id, 'short', self.Lots, entry_price, sl_price, tp_price, initial_trailing_stop)
self.pos = -1 # 更新全局持仓状态
# 兼容旧代码
self.short_trailing_stop_price = initial_trailing_stop
self.sl_shor_price = sl_price
self.save_to_csv(instrument_id)
elif action == '平多' and current_stops['long']['position'] > 0:
print('执行平多操作')
self.insert_order(data['ExchangeID'], data['InstrumentID'],
data['BidPrice1']-self.py,
current_stops['long']['position'],
b'1', b'3')
# 清空多头持仓信息
self.clear_position_info(instrument_id, 'long')
elif action == '平空' and current_stops['short']['position'] > 0:
print('执行平空操作')
self.insert_order(data['ExchangeID'], data['InstrumentID'],
data['AskPrice1']+self.py,
current_stops['short']['position'],
b'0', b'3')
# 清空空头持仓信息
self.clear_position_info(instrument_id, 'short')
# 添加处理复合交易指令的逻辑 - 平多开空
elif action == '平多开空' and current_stops['long']['position'] > 0:
print('执行平多开空操作')
# 第一步:平多
print('1. 执行平多操作')
self.insert_order(data['ExchangeID'], data['InstrumentID'],
data['BidPrice1']-self.py,
current_stops['long']['position'],
b'1', b'3')
# 清空多头持仓信息
self.clear_position_info(instrument_id, 'long')
# 第二步:开空
print('2. 执行开空操作')
entry_price = float(data['BidPrice1'])
self.insert_order(data['ExchangeID'], data['InstrumentID'],
entry_price-self.py, self.Lots, b'1', b'0')
# 使用AI建议的止损止盈价格
sl_price = stop_loss if stop_loss > 0 else entry_price * (1 + self.fixed_stop_loss_percent)
tp_price = take_profit if take_profit > 0 else entry_price * (1 - self.trailing_stop_percent)
# 设置初始跟踪止损价,与开仓价保持一定距离
initial_trailing_stop = entry_price * (1 + self.trailing_stop_percent)
log_message = f"设置止损价: {sl_price}, 止盈价: {tp_price}, 跟踪止损价: {initial_trailing_stop}, 跟踪百分比: {self.trailing_stop_percent*100:.3f}%"
STOPLOSS_LOGGER.info(log_message)
# 更新空头持仓信息
self.update_stop_order_dict(instrument_id, 'short', self.Lots, entry_price, sl_price, tp_price, initial_trailing_stop)
self.pos = -1 # 更新全局持仓状态
# 兼容旧代码
self.short_trailing_stop_price = initial_trailing_stop
self.sl_shor_price = sl_price
self.save_to_csv(instrument_id)
# 添加处理复合交易指令的逻辑 - 平空开多
elif action == '平空开多' and current_stops['short']['position'] > 0:
print('执行平空开多操作')
# 第一步:平空
print('1. 执行平空操作')
self.insert_order(data['ExchangeID'], data['InstrumentID'],
data['AskPrice1']+self.py,
current_stops['short']['position'],
b'0', b'3')
# 清空空头持仓信息
self.clear_position_info(instrument_id, 'short')
# 第二步:开多
print('2. 执行开多操作')
entry_price = float(data['AskPrice1'])
self.insert_order(data['ExchangeID'], data['InstrumentID'],
entry_price+self.py, self.Lots, b'0', b'0')
# 使用AI建议的止损止盈价格
sl_price = stop_loss if stop_loss > 0 else entry_price * (1 - self.fixed_stop_loss_percent)
tp_price = take_profit if take_profit > 0 else entry_price * (1 + self.trailing_stop_percent)
# 设置初始跟踪止损价,与开仓价保持一定距离
initial_trailing_stop = entry_price * (1 - self.trailing_stop_percent)
log_message = f"设置止损价: {sl_price}, 止盈价: {tp_price}, 跟踪止损价: {initial_trailing_stop}, 跟踪百分比: {self.trailing_stop_percent*100:.3f}%"
STOPLOSS_LOGGER.info(log_message)
# 更新多头持仓信息
self.update_stop_order_dict(instrument_id, 'long', self.Lots, entry_price, sl_price, tp_price, initial_trailing_stop)
self.pos = 1 # 更新全局持仓状态
# 兼容旧代码
self.long_trailing_stop_price = initial_trailing_stop
self.sl_long_price = sl_price
self.save_to_csv(instrument_id)
# 如果AI建议调整止损止盈价格
elif action == '不操作':
if stop_loss > 0:
if current_stops['long']['position'] > 0: # 多头持仓
self.update_stop_order_dict(instrument_id, 'long', None, None, stop_loss, None, None)
print(f'已调整多头止损价: {stop_loss}')
# 兼容旧代码
self.sl_long_price = stop_loss
self.save_to_csv(instrument_id)
elif current_stops['short']['position'] > 0: # 空头持仓
self.update_stop_order_dict(instrument_id, 'short', None, None, stop_loss, None, None)
print(f'已调整空头止损价: {stop_loss}')
# 兼容旧代码
self.sl_shor_price = stop_loss
self.save_to_csv(instrument_id)
if take_profit > 0:
if current_stops['long']['position'] > 0: # 多头持仓
self.update_stop_order_dict(instrument_id, 'long', None, None, None, take_profit, None)
print(f'已调整多头止盈价: {take_profit}')
# 兼容旧代码
self.long_trailing_stop_price = take_profit
self.save_to_csv(instrument_id)
elif current_stops['short']['position'] > 0: # 空头持仓
self.update_stop_order_dict(instrument_id, 'short', None, None, None, take_profit, None)
print(f'已调整空头止盈价: {take_profit}')
# 兼容旧代码
self.short_trailing_stop_price = take_profit
self.save_to_csv(instrument_id)
print("===== 交易信号执行完成 =====\n")
else:
print(f"信号置信度{confidence}低于执行阈值(5),不执行交易")
print("===== 交易信号处理完成 =====\n")
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
#注意运行前请先安装好algoplus,
# pip install AlgoPlus
#http://www.algo.plus/ctp/python/0103001.html
def format_data_for_llm(self, df):
"""
将DataFrame格式化为适合LLM分析的文本格式包含历史交易信息
"""
# 提取最近几条记录
recent_data = df.tail(self.trader_rows)
# 构建基本信息
data_text = "订单流数据分析:\n\n"
# 提取最新行情数据
instrument_id = recent_data['symbol'].iloc[-1]
# 添加最新价格和交易量信息
data_text += f"当前时间: {recent_data['datetime'].iloc[-1]}\n"
data_text += f"当前价格: {recent_data['close'].iloc[-1]}\n"
data_text += f"开盘价: {recent_data['open'].iloc[-1]}\n"
data_text += f"最高价: {recent_data['high'].iloc[-1]}\n"
data_text += f"最低价: {recent_data['low'].iloc[-1]}\n"
data_text += f"成交量: {recent_data['volume'].iloc[-1]}\n"
# 计算价格趋势
if len(recent_data) >= 5:
price_trend = recent_data['close'].pct_change().tail(5).mean() * 100
data_text += f"价格短期趋势: {'上涨' if price_trend > 0 else '下跌'} ({price_trend:.2f}%)\n"
# 计算价格波动性
if len(recent_data) >= 5:
volatility = recent_data['close'].pct_change().tail(5).std() * 100
data_text += f"价格波动性: {volatility:.2f}%\n"
# 添加支撑和阻力位分析
recent_high = recent_data['high'].max()
recent_low = recent_data['low'].min()
data_text += f"近期阻力位: {recent_high}\n"
data_text += f"近期支撑位: {recent_low}\n"
# 添加均线分析 - 计算5、10、20均线
if len(df) >= 20:
# 计算均线
ma5 = df['close'].rolling(5).mean().iloc[-1]
ma10 = df['close'].rolling(10).mean().iloc[-1]
ma20 = df['close'].rolling(20).mean().iloc[-1]
data_text += f"MA5: {ma5:.2f}\n"
data_text += f"MA10: {ma10:.2f}\n"
data_text += f"MA20: {ma20:.2f}\n"
# 判断均线形态
if abs(ma5 - ma10) / ma10 < 0.001 and abs(ma10 - ma20) / ma20 < 0.001:
ma_pattern = "均线粘合"
elif ma5 > ma10 and ma10 > ma20:
ma_pattern = "多头排列"
elif ma5 < ma10 and ma10 < ma20:
ma_pattern = "空头排列"
elif ma5 > ma10 and ma10 < ma20:
ma_pattern = "金叉形态"
elif ma5 < ma10 and ma10 > ma20:
ma_pattern = "死叉形态"
else:
ma_pattern = "无明显形态"
data_text += f"均线形态: {ma_pattern}\n"
# 价格与均线关系
current_price = df['close'].iloc[-1]
data_text += f"价格相对MA5: {'上方' if current_price > ma5 else '下方'} ({(current_price/ma5-1)*100:.2f}%)\n"
data_text += f"价格相对MA10: {'上方' if current_price > ma10 else '下方'} ({(current_price/ma10-1)*100:.2f}%)\n"
data_text += f"价格相对MA20: {'上方' if current_price > ma20 else '下方'} ({(current_price/ma20-1)*100:.2f}%)\n"
# 日内超涨超跌分析
if len(df) >= 20:
# 计算日内振幅
daily_high = df['high'].iloc[-20:].max()
daily_low = df['low'].iloc[-20:].min()
daily_range = (daily_high - daily_low) / daily_low * 100
# 当前价格在日内范围的位置
current_in_range = (df['close'].iloc[-1] - daily_low) / (daily_high - daily_low) * 100 if (daily_high - daily_low) > 0 else 50
data_text += f"日内振幅: {daily_range:.2f}%\n"
data_text += f"价格在日内范围的位置: {current_in_range:.2f}% (0%为日低, 100%为日高)\n"
# 判断超涨超跌
if current_in_range > 85:
data_text += "日内状态: 可能超涨\n"
elif current_in_range < 15:
data_text += "日内状态: 可能超跌\n"
else:
data_text += "日内状态: 正常区间\n"
# 形态识别
if len(df) >= 20:
# 简单K线形态识别
recent_k = df.tail(5).copy()
# 计算实体和影线
recent_k['body'] = abs(recent_k['close'] - recent_k['open'])
recent_k['upper_shadow'] = recent_k.apply(lambda x: x['high'] - max(x['open'], x['close']), axis=1)
recent_k['lower_shadow'] = recent_k.apply(lambda x: min(x['open'], x['close']) - x['low'], axis=1)
# 最新K线特征
latest_k = recent_k.iloc[-1]
prev_k = recent_k.iloc[-2] if len(recent_k) > 1 else None
data_text += "\nK线形态分析:\n"
# 判断最新K线类型
if latest_k['body'] == 0:
k_type = "十字星"
elif latest_k['upper_shadow'] > 2 * latest_k['body'] and latest_k['lower_shadow'] < 0.5 * latest_k['body']:
k_type = "上影线长"
elif latest_k['lower_shadow'] > 2 * latest_k['body'] and latest_k['upper_shadow'] < 0.5 * latest_k['body']:
k_type = "下影线长"
elif latest_k['close'] > latest_k['open'] and latest_k['body'] > np.mean(recent_k['body']):
k_type = "大阳线"
elif latest_k['close'] < latest_k['open'] and latest_k['body'] > np.mean(recent_k['body']):
k_type = "大阴线"
elif latest_k['close'] > latest_k['open']:
k_type = "小阳线"
elif latest_k['close'] < latest_k['open']:
k_type = "小阴线"
else:
k_type = "普通K线"
data_text += f"最新K线类型: {k_type}\n"
# 判断简单组合形态
if prev_k is not None:
if prev_k['close'] < prev_k['open'] and latest_k['close'] > latest_k['open'] and latest_k['open'] <= prev_k['close'] and latest_k['close'] > prev_k['open']:
data_text += "组合形态: 可能构成看涨吞没形态\n"
elif prev_k['close'] > prev_k['open'] and latest_k['close'] < latest_k['open'] and latest_k['open'] >= prev_k['close'] and latest_k['close'] < prev_k['open']:
data_text += "组合形态: 可能构成看跌吞没形态\n"
elif prev_k['close'] < prev_k['open'] and latest_k['close'] > latest_k['open'] and latest_k['body'] > 1.5 * prev_k['body']:
data_text += "组合形态: 可能构成看涨势能增强\n"
elif prev_k['close'] > prev_k['open'] and latest_k['close'] < latest_k['open'] and latest_k['body'] > 1.5 * prev_k['body']:
data_text += "组合形态: 可能构成看跌势能增强\n"
# 添加订单流特定数据
data_text += f"\n订单流净值: {recent_data['delta'].iloc[-1]}\n"
data_text += f"订单流累计值: {recent_data['delta累计'].iloc[-1] if 'delta累计' in recent_data.columns else '无数据'}\n"
data_text += f"堆积指标: {recent_data['dj'].iloc[-1]}\n"
# 添加日均线数据
data_text += f"日均线: {recent_data['dayma'].iloc[-1] if 'dayma' in recent_data.columns else '无数据'}\n"
# 添加价格与日均线的关系
if 'dayma' in recent_data.columns:
price_above_ma = recent_data['close'].iloc[-1] > recent_data['dayma'].iloc[-1]
data_text += f"价格位于日均线: {'上方' if price_above_ma else '下方'}\n"
# 订单流全面分析
data_text += "\n订单流详细分析:\n"
# 计算订单流趋势
if len(recent_data) >= 5:
delta_values = recent_data['delta'].values
delta_trend = np.mean(np.diff(delta_values)) if len(delta_values) > 1 else 0
data_text += f"订单流趋势: {'增强中' if delta_trend > 0 else '减弱中'} (变化率: {delta_trend:.2f})\n"
# 计算订单流强度(使用绝对值的平均值)
delta_strength = np.mean(np.abs(recent_data['delta'].values))
data_text += f"订单流强度: {delta_strength:.2f}\n"
# 订单流指标超涨超跌分析
if len(df) >= 20:
delta_values = df['delta'].iloc[-20:].values
delta_mean = np.mean(delta_values)
delta_std = np.std(delta_values)
current_delta = df['delta'].iloc[-1]
# Z分数计算
if delta_std > 0:
delta_z = (current_delta - delta_mean) / delta_std
data_text += f"订单流偏离度(Z分数): {delta_z:.2f}\n"
if delta_z > 2:
data_text += "订单流状态: 可能超买\n"
elif delta_z < -2:
data_text += "订单流状态: 可能超卖\n"
else:
data_text += "订单流状态: 正常区间\n"
# 买卖力量对比
if 'bid_v' in recent_data.columns and 'ask_v' in recent_data.columns:
bid_power = recent_data['bid_v'].mean()
ask_power = recent_data['ask_v'].mean()
power_ratio = bid_power / ask_power if ask_power != 0 else float('inf')
data_text += f"买卖比例: {power_ratio:.2f} (>1买方强势<1卖方强势)\n"
# 订单流累计趋势
if 'delta累计' in recent_data.columns and len(recent_data) >= 2:
cumulative_start = recent_data['delta累计'].iloc[0]
cumulative_end = recent_data['delta累计'].iloc[-1]
cumulative_change = cumulative_end - cumulative_start
data_text += f"累计订单流变化: {cumulative_change:.2f} (从 {cumulative_start:.2f}{cumulative_end:.2f})\n"
# 订单流波动性
delta_volatility = np.std(recent_data['delta'].values)
data_text += f"订单流波动性: {delta_volatility:.2f}\n"
# 订单流与价格相关性
if len(recent_data) >= 10: # 确保有足够数据计算相关性
price_changes = recent_data['close'].pct_change().dropna()
delta_changes = recent_data['delta'].iloc[1:].reset_index(drop=True) # 对齐索引
if len(price_changes) > 0 and len(delta_changes) == len(price_changes):
try:
correlation = np.corrcoef(price_changes, delta_changes)[0, 1] if len(price_changes) > 1 else 0
data_text += f"订单流与价格相关性: {correlation:.2f}\n"
data_text += f"相关性解读: {'强正相关' if correlation > 0.7 else '正相关' if correlation > 0.3 else '弱相关' if correlation > -0.3 else '负相关' if correlation > -0.7 else '强负相关'}\n"
except:
data_text += "订单流与价格相关性: 计算错误\n"
# 订单流势能分析(最近几分钟的方向)
recent_deltas = recent_data['delta'].tail(5).values
positive_count = sum(1 for x in recent_deltas if x > 0)
negative_count = sum(1 for x in recent_deltas if x < 0)
data_text += f"最近订单流方向: {'多头主导' if positive_count > negative_count else '空头主导' if positive_count < negative_count else '方向不明'} ({positive_count}正/{negative_count}负)\n"
# 订单流强弱可视化
delta_last_5 = recent_data['delta'].tail(5).values
strength_visual = ""
for val in delta_last_5:
if val > 3:
strength_visual += "↑↑ "
elif val > 0:
strength_visual += ""
elif val < -3:
strength_visual += "↓↓ "
elif val < 0:
strength_visual += ""
else:
strength_visual += ""
data_text += f"订单流强弱可视化 (最近5分钟): {strength_visual}\n"
# 添加最近几条记录的趋势
data_text += "\n最近几条记录的趋势:\n"
# 添加最近5条记录的关键指标变化
for i in range(min(5, len(recent_data))):
idx = -(i+1)
data_text += f"记录 {i+1}: 时间={recent_data['datetime'].iloc[idx]}, 价格={recent_data['close'].iloc[idx]}, "
data_text += f"订单流净值={recent_data['delta'].iloc[idx]}, 堆积={recent_data['dj'].iloc[idx]}\n"
# 添加当前持仓信息,使用新的止盈止损字典
data_text += "\n当前持仓状态:\n"
# 获取该合约的止盈止损信息
stops = self.stop_order_dict.get(instrument_id, {
'long': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0},
'short': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0}
})
# 获取多头和空头持仓信息
long_pos = stops.get('long', {}).get('position', 0)
short_pos = stops.get('short', {}).get('position', 0)
# 判断当前持仓方向
if long_pos > 0:
data_text += f"持仓方向: 多头\n"
data_text += f"持仓数量: {long_pos}\n"
data_text += f"开仓价格: {stops['long']['entry_price']}\n"
data_text += f"止损价格: {stops['long']['stop_loss']}\n"
data_text += f"止盈价格: {stops['long']['take_profit']}\n"
data_text += f"跟踪止损价: {stops['long']['trailing_stop']}\n"
# 计算当前盈亏
current_price = recent_data['close'].iloc[-1]
profit_percent = (current_price - stops['long']['entry_price']) / stops['long']['entry_price'] * 100
data_text += f"当前盈亏: {profit_percent:.2f}%\n"
elif short_pos > 0:
data_text += f"持仓方向: 空头\n"
data_text += f"持仓数量: {short_pos}\n"
data_text += f"开仓价格: {stops['short']['entry_price']}\n"
data_text += f"止损价格: {stops['short']['stop_loss']}\n"
data_text += f"止盈价格: {stops['short']['take_profit']}\n"
data_text += f"跟踪止损价: {stops['short']['trailing_stop']}\n"
# 计算当前盈亏
current_price = recent_data['close'].iloc[-1]
profit_percent = (stops['short']['entry_price'] - current_price) / stops['short']['entry_price'] * 100
data_text += f"当前盈亏: {profit_percent:.2f}%\n"
else:
data_text += "持仓方向: 空仓\n"
data_text += "持仓数量: 0\n"
# 添加风险管理参数信息
data_text += "\n风险管理参数设置:\n"
data_text += f"固定止损百分比: {self.fixed_stop_loss_percent * 100:.2f}%\n"
data_text += f"跟踪止损百分比: {self.trailing_stop_percent * 100:.2f}%\n"
# 添加交易建议提示
data_text += "\n请根据以上信息,分析当前市场状态并给出交易建议。需要考虑:\n"
data_text += "1. 当前持仓状态是否合理\n"
data_text += "2. 是否需要调整止损止盈位置\n"
data_text += "3. 是否需要平仓或反手\n"
data_text += "4. 是否适合开新仓\n"
data_text += "5. 是否需要调整跟踪止损百分比参数范围建议0.0001-0.001\n"
data_text += "6. 是否出现抄底摸顶机会\n"
data_text += "7. 是否存在日内波段交易机会\n"
return data_text
def update_stop_order_dict(self, instrument_id, direction, position, entry_price=None, stop_loss=None, take_profit=None, trailing_stop=None):
"""更新止损止盈信息"""
# 初始化合约的止损止盈字典
if instrument_id not in self.stop_order_dict:
self.stop_order_dict[instrument_id] = {
'long': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0},
'short': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0}
}
# 获取当前值
current_values = self.stop_order_dict[instrument_id][direction].copy()
# 更新值
if position is not None:
self.stop_order_dict[instrument_id][direction]['position'] = position
if entry_price is not None:
self.stop_order_dict[instrument_id][direction]['entry_price'] = entry_price
if stop_loss is not None:
self.stop_order_dict[instrument_id][direction]['stop_loss'] = stop_loss
if take_profit is not None:
self.stop_order_dict[instrument_id][direction]['take_profit'] = take_profit
if trailing_stop is not None:
self.stop_order_dict[instrument_id][direction]['trailing_stop'] = trailing_stop
# 记录变化值
updated_values = self.stop_order_dict[instrument_id][direction]
change_log = f"更新{instrument_id} {direction}头寸止损止盈: "
changes = []
if position is not None and position != current_values['position']:
changes.append(f"仓位: {current_values['position']} -> {position}")
if entry_price is not None and entry_price != current_values['entry_price']:
changes.append(f"入场价: {current_values['entry_price']} -> {entry_price}")
if stop_loss is not None and stop_loss != current_values['stop_loss']:
changes.append(f"止损价: {current_values['stop_loss']} -> {stop_loss}")
if take_profit is not None and take_profit != current_values['take_profit']:
changes.append(f"止盈价: {current_values['take_profit']} -> {take_profit}")
if trailing_stop is not None and trailing_stop != current_values['trailing_stop']:
changes.append(f"跟踪止损价: {current_values['trailing_stop']} -> {trailing_stop}")
if changes:
change_log += ", ".join(changes)
STOPLOSS_LOGGER.info(change_log)
# 保存到文件
self.save_stop_orders_to_file(instrument_id)
def save_stop_orders_to_file(self, instrument_id):
"""将止盈止损信息保存到文件"""
# 使用完整的合约代码
symbol = str(instrument_id)
folder_path = "traderdata"
file_path = os.path.join(folder_path, f"{symbol}_stops.json")
# 如果文件夹不存在则创建
if not os.path.exists(folder_path):
os.makedirs(folder_path)
# 保存字典到JSON文件
with open(file_path, 'w') as f:
json.dump(self.stop_order_dict.get(instrument_id, {}), f, indent=4)
# 获取该合约的止盈止损信息
stops = self.stop_order_dict.get(instrument_id, {
'long': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0},
'short': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0}
})
# 打印详细信息
print(f"\n===== 已更新止盈止损信息: {instrument_id} =====")
print(f"多头持仓: {stops['long']['position']}")
print(f"多头入场价: {stops['long']['entry_price']}")
print(f"多头止损价: {stops['long']['stop_loss']}")
print(f"多头止盈价: {stops['long']['take_profit']}")
print(f"多头跟踪止损: {stops['long']['trailing_stop']}")
print(f"空头持仓: {stops['short']['position']}")
print(f"空头入场价: {stops['short']['entry_price']}")
print(f"空头止损价: {stops['short']['stop_loss']}")
print(f"空头止盈价: {stops['short']['take_profit']}")
print(f"空头跟踪止损: {stops['short']['trailing_stop']}")
print("======================================\n")
def load_stop_orders_from_file(self, instrument_id):
"""从文件加载止盈止损信息"""
# 如果合约ID已经在字典中直接返回避免重复加载
if instrument_id in self.stop_order_dict:
return True
# 使用完整的合约代码
symbol = str(instrument_id)
folder_path = "traderdata"
file_path = os.path.join(folder_path, f"{symbol}_stops.json")
if os.path.exists(file_path):
try:
with open(file_path, 'r') as f:
stops_data = json.load(f)
# 更新字典
self.stop_order_dict[instrument_id] = stops_data
print(f"首次加载止盈止损信息: {instrument_id}")
return True
except Exception as e:
print(f"加载止盈止损信息失败: {e}")
# 如果文件不存在,初始化空字典结构
if instrument_id not in self.stop_order_dict:
self.stop_order_dict[instrument_id] = {
'long': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0},
'short': {'position': 0, 'entry_price': 0, 'stop_loss': 0, 'take_profit': 0, 'trailing_stop': 0}
}
print(f"初始化止盈止损结构: {instrument_id}")
return False
def check_stop_conditions(self, data):
"""检查是否满足止盈止损条件"""
instrument_id = data['InstrumentID'].decode()
# 如果该合约不在止盈止损字典中,直接返回
if instrument_id not in self.stop_order_dict:
return
current_bid = float(data['BidPrice1']) # 当前买价
current_ask = float(data['AskPrice1']) # 当前卖价
stops = self.stop_order_dict[instrument_id]
# 检查多头止盈止损
if stops['long']['position'] > 0:
entry_price = stops['long']['entry_price'] # 获取开仓价
# 检查止损
if stops['long']['stop_loss'] > 0 and current_bid <= stops['long']['stop_loss']:
print(f"触发多头止损: {instrument_id}, 价格: {current_bid}, 止损价: {stops['long']['stop_loss']}")
self.insert_order(data['ExchangeID'], data['InstrumentID'], current_bid-self.py,
stops['long']['position'], b'1', b'3')
# 清空多头持仓信息
self.clear_position_info(instrument_id, 'long')
self.pos = 0 # 更新全局持仓状态
# 检查跟踪止损 - 新增开仓价判断条件
elif stops['long']['trailing_stop'] > 0 and current_bid < stops['long']['trailing_stop'] and current_bid > entry_price:
print(f"触发多头跟踪止损: {instrument_id}, 价格: {current_bid}, 跟踪止损价: {stops['long']['trailing_stop']}, 开仓价: {entry_price}")
self.insert_order(data['ExchangeID'], data['InstrumentID'], current_bid-self.py,
stops['long']['position'], b'1', b'3')
# 清空多头持仓信息
self.clear_position_info(instrument_id, 'long')
self.pos = 0 # 更新全局持仓状态
# 检查止盈
elif stops['long']['take_profit'] > 0 and current_bid >= stops['long']['take_profit']:
print(f"触发多头止盈: {instrument_id}, 价格: {current_bid}, 止盈价: {stops['long']['take_profit']}")
self.insert_order(data['ExchangeID'], data['InstrumentID'], current_bid-self.py,
stops['long']['position'], b'1', b'3')
# 清空多头持仓信息
self.update_stop_order_dict(instrument_id, 'long', 0, 0, 0, 0)
self.pos = 0 # 更新全局持仓状态
# 更新跟踪止损价 - 只在价格上涨且大于开仓价时更新
elif stops['long']['trailing_stop'] > 0 and current_bid > entry_price:
# 只有当前价格比之前设置的跟踪止损价高一定幅度时才更新
new_trailing_stop = current_bid * (1 - self.trailing_stop_percent)
if new_trailing_stop > stops['long']['trailing_stop']:
self.update_stop_order_dict(instrument_id, 'long', None, None, None, None, new_trailing_stop)
print(f"更新多头跟踪止损: {instrument_id}, 新止损价: {new_trailing_stop}, 开仓价: {entry_price}")
# 检查空头止盈止损
if stops['short']['position'] > 0:
entry_price = stops['short']['entry_price'] # 获取开仓价
# 检查止损
if stops['short']['stop_loss'] > 0 and current_ask >= stops['short']['stop_loss']:
print(f"触发空头止损: {instrument_id}, 价格: {current_ask}, 止损价: {stops['short']['stop_loss']}")
self.insert_order(data['ExchangeID'], data['InstrumentID'], current_ask+self.py,
stops['short']['position'], b'0', b'3')
# 清空空头持仓信息
self.update_stop_order_dict(instrument_id, 'short', 0, 0, 0, 0, 0)
self.pos = 0 # 更新全局持仓状态
# 检查跟踪止损 - 新增开仓价判断条件
elif stops['short']['trailing_stop'] > 0 and current_ask > stops['short']['trailing_stop'] and current_ask < entry_price:
print(f"触发空头跟踪止损: {instrument_id}, 价格: {current_ask}, 跟踪止损价: {stops['short']['trailing_stop']}, 开仓价: {entry_price}")
self.insert_order(data['ExchangeID'], data['InstrumentID'], current_ask+self.py,
stops['short']['position'], b'0', b'3')
# 清空空头持仓信息
self.clear_position_info(instrument_id, 'short')
self.pos = 0 # 更新全局持仓状态
# 检查止盈
elif stops['short']['take_profit'] > 0 and current_ask <= stops['short']['take_profit']:
print(f"触发空头止盈: {instrument_id}, 价格: {current_ask}, 止盈价: {stops['short']['take_profit']}")
self.insert_order(data['ExchangeID'], data['InstrumentID'], current_ask+self.py,
stops['short']['position'], b'0', b'3')
# 清空空头持仓信息
self.update_stop_order_dict(instrument_id, 'short', 0, 0, 0, 0, 0)
self.pos = 0 # 更新全局持仓状态
# 更新跟踪止损价 - 只在价格下跌且小于开仓价时更新
elif stops['short']['trailing_stop'] > 0 and current_ask < entry_price:
# 只有当前价格比之前设置的跟踪止损价低一定幅度时才更新
new_trailing_stop = current_ask * (1 + self.trailing_stop_percent)
if new_trailing_stop < stops['short']['trailing_stop'] or stops['short']['trailing_stop'] == 0:
self.update_stop_order_dict(instrument_id, 'short', None, None, None, None, new_trailing_stop)
print(f"更新空头跟踪止损: {instrument_id}, 新止损价: {new_trailing_stop}, 开仓价: {entry_price}")
#保存数据
def clear_position_info(self, instrument_id, direction):
"""
清空指定合约和方向的持仓信息
Args:
instrument_id: 合约ID
direction: 方向 'long''short''all'
"""
# 确保合约ID在字典中存在
if instrument_id not in self.stop_order_dict:
return
if direction == 'long' or direction == 'all':
# 清空多头持仓信息
self.stop_order_dict[instrument_id]['long'] = {
'position': 0,
'entry_price': 0,
'stop_loss': 0,
'take_profit': 0,
'trailing_stop': 0
}
# 兼容旧代码
if direction == 'long':
self.long_trailing_stop_price = 0
self.sl_long_price = 0
if direction == 'short' or direction == 'all':
# 清空空头持仓信息
self.stop_order_dict[instrument_id]['short'] = {
'position': 0,
'entry_price': 0,
'stop_loss': 0,
'take_profit': 0,
'trailing_stop': 0
}
# 兼容旧代码
if direction == 'short':
self.short_trailing_stop_price = 0
self.sl_shor_price = 0
# 同步全局持仓状态
if direction == 'all':
self.pos = 0
self.long_trailing_stop_price = 0
self.short_trailing_stop_price = 0
self.sl_long_price = 0
self.sl_shor_price = 0
# 保存到文件
self.save_stop_orders_to_file(instrument_id)
self.save_to_csv(instrument_id)
print(f"已清空{instrument_id}{direction}持仓信息")
# 修改call_deepseek_model函数移除JSON格式输出的要求改为从普通文本响应中提取交易信号。
def call_deepseek_model(data_df, trader_instance, max_retries=2):
"""
调用qwq-32b大模型分析订单流数据
Args:
data_df: 包含订单流数据的DataFrame
trader_instance: MyTrader实例用于访问持仓信息
max_retries: 最大重试次数
Returns:
dict: 包含交易信号的字典
"""
# 直接从环境变量获取API密钥
api_key = GLOBAL_LLM_CONFIG.get('api_key')
base_url = GLOBAL_LLM_CONFIG.get('base_url')
model_name = GLOBAL_LLM_CONFIG.get('model_name')
# 检查API密钥是否为空
if not api_key:
print("错误: API密钥未设置请在main函数中配置GLOBAL_LLM_CONFIG['api_key']")
return {"action": "不操作", "reason": "API密钥未设置", "confidence": 0, "stop_loss": 0, "take_profit": 0}
# 将DataFrame转换为可读性好的文本传递trader_instance
data_text = trader_instance.format_data_for_llm(data_df)
# 构建提示词
prompt = f"""
作为专注于趋势交易的期货交易员,基于以下市场数据做出交易决策:
{data_text}
请重点考虑以下关键因素:
1. 均线系统(5、10、20均线)的排列状态和金叉死叉信号
2. 价格是否处于明确的上升或下降通道中
3. 关键阻力位和支撑位的突破情况及其确认
4. 成交量是否配合价格趋势(上涨时放量,下跌时同样放量)
5. 动量指标(MACD、KDJ等)的走势和信号
6. 订单流趋势与主力资金方向的一致性
7. 市场多空力量对比的持续性变化
8. 当前趋势的强度与持续时间评估
【风险控制原则】:
- 严格控制每笔交易亏损不超过本金的1%
- 趋势交易的止损位应设置在次级回调位或关键技术位之下通常为开仓价的0.3%-0.8%
- 使用移动止损机制跟踪趋势发展,锁定利润
- 避免在盘整区间内频繁交易,等待明确的突破信号
请根据市场状态和持仓情况,给出明确的交易指令:
1. 交易方向: 不操作/开多/开空/平多/平空/平多开空/平空开多
2. 执行理由: 详细说明趋势特征、突破信号、动量变化和持续性评估
3. 置信度: 1-10的数字反映趋势信号的清晰度必须是整数
4. 止损价: 明确的止损价格(必须是数字),设置在关键支撑/阻力位附近
5. 止盈价: 明确的首个止盈目标(必须是数字)应至少是止损距离的1.5倍
6. 跟踪止损百分比: 0.001-0.01之间的数字,用于设置移动止损追踪趋势
请按以下格式返回交易决策,格式必须严格匹配:
action: 不操作/开多/开空/平多/平空/平多开空/平空开多
reason: 交易理由
confidence: 置信度(1-10)
stop_loss: 止损价
take_profit: 止盈价
trailing_percent: 跟踪止损百分比(0.001-0.01)
"""
system_prompt = {"role": "system",
"content": "你是一位专注于趋势交易的期货交易员,擅长识别和追踪强势趋势。你的交易风格注重动量和突破信号,善于在确认趋势后顺势而为,合理管理风险的同时最大化捕捉趋势利润。你特别关注均线系统、趋势线、成交量确认和动量指标,能精准判断趋势的强度和持续性。你擅长使用移动止损保护利润,在趋势延续时持有头寸,在趋势减弱时及时退出。请根据指定格式返回交易决策。"}
# 添加重试机制
retries = 0
while retries <= max_retries:
try:
# 如果不是第一次尝试,输出重试信息
if retries > 0:
print(f"正在进行第 {retries} 次重试...")
# 调试信息
print(f"使用API参数: base_url={base_url}, model={model_name}")
# 添加明显的提示信息表示正在调用大模型API
print("\n============================================")
print("【正在调用大模型API进行交易分析请稍候...】")
print("============================================\n")
# 记录开始时间
api_start_time = time.time()
# 使用OpenAI客户端格式调用API
client = OpenAI(api_key=api_key, base_url=base_url)
# 发起流式请求
response = client.chat.completions.create(
model=model_name,
messages=[
system_prompt,
{"role": "user", "content": prompt}
],
temperature=0.1,
stream=False, # 不启用流式模式
max_tokens=8192,
timeout=60
)
# 提取模型响应内容
model_response = response.choices[0].message.content
# 计算耗时
api_elapsed = time.time() - api_start_time
print(f"\n\n模型响应总耗时: {api_elapsed:.2f}")
print("完整响应前100字符: " + model_response[:100] + "...")
# 添加明显的提示信息表示API调用已完成
print("\n============================================")
print("【大模型API调用完成】")
print("============================================\n")
# 从文本中解析出交易信号
try:
trading_signal = parse_trading_signal(model_response)
return trading_signal
except Exception as parse_err:
print(f"解析模型响应出错: {parse_err}")
# 尝试从自由文本中提取关键信息
return extract_trading_signal_from_text(model_response)
except Exception as e:
retries += 1
if retries <= max_retries:
print(f"调用大模型API出错: {e}")
print(f"将在3秒后重试...")
time.sleep(3) # 等待3秒后重试
else:
print(f"已达到最大重试次数 ({max_retries})调用大模型API失败")
print(f"错误详情: {e}")
print("\n请检查以下几点:")
print("1. API密钥格式是否正确应以'sk-'开头")
print("2. 确认已安装最新版本的openai库: pip install --upgrade openai")
print("3. 确认您的API密钥对应的模型是否为deepseek-reasoner")
# 返回默认的不操作信号
return {"action": "不操作", "reason": f"API调用失败: {str(e)[:100]}", "confidence": 0, "stop_loss": 0, "take_profit": 0}
# 如果所有重试都失败了,返回默认值
return {"action": "不操作", "reason": "API调用重试耗尽", "confidence": 0, "stop_loss": 0, "take_profit": 0}
def parse_trading_signal(text):
"""
从文本格式的模型响应中解析出交易信号
Args:
text: 模型响应文本
Returns:
dict: 包含交易信号的字典
"""
lines = text.strip().split('\n')
trading_signal = {}
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
key = key.strip().lower()
value = value.strip()
if key == 'action':
trading_signal['action'] = value
elif key == 'reason':
trading_signal['reason'] = value
elif key == 'confidence':
try:
trading_signal['confidence'] = int(value)
except ValueError:
# 尝试从文本中提取数字
import re
match = re.search(r'\d+', value)
if match:
trading_signal['confidence'] = int(match.group())
else:
trading_signal['confidence'] = 0
elif key == 'stop_loss':
try:
trading_signal['stop_loss'] = float(value)
except ValueError:
# 尝试从文本中提取数字
import re
match = re.search(r'\d+(\.\d+)?', value)
if match:
trading_signal['stop_loss'] = float(match.group())
else:
trading_signal['stop_loss'] = 0
elif key == 'take_profit':
try:
trading_signal['take_profit'] = float(value)
except ValueError:
# 尝试从文本中提取数字
import re
match = re.search(r'\d+(\.\d+)?', value)
if match:
trading_signal['take_profit'] = float(match.group())
else:
trading_signal['take_profit'] = 0
elif key == 'trailing_percent' or key == 'trailing_stop_percent':
try:
value_float = float(value)
# 确保值在合理范围内
if 0.0005 <= value_float <= 0.015:
trading_signal['trailing_percent'] = value_float
else:
# 如果值不在预期范围内,尝试判断是否使用了百分比格式
if value_float >= 0.05 and value_float <= 1.5:
# 可能是百分比格式,转换为小数
trading_signal['trailing_percent'] = value_float / 100
else:
# 设置为默认值
trading_signal['trailing_percent'] = 0.005
except ValueError:
# 尝试从文本中提取数字
import re
match = re.search(r'\d+(\.\d+)?', value)
if match:
try:
trailing_value = float(match.group())
if trailing_value >= 0.5 and trailing_value <= 10:
trading_signal['trailing_percent'] = trailing_value / 100
else:
trading_signal['trailing_percent'] = trailing_value
except:
trading_signal['trailing_percent'] = 0.02
else:
trading_signal['trailing_percent'] = 0.02
# 检查是否有缺失的字段,如果有,设置默认值
if 'action' not in trading_signal:
trading_signal['action'] = '不操作'
if 'reason' not in trading_signal:
trading_signal['reason'] = '未提供理由'
if 'confidence' not in trading_signal:
trading_signal['confidence'] = 0
if 'stop_loss' not in trading_signal:
trading_signal['stop_loss'] = 0
if 'take_profit' not in trading_signal:
trading_signal['take_profit'] = 0
if 'trailing_percent' not in trading_signal:
trading_signal['trailing_percent'] = 0.005 # 修改默认值
return trading_signal
def extract_trading_signal_from_text(text):
"""
从自由格式文本中尝试提取交易信号
Args:
text: 模型响应文本
Returns:
dict: 包含交易信号的字典
"""
# 默认交易信号
trading_signal = {
"action": "不操作",
"reason": "无法解析模型响应",
"confidence": 0,
"stop_loss": 0,
"take_profit": 0,
"trailing_percent": 0.005 # 更新默认的跟踪止损百分比
}
# 尝试判断交易方向
text_lower = text.lower()
if "平多开空" in text_lower:
trading_signal["action"] = "平多开空"
elif "平空开多" in text_lower:
trading_signal["action"] = "平空开多"
elif "开多" in text_lower:
trading_signal["action"] = "开多"
elif "开空" in text_lower:
trading_signal["action"] = "开空"
elif "平多" in text_lower:
trading_signal["action"] = "平多"
elif "平空" in text_lower:
trading_signal["action"] = "平空"
elif "不操作" in text_lower or "观望" in text_lower:
trading_signal["action"] = "不操作"
# 尝试从文本中提取置信度
import re
confidence_matches = re.findall(r'置信度[:]\s*(\d+)', text_lower)
if confidence_matches:
try:
trading_signal["confidence"] = int(confidence_matches[0])
except ValueError:
pass
# 尝试提取止损价
stop_loss_matches = re.findall(r'止损价[:]\s*(\d+(\.\d+)?)', text_lower)
if stop_loss_matches:
try:
trading_signal["stop_loss"] = float(stop_loss_matches[0][0])
except ValueError:
pass
# 尝试提取止盈价
take_profit_matches = re.findall(r'止盈价[:]\s*(\d+(\.\d+)?)', text_lower)
if take_profit_matches:
try:
trading_signal["take_profit"] = float(take_profit_matches[0][0])
except ValueError:
pass
# 尝试提取跟踪止损百分比
trailing_matches = re.findall(r'跟踪止损百分比[:]\s*(\d+(\.\d+)?)', text_lower)
if not trailing_matches:
trailing_matches = re.findall(r'跟踪百分比[:]\s*(\d+(\.\d+)?)', text_lower)
if trailing_matches:
try:
trailing_value = float(trailing_matches[0][0])
# 判断是否为百分比格式
if trailing_value >= 0.5 and trailing_value <= 10:
trading_signal["trailing_percent"] = trailing_value / 100
else:
trading_signal["trailing_percent"] = trailing_value
except ValueError:
pass
# 提取理由
reason_matches = re.findall(r'理由[:]\s*(.*?)(?=\n|$)', text_lower)
if reason_matches:
trading_signal["reason"] = reason_matches[0]
return trading_signal
# 修改run_trader函数改为接收统一的配置字典
def run_trader(broker_id, td_server, investor_id, password, app_id, auth_code, md_queue=None, page_dir='', private_resume_type=2, public_resume_type=2, config=None):
# 设置全局变量
global GLOBAL_LLM_CONFIG
# 如果传入了配置字典,直接使用
if config:
# 使用deepcopy避免潜在的引用问题
GLOBAL_LLM_CONFIG = copy.deepcopy(config)
# else:
# # 确保有默认值
# if 'bar_resample_rule' not in GLOBAL_LLM_CONFIG:
# GLOBAL_LLM_CONFIG['bar_resample_rule'] = '1T'
# if 'load_history' not in GLOBAL_LLM_CONFIG:
# GLOBAL_LLM_CONFIG['load_history'] = False
# if 'history_rows' not in GLOBAL_LLM_CONFIG:
# GLOBAL_LLM_CONFIG['history_rows'] = 1000
# if 'trader_rows' not in GLOBAL_LLM_CONFIG:
# GLOBAL_LLM_CONFIG['trader_rows'] = 10
my_trader = MyTrader(
broker_id,
td_server,
investor_id,
password,
app_id,
auth_code,
md_queue,
page_dir,
private_resume_type,
public_resume_type
)
# 设置历史数据加载参数
my_trader.load_history = GLOBAL_LLM_CONFIG['load_history']
my_trader.history_rows = GLOBAL_LLM_CONFIG['history_rows']
my_trader.trader_rows = GLOBAL_LLM_CONFIG['trader_rows']
my_trader.Join()
def ceshiapi(api_key):
# 测试API连接
print(f"测试API连接使用密钥: {api_key[:5]}...{api_key[-5:]}")
try:
client = OpenAI(api_key=api_key, base_url=GLOBAL_LLM_CONFIG['base_url'] )
response = client.chat.completions.create(
model=GLOBAL_LLM_CONFIG['model_name'],
messages=[
{"role": "system", "content": "你是一个助手"},
{"role": "user", "content": "测试"}
],
stream=False, # 新增此行
max_tokens=10
)
print(f"API连接测试成功")
except Exception as e:
print(f"API连接测试失败: {e}")
import traceback
traceback.print_exc()
# 修改主函数中的配置和调用方式
if __name__ == '__main__':
#公众号松鼠Quant
#主页www.quant789.com
#本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!
#版权归松鼠Quant所有禁止转发、转卖源码违者必究。
#注意运行前请先安装好algoplus,
# pip install AlgoPlus
# 配置大模型参数 - 直接通过环境变量设置
# 设置您的实际API密钥
api_key = "" # 请确保使用有效的密钥
# 同时设置到全局变量中
GLOBAL_LLM_CONFIG['api_key'] = api_key
GLOBAL_LLM_CONFIG['base_url'] = "https://api.gptsapi.net/v1"
GLOBAL_LLM_CONFIG['model_name'] = "o3-mini-2025-01-31"
ceshiapi(api_key)# 测试API连接
# 将所有配置参数整合到GLOBAL_LLM_CONFIG中
# 历史数据加载配置
GLOBAL_LLM_CONFIG['load_history'] = True # 是否加载历史数据
GLOBAL_LLM_CONFIG['history_rows'] = 50 # 加载历史文件中数据量
GLOBAL_LLM_CONFIG['trader_rows'] = 10 # 当tader_df里的数据大于10行时开始计算指标及触发AI模型
# 设置K线时间粒度
GLOBAL_LLM_CONFIG['bar_resample_rule'] = '3T' # 默认3分钟K线可以修改为'5T'(5分钟),'15T'(15分钟)等,也可用'5S'(5秒),'30S'(30秒)或'1H'(1小时),'2H'(2小时),'4H'(4小时),'1D'(1天)
#用simnow模拟不要忘记屏蔽下方实盘的future_account字典
future_account = get_simulate_account(
investor_id='', # simnow账户注意是登录账户的IDSIMNOW个人首页查看
password='', # simnow密码
server_name='电信1', # 电信1、电信2、移动、TEST、N视界
subscribe_list=[b'au2506'], # 合约列表
)
# #实盘用这个不要忘记屏蔽上方simnow的future_account字典
# future_account = FutureAccount(
# broker_id='', # 期货公司BrokerID
# server_dict={'TDServer': "ip:port", 'MDServer': 'ip:port'}, # TDServer为交易服务器MDServer为行情服务器。服务器地址格式为"ip:port。"
# reserve_server_dict={}, # 备用服务器地址
# investor_id='', # 账户
# password='', # 密码
# app_id='simnow_client_test', # 认证使用AppID
# auth_code='0000000000000000', # 认证使用授权码
# subscribe_list=[b'rb2405'], # 订阅合约列表
# md_flow_path='./log', # MdApi流文件存储地址默认MD_LOCATION
# td_flow_path='./log', # TraderApi流文件存储地址默认TD_LOCATION
# )
print('开始',len(future_account.subscribe_list))
# 共享队列
share_queue = Queue(maxsize=200)
# 行情进程
md_process = Process(target=run_tick_engine, args=(future_account, [share_queue]))
# 使用深拷贝创建一个完全独立的配置副本
import copy
config_copy = copy.deepcopy(GLOBAL_LLM_CONFIG)
# 交易进程 - 增加历史数据加载参数和K线时间粒度参数
trader_process = Process(target=run_trader, args=(
future_account.broker_id,
future_account.server_dict['TDServer'],
future_account.investor_id,
future_account.password,
future_account.app_id,
future_account.auth_code,
share_queue, # 队列
future_account.td_flow_path,
2, # private_resume_type
2, # public_resume_type
config_copy, # 传递独立的配置副本,避免潜在的引用问题
))
md_process.start()
trader_process.start()
md_process.join()
trader_process.join()