QUANTAXIS探索(八)量化策略实现类QAStrategy的模拟模式

接入交易行情数据后,也可以使用模拟模式。
在该模式下,策略等待行情推送,收到行情推送后,更新交易数据,推动策略循环。

模拟交易:run_sim

流程路线:run_sim -> _debug_sim -> subscribe_data -> callback -> upcoming_data -> on_bar。
通过订阅行情数据,并在回调函数中更新数据、状态,推动事件循环。

run_sim/_debug_sim

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)

subscribe_data

订阅行情数据代码如下,实现对每个代码监听来自 '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

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)

upcoming_data

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()

程序输出如下,并等待行情推送:
QUANTAXIS探索(八)量化策略实现类QAStrategy的模拟模式_第1张图片

自定义行情推送

需要使用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记录:
QUANTAXIS探索(八)量化策略实现类QAStrategy的模拟模式_第2张图片
因此,可以对接到自己的行情源,本地的、实时的、模拟的,通过pub发送行情。

使用qarealtime_collector模拟行情

QAStrategy只是接收行情并处理,但行情的来源还得通过其他途径,QUANTAXIS中qarealtime_collector库提供了该功能的实现。
功能实现上,需要一个tick发生器,其次需要一个tick收集器,然后将tick 重采样到对应的周期。

pip install qarealtime_collector  #行情收集
pip install quantaxis-randomprice # 生成随机行情,被 qarealtime_collector  模块使用

代码如下,开启三个终端分别运行:

开启tick生成器

from QARealtimeCollector.collectors.simmarket import QARTC_RandomTick
gen=QARTC_RandomTick('rb2310','2023-06-08',3650,1)
gen.start()

输出示例:
QUANTAXIS探索(八)量化策略实现类QAStrategy的模拟模式_第3张图片

开启tick收集器

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()

输出示例:
在这里插入图片描述
策略端的输出:
在这里插入图片描述

你可能感兴趣的:(python,金融)