接入交易行情数据后,也可以使用模拟模式。
在该模式下,策略等待行情推送,收到行情推送后,更新交易数据,推动策略循环。
流程路线:run_sim -> _debug_sim -> subscribe_data -> callback -> upcoming_data -> on_bar。
通过订阅行情数据,并在回调函数中更新数据、状态,推动事件循环。
def run_sim(self):
self._debug_sim()
def _debug_sim(self):
self.running_mode = 'sim'
# 分钟级别数据
if self.frequence.endswith('min'):
# 如果为分钟线,通过通达信引擎获取数据(在当前时刻之前的当日数据)
if isinstance(self.code, str):
self._old_data = QA.QA_fetch_get_future_min('tdx', self.code.upper(), QA.QA_util_get_last_day(
QA.QA_util_get_real_date(str(datetime.date.today()))), str(datetime.datetime.now()), self.frequence)[:-1].set_index(['datetime', 'code'])
self._old_data = self._old_data.assign(volume=self._old_data.trade).loc[:, [
'open', 'high', 'low', 'close', 'volume']]
else:
self._old_data = pd.concat([QA.QA_fetch_get_future_min('tdx', item.upper(), QA.QA_util_get_last_day(
QA.QA_util_get_real_date(str(datetime.date.today()))), str(datetime.datetime.now()), self.frequence)[:-1].set_index(['datetime', 'code']) for item in self.code], sort=False)
self._old_data = self._old_data.assign(volume=self._old_data.trade).loc[:, [
'open', 'high', 'low', 'close', 'volume']]
else:
# 如果为日线,则不用更新
self._old_data = pd.DataFrame()
# 模拟数据存储于QREALTIME数据库
self.database = pymongo.MongoClient(mongo_ip).QAREALTIME
self.client = self.database.account
self.subscriber_client = self.database.subscribe
# 非回测使用QIFI_Account
self.acc = QIFI_Account(
username=self.strategy_id, password=self.strategy_id, trade_host=mongo_ip, init_cash=self.init_cash)
self.acc.initial()
self.acc.on_sync = self.on_sync
self.pub = publisher_routing(exchange='QAORDER_ROUTER', host=self.trade_host,
port=self.trade_port, user=self.trade_user, password=self.trade_password)
self.pubacc = publisher_topic(exchange='QAAccount', host=self.trade_host,
port=self.trade_port, user=self.trade_user, password=self.trade_password)
# 订阅行情数据
if isinstance(self.code, str):
self.subscribe_data(self.code.lower(), self.frequence, self.data_host,
self.data_port, self.data_user, self.data_password, self.model)
else:
self.subscribe_multi(self.code, self.frequence, self.data_host,
self.data_port, self.data_user, self.data_password, self.model)
print('account {} start sim'.format(self.strategy_id))
self.database.strategy_schedule.job_control.update(
{'strategy_id': self.strategy_id},
{'strategy_id': self.strategy_id, 'taskid': self.taskid,
'filepath': os.path.abspath(__file__), 'status': 200}, upsert=True)
订阅行情数据代码如下,实现对每个代码监听来自 'realtime_stock_XXX’的消息,并设置回调函数
def subscribe_data(self, code, frequence, data_host, data_port, data_user, data_password):
"""[summary]
Arguments:
code {[type]} -- [description]
frequence {[type]} -- [description]
"""
self.sub = subscriber_topic(exchange='realtime_stock_{}'.format(
frequence), host=data_host, port=data_port, user=data_user, password=data_password, routing_key='')
for item in code:
self.sub.add_sub(exchange='realtime_stock_{}'.format(
frequence), routing_key=item)
self.sub.callback = self.callback
回调函数,完成消息队列数据到bar数据的转换,并推送事件。
def callback(self, a, b, c, body):
"""在strategy的callback中,我们需要的是
1. 更新数据
2. 更新bar
3. 更新策略状态
4. 推送事件
Arguments:
a {[type]} -- [description]
b {[type]} -- [description]
c {[type]} -- [description]
body {[type]} -- [description]
"""
# body是消息的内容,如{'datetime':'2023-06-01 09:40:00','code':rb2305,'open':4500...}
self.new_data = json.loads(str(body, encoding='utf-8'))
self.latest_price[self.new_data['code']] = self.new_data['close']
if self.dt != str(self.new_data['datetime'])[0:16]:
# [0:16]是分钟线位数
self.dt = str(self.new_data['datetime'])[0:16]
self.isupdate = True
# 向account发送on_price_change事件,更新账户状态
self.acc.on_price_change(self.new_data['code'], self.new_data['close'])
# .loc[:, ['open', 'high', 'low', 'close', 'volume', 'tradetime']]
bar = pd.DataFrame([self.new_data]).set_index(['datetime', 'code'])
now = datetime.datetime.now()
if now.hour == 20 and now.minute == 59 and now.second < 10:
self.daily_func()
time.sleep(10)
# res = self.job_control.find_one(
# {'strategy_id': self.strategy_id, 'strategy_id': self.strategy_id})
# self.control_status(res)
self.running_time = self.new_data['datetime']
# 转到upcoming_data
self.upcoming_data(bar)
def upcoming_data(self, new_bar):
"""upcoming_bar :
在这一步中, 我们主要进行的是
1. 更新self._market_data
2. 更新账户
3. 更新持仓
4. 通知on_bar
Arguments:
new_bar {pd.DataFrame} -- [description]
"""
code = new_bar.index.levels[1][0]
if len(self._old_data) > 0:
self._market_data = pd.concat(
[self._old_data, new_bar], sort=False)
else:
self._market_data = new_bar
# QA.QA_util_log_info(self._market_data)
if self.isupdate:
self.update()
self.isupdate = False
# 更新账户和持仓
self.update_account()
if isinstance(self.code, str):
self.positions.on_price_change(float(self.latest_price[code]))
else:
for item in self.code:
self.acc.get_position(item).on_price_change(
float(self.latest_price[code]))
# 调用on_bar事件,并将记录转换为json格式。
self.on_bar(json.loads(
new_bar.reset_index().to_json(orient='records'))[0])
所以,只需要另一端能够发送行情,这里就能监听到对应代码的行情并进行处理。
定义简单策略,仅实现行情的打印
from QAStrategy import QAStrategyCTABase
class Strategy(QAStrategyCTABase):
def on_bar(self, bar):
print(bar)
s = Strategy(code='rb2310', frequence='1min', strategy_id= 'admin' )
s.run_sim()
需要使用QAPUBSU模块
pip install quantaxis_pubsub
推送行情的代码:
import json
from QAPUBSUB.producer import publisher,publisher_routing
pub=publisher(exchange='realtime_1min_rb2310') # exchange=realtime_频率_代码
#手动发送行情
pub.pub(json.dumps({'code':'rb2309','datetime':'2023-06-07 11:13:22','open':3660,'close':3660,'high':3693,'low':3620}))
输出如下,会新增一条json记录:
因此,可以对接到自己的行情源,本地的、实时的、模拟的,通过pub发送行情。
QAStrategy只是接收行情并处理,但行情的来源还得通过其他途径,QUANTAXIS中qarealtime_collector库提供了该功能的实现。
功能实现上,需要一个tick发生器,其次需要一个tick收集器,然后将tick 重采样到对应的周期。
pip install qarealtime_collector #行情收集
pip install quantaxis-randomprice # 生成随机行情,被 qarealtime_collector 模块使用
代码如下,开启三个终端分别运行:
from QARealtimeCollector.collectors.simmarket import QARTC_RandomTick
gen=QARTC_RandomTick('rb2310','2023-06-08',3650,1)
gen.start()
from QARealtimeCollector.collectors.simcollector import QARTC_CTPTickCollector
c=QARTC_CTPTickCollector('rb2310')
c.start()
from QARealtimeCollector.datahandler import QARTC_Resampler
resampler=QARTC_Resampler(code='rb2310',freqence='1min')
resampler.start()