def __init__(self, feature, quantile=0.998, init_cash=50000000, rolling=5, portfolioname='feature', mongo_ip=mongo_ip, clickhouse_host=clickhouse_ip, clickhouse_port=clickhouse_port, clickhouse_user=clickhouse_user, clickhouse_password=clickhouse_password) -> None: """ feature --> standard QAFeature quantile -> prue long only upper quantile can be selected init_cash --> account backtest initcash rolling --> dategap for rolling sell clickhouse should be save data first mongoip --> use to save qifiaccount """ self.feature = feature.reset_index().drop_duplicates( ['date', 'code']).set_index(['date', 'code']).sort_index().dropna() self.featurename = feature.columns[0] self.start = str(self.feature.index.levels[0][0])[0:10] self.end = str(self.feature.index.levels[0][-1])[0:10] self.codelist = self.feature.index.levels[1].tolist() self.client = QACKClient(host=clickhouse_host, port=clickhouse_port, user=clickhouse_user, password=clickhouse_password) self.quantile = quantile self.preload = self.feature.groupby( level=0, as_index=False, group_keys=False).apply(lambda x: self.slice_feature(x)) self.datacenter = self.client.get_stock_day_qfq_adv( self.codelist, self.start, self.end) self.closepanel = self.datacenter.closepanel.bfill( ) ## 向前复权 匹配股票停牌模式 使用复牌后第一个收盘价卖出 self.account = QIFI_Account(init_cash=init_cash, username='******'.format( self.featurename, uuid4()), broker_name='feature', portfolioname=portfolioname, password='******', nodatabase=False, model='BACKTEST', trade_host=mongo_ip) self.tradetable = {} self.rolling = rolling self.cashpre = init_cash / rolling self.account.initial()
def _debug_sim(self): self.running_mode = 'sim' self._old_data = QA.QA_fetch_stock_min( self.code, QA.QA_util_get_last_day( QA.QA_util_get_real_date(str(datetime.date.today()))), str(datetime.datetime.now()), format='pd', frequence=self.frequence).set_index(['datetime', 'code']) self._old_data = self._old_data.loc[:, [ 'open', 'high', 'low', 'close', 'volume' ]] self.database = pymongo.MongoClient(mongo_ip).QAREALTIME self.client = self.database.account self.subscriber_client = self.database.subscribe self.acc = QIFI_Account(username=self.strategy_id, password=self.strategy_id, trade_host=mongo_ip) self.acc.initial() self.pub = publisher_routing(exchange='QAORDER_ROUTER', host=self.trade_host, port=self.trade_port, user=self.trade_user, password=self.trade_password) self.subscribe_data(self.code, self.frequence, self.data_host, self.data_port, self.data_user, self.data_password) 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) # threading.Thread(target=, daemon=True).start() self.sub.start()
class QAStrategyCtaBase(): def __init__(self, code='rb2005', frequence='1min', strategy_id='QA_STRATEGY', risk_check_gap=1, portfolio='default', start='2020-01-01', end='2020-05-21', init_cash=1000000, send_wx=False, data_host=eventmq_ip, data_port=eventmq_port, data_user=eventmq_username, data_password=eventmq_password, trade_host=eventmq_ip, trade_port=eventmq_port, trade_user=eventmq_username, trade_password=eventmq_password, taskid=None, mongo_ip=mongo_ip, model='py'): """ code 可以传入单个标的 也可以传入一组标的(list) 会自动基于code来判断是什么市场 TODO: 支持多个市场同时存在 self.trade_host 交易所在的eventmq的ip [挂ORDER_ROUTER的] """ self.username = '******' self.password = '******' self.trade_host = trade_host self.code = code self.frequence = frequence self.strategy_id = strategy_id self.portfolio = portfolio self.data_host = data_host self.data_port = data_port self.data_user = data_user self.data_password = data_password self.trade_host = trade_host self.trade_port = trade_port self.trade_user = trade_user self.trade_password = trade_password self.start = start self.end = end self.init_cash = init_cash self.taskid = taskid self.running_time = '' self.market_preset = MARKET_PRESET() self._market_data = [] self.risk_check_gap = risk_check_gap self.latest_price = {} self.isupdate = False self.model = model self.new_data = {} self._systemvar = {} self._signal = [] self.send_wx = send_wx if isinstance(self.code, str): self.last_order_towards = {self.code: {'BUY': '', 'SELL': ''}} else: self.last_order_towards = dict( zip(self.code, [{ 'BUY': '', 'SELL': '' } for i in range(len(self.code))])) self.dt = '' if isinstance(self.code, str): self.market_type = MARKET_TYPE.FUTURE_CN if re.search( r'[a-zA-z]+', self.code) else MARKET_TYPE.STOCK_CN else: self.market_type = MARKET_TYPE.FUTURE_CN if re.search( r'[a-zA-z]+', self.code[0]) else MARKET_TYPE.STOCK_CN self.bar_order = { 'BUY_OPEN': 0, 'SELL_OPEN': 0, 'BUY_CLOSE': 0, 'SELL_CLOSE': 0 } self._num_cached = 120 self._cached_data = [] self.user_init() @property def bar_id(self): return len(self._market_data) @property def BarsSinceEntryLong(self): return self.bar_id - self.bar_order.get('BUY_OPEN', self.bar_id) @property def BarsSinceEntryShort(self): return self.bar_id - self.bar_order.get('SELL_OPEN', self.bar_id) @property def EntryPriceLong(self): code = self.get_code() return self.get_positions(code).open_price_long @property def EntryPriceShort(self): code = self.get_code() return self.get_positions(code).open_price_short def on_sync(self): if self.running_mode != 'backtest': self.pubacc.pub(json.dumps(self.acc.message), routing_key=self.strategy_id) 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() self.database = pymongo.MongoClient(mongo_ip).QAREALTIME self.client = self.database.account self.subscriber_client = self.database.subscribe 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) def debug_sim(self): self._debug_sim() threading.Thread(target=self.sub.start, daemon=True).start() def run_sim(self): self._debug_sim() self.sub.start() def run_backtest(self): self.debug() self.acc.save() risk = QA_Risk(self.acc) risk.save() try: """add rank flow if exist QARank是我们内部用于评价策略ELO的库 此处并不影响正常使用 """ from QARank import QA_Rank QA_Rank(self.acc).send() except: pass def user_init(self): """ 用户自定义的init过程 """ pass def debug(self): self.running_mode = 'backtest' self.database = pymongo.MongoClient(mongo_ip).QUANTAXIS user = QA_User(username=self.username, password=self.password) port = user.new_portfolio(self.portfolio) self.acc = port.new_accountpro(account_cookie=self.strategy_id, init_cash=self.init_cash, market_type=self.market_type, frequence=self.frequence) self.positions = self.acc.get_position(self.code) print(self.acc) print(self.acc.market_type) data = QA.QA_quotation(self.code.upper(), self.start, self.end, source=QA.DATASOURCE.MONGO, frequence=self.frequence, market=self.market_type, output=QA.OUTPUT_FORMAT.DATASTRUCT) data.data.apply(self.x1, axis=1) def x1(self, item): self.latest_price[item.name[1]] = item['close'] if str(item.name[0])[0:10] != str(self.running_time)[0:10]: self.on_dailyclose() self.on_dailyopen() if self.market_type == QA.MARKET_TYPE.STOCK_CN: print('backtest: Settle!') self.acc.settle() self._on_1min_bar() self._market_data.append(copy.deepcopy(item)) self.running_time = str(item.name[0]) self.on_bar(item) def debug_t0(self): self.running_mode = 'backtest' # self.database = pymongo.MongoClient(mongo_ip).QUANTAXIS # user = QA_User(username=self.username, password=self.password) # port = user.new_portfolio(self.portfolio) # self.acc = port.new_accountpro( # account_cookie=self.strategy_id, init_cash=self.init_cash, init_hold={ # self.code: 100000}, # market_type=self.market_type, running_environment=RUNNING_ENVIRONMENT.TZERO) # self.positions = self.acc.get_position(self.code) data = QA.QA_quotation(self.code.upper(), self.start, self.end, source=QA.DATASOURCE.MONGO, frequence=self.frequence, market=self.market_type, output=QA.OUTPUT_FORMAT.DATASTRUCT) def x1(item): self.latest_price[item.name[1]] = item['close'] if str(item.name[0])[0:10] != str(self.running_time)[0:10]: self.on_dailyclose() for order in self.acc.close_positions_order: order.trade('closebySys', order.price, order.amount, order.datetime) self.on_dailyopen() if self.market_type == QA.MARKET_TYPE.STOCK_CN: print('backtest: Settle!') self.acc.settle() self._on_1min_bar() self._market_data.append(copy.deepcopy(item)) self.running_time = str(item.name[0]) self.on_bar(item) data.data.apply(x1, axis=1) def debug_currenttick(self, freq): data = QA.QA_fetch_get_future_transaction_realtime( 'tdx', self.code.upper()) self.running_mode = 'backtest' self.database = pymongo.MongoClient(mongo_ip).QUANTAXIS user = QA_User(username=self.username, password=self.password) port = user.new_portfolio(self.portfolio) self.strategy_id = self.strategy_id + \ 'currenttick_{}_{}'.format(str(datetime.date.today()), freq) self.acc = port.new_accountpro(account_cookie=self.strategy_id, init_cash=self.init_cash, market_type=self.market_type) self.positions = self.acc.get_position(self.code) data = data.assign( price=data.price / 1000).loc[:, ['code', 'price', 'volume']].resample(freq).apply({ 'code': 'last', 'price': 'ohlc', 'volume': 'sum' }).dropna() data.columns = data.columns.droplevel(0) data = data.reset_index().set_index(['datetime', 'code']) def x1(item): self.latest_price[item.name[1]] = item['close'] if str(item.name[0])[0:10] != str(self.running_time)[0:10]: self.on_dailyclose() self.on_dailyopen() self._on_1min_bar() self._market_data.append(copy.deepcopy(item)) self.running_time = str(item.name[0]) self.on_bar(item) data.apply(x1, axis=1) def debug_histick(self, freq): data = QA.QA_fetch_get_future_transaction('tdx', self.code.upper(), self.start, self.end) self.running_mode = 'backtest' self.database = pymongo.MongoClient(mongo_ip).QUANTAXIS user = QA_User(username=self.username, password=self.password) port = user.new_portfolio(self.portfolio) self.strategy_id = self.strategy_id + \ 'histick_{}_{}_{}'.format(self.start, self.end, freq) self.acc = port.new_accountpro(account_cookie=self.strategy_id, init_cash=self.init_cash, market_type=self.market_type) self.positions = self.acc.get_position(self.code) data = data.assign( price=data.price / 1000).loc[:, ['code', 'price', 'volume']].resample(freq).apply({ 'code': 'last', 'price': 'ohlc', 'volume': 'sum' }).dropna() data.columns = data.columns.droplevel(0) data = data.reset_index().set_index(['datetime', 'code']) def x1(item): self.latest_price[item.name[1]] = item['close'] if str(item.name[0])[0:10] != str(self.running_time)[0:10]: self.on_dailyclose() self.on_dailyopen() self._on_1min_bar() self._market_data.append(copy.deepcopy(item)) self.running_time = str(item.name[0]) self.on_bar(item) data.apply(x1, axis=1) def subscribe_data(self, code, frequence, data_host, data_port, data_user, data_password, model='py'): """[summary] Arguments: code {[type]} -- [description] frequence {[type]} -- [description] """ if frequence.endswith('min'): if model == 'py': self.sub = subscriber(exchange='realtime_{}_{}'.format( frequence, code), host=data_host, port=data_port, user=data_user, password=data_password) elif model == 'rust': self.sub = subscriber_routing( exchange='realtime_{}'.format(code), routing_key=frequence, host=data_host, port=data_port, user=data_user, password=data_password) self.sub.callback = self.callback elif frequence.endswith('s'): import re self._num_cached = 2 * int(re.findall(r'\d+', self.frequence)[0]) self.sub = subscriber_routing(exchange='CTPX', routing_key=code, host=data_host, port=data_port, user=data_user, password=data_password) self.sub.callback = self.second_callback elif frequence.endswith('tick'): self._num_cached = 1 self.sub = subscriber_routing(exchange='CTPX', routing_key=code, host=data_host, port=data_port, user=data_user, password=data_password) self.sub.callback = self.tick_callback def subscribe_multi(self, codelist, frequence, data_host, data_port, data_user, data_password, model='py'): if frequence.endswith('min'): if model == 'rust': self.sub = subscriber_routing(exchange='realtime_{}'.format( codelist[0]), routing_key=frequence, host=data_host, port=data_port, user=data_user, password=data_password) for item in codelist[1:]: self.sub.add_sub(exchange='realtime_{}'.format(item), routing_key=frequence) elif model == 'py': self.sub = subscriber_routing(exchange='realtime_{}'.format( codelist[0].lower()), routing_key=frequence, host=data_host, port=data_port, user=data_user, password=data_password) for item in codelist[1:]: self.sub.add_sub(exchange='realtime_{}'.format( item.lower()), routing_key=frequence) self.sub.callback = self.callback elif frequence.endswith('tick'): self._num_cached = 1 self.sub = subscriber_routing(exchange='CTPX', routing_key=codelist[0].lower(), host=data_host, port=data_port, user=data_user, password=data_password) for item in codelist[1:]: self.sub.add_sub(exchange='CTPX', routing_key=item.lower()) self.sub.callback = self.tick_callback @property def old_data(self): return self._old_data def update(self): """ 此处是切换bar的时候的节点 """ self._old_data = self._market_data self._on_1min_bar() @property def market_datetime(self): """计算的market时间点 此api慎用 因为会惰性计算全市场的值 Returns: [type] -- [description] """ return self.market_data.index.levels[0] @property def market_data(self): if self.running_mode == 'sim': return self._market_data elif self.running_mode == 'backtest': return pd.concat(self._market_data[-100:], axis=1, sort=False).T def force_close(self): # 强平 if self.positions.volume_long > 0: self.send_order('SELL', 'CLOSE', price=self.positions.last_price, volume=self.positions.volume_long) if self.positions.volume_short > 0: self.send_order('BUY', 'CLOSE', price=self.positions.last_price, volume=self.positions.volume_short) 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])) self.on_bar( json.loads(new_bar.reset_index().to_json(orient='records'))[0]) def ind2str(self, ind, ind_type): z = ind.tail(1).reset_index().to_dict(orient='records')[0] return json.dumps({ 'topic': ind_type, 'code': self.code, 'type': self.frequence, 'data': z }) def second_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] second ==> 2*second tick b'{"ask_price_1": 4145.0, "ask_price_2": 0, "ask_price_3": 0, "ask_price_4": 0, "ask_price_5": 0, "ask_volume_1": 69, "ask_volume_2": 0, "ask_volume_3": 0, "ask_volume_4": 0, "ask_volume_5": 0, "average_price": 61958.14258714826, "bid_price_1": 4143.0, "bid_price_2": 0, "bid_price_3": 0, "bid_price_4": 0, "bid_price_5": 0, "bid_volume_1": 30, "bid_volume_2": 0, "bid_volume_3": 0, "bid_volume_4": 0, "bid_volume_5": 0, "datetime": "2019-11-20 01:57:08", "exchange": "SHFE", "gateway_name": "ctp", "high_price": 4152.0, "last_price": 4144.0, "last_volume": 0, "limit_down": 3872.0, "limit_up": 4367.0, "local_symbol": "ag1912.SHFE", "low_price": 4105.0, "name": "", "open_interest": 277912.0, "open_price": 4140.0, "preSettlementPrice": 4120.0, "pre_close": 4155.0, "symbol": "ag1912", "volume": 114288}' tick 会基于热数据的量 self._num_cached 来判断更新/重采样 """ self.new_data = json.loads(str(body, encoding='utf-8')) self._cached_data.append(self.new_data) self.latest_price[ self.new_data['symbol']] = self.new_data['last_price'] # if len(self._cached_data) == self._num_cached: # self.isupdate = True if len(self._cached_data) > 3 * self._num_cached: # 控制缓存数据量 self._cached_data = self._cached_data[self._num_cached:] data = pd.DataFrame( self._cached_data).loc[:, ['datetime', 'last_price', 'volume']] data = data.assign(datetime=pd.to_datetime(data.datetime)).set_index( 'datetime').resample(self.frequence).apply({ 'last_price': 'ohlc', 'volume': 'last' }).dropna() data.columns = data.columns.droplevel(0) data = data.assign(volume=data.volume.diff(), code=self.new_data['symbol']) data = data.reset_index().set_index(['datetime', 'code']) self.acc.on_price_change(self.new_data['symbol'], self.latest_price[self.new_data['symbol']]) # .loc[:, ['open', 'high', 'low', 'close', 'volume', 'tradetime']] now = datetime.datetime.now() if now.hour == 20 and now.minute == 59 and now.second < 10: self.daily_func() time.sleep(10) self.running_time = self.new_data['datetime'] # print(data.iloc[-1].index[0]) if self.dt != data.index[-1][0]: self.isupdate = True self.dt = data.index[-1][0] self.upcoming_data(data.tail(1)) def tick_callback(self, a, b, c, body): self.new_data = json.loads(str(body, encoding='utf-8')) self.latest_price[ self.new_data['symbol']] = self.new_data['last_price'] self.running_time = self.new_data['datetime'] self.on_tick(self.new_data) def get_code_marketdata(self, code): return self.market_data.loc[(slice(None), code), :] def get_current_marketdata(self): return self.market_data.loc[(self.running_time, slice(None)), :] 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] """ 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 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'] self.upcoming_data(bar) def control_status(self, res): print(res) def add_subscriber(self, qaproid): """Add a subscriber 增加订阅者的QAPRO_ID """ self.subscriber_client.insert_one({ 'strategy_id': self.strategy_id, 'user_id': qaproid }) @property def subscriber_list(self): """订阅者 Returns: [type] -- [description] """ return list( set([ item['user_id'] for item in self.subscriber_client.find( {'strategy_id': self.strategy_id}) ])) def load_strategy(self): raise NotImplementedError def on_dailyopen(self): pass def on_dailyclose(self): pass def on_bar(self, bar): raise NotImplementedError def on_tick(self, tick): raise NotImplementedError def _on_1min_bar(self): #raise NotImplementedError if len(self._systemvar.keys()) > 0: self._signal.append(copy.deepcopy(self._systemvar)) try: self.on_1min_bar() except: pass def on_deal(self, order): """ order is a dict type """ print('------this is on deal message ------') print(order) def on_1min_bar(self): raise NotImplementedError def on_5min_bar(self): raise NotImplementedError def on_15min_bar(self): raise NotImplementedError def on_30min_bar(self): raise NotImplementedError def order_handler(self): self._orders = {} def daily_func(self): QA.QA_util_log_info('DAILY FUNC') def risk_check(self): pass def plot(self, name, data, format): """ plot是可以存储你的临时信息的接口, 后期会接入可视化 Arguments: name {[type]} -- [description] data {[type]} -- [description] format {[type]} -- [description] """ self._systemvar[name] = { 'datetime': copy.deepcopy(str(self.running_time)), 'value': data, 'format': format } def get_code(self): if isinstance(self.code, str): return self.code else: return self.code[0] def check_order(self, direction, offset, code=None): """[summary] 同方向不开仓 只对期货市场做限制 buy - open sell - close """ if code == None: code = self.get_code() if self.market_type == QA.MARKET_TYPE.FUTURE_CN: if self.last_order_towards[code][direction] == str(offset): return False else: return True else: return True def on_ordererror(self, direction, offset, price, volume): print('order Error ') def receive_simpledeal(self, code: str, trade_time, trade_amount, direction, offset, trade_price, message='sell_open'): self.send_order(direction=direction, offset=offset, volume=trade_amount, price=trade_price, order_id=QA.QA_util_random_with_topic( self.strategy_id)) def send_order(self, direction='BUY', offset='OPEN', price=3925, volume=10, order_id='', code=None): if code == None: code = self.get_code() towards = eval('ORDER_DIRECTION.{}_{}'.format(direction, offset)) order_id = str(uuid.uuid4()) if order_id == '' else order_id if isinstance(price, float): pass elif isinstance(price, pd.Series): price = price.values[0] if self.running_mode == 'sim': # 在此处拦截无法下单的订单 if (direction == 'BUY' and self.latest_price[code] <= price) or ( direction == 'SELL' and self.latest_price[code] >= price): QA.QA_util_log_info( '============ {} SEND ORDER =================='.format( order_id)) QA.QA_util_log_info( 'direction{} offset {} price{} volume{}'.format( direction, offset, price, volume)) if self.check_order(direction, offset, code=code): #self.last_order_towards = {'BUY': '', 'SELL': ''} self.last_order_towards[code][direction] = offset now = str(datetime.datetime.now()) order = self.acc.send_order(code=code, towards=towards, price=price, amount=volume, order_id=order_id) print(order) order['topic'] = 'send_order' self.pub.pub(json.dumps(order), routing_key=self.strategy_id) self.acc.make_deal(order) self.on_deal(order) self.bar_order['{}_{}'.format(direction, offset)] = self.bar_id if self.send_wx: for user in self.subscriber_list: QA.QA_util_log_info(self.subscriber_list) try: requests.post( 'http://www.yutiansut.com/signal?user_id={}&template={}&strategy_id={}&realaccount={}&code={}&order_direction={}&order_offset={}&price={}&volume={}&order_time={}' .format(user, "xiadan_report", self.strategy_id, self.acc.user_id, code.lower(), direction, offset, price, volume, now)) except Exception as e: QA.QA_util_log_info(e) else: QA.QA_util_log_info('failed in ORDER_CHECK') else: self.on_ordererror(direction, offset, price, volume) elif self.running_mode == 'backtest': self.bar_order['{}_{}'.format(direction, offset)] = self.bar_id if self.market_type == 'stock_cn': order = self.acc.send_order(code=code, amount=volume, time=self.running_time, towards=towards, price=price) order.trade(order.order_id, order.price, order.amount, order.datetime) self.on_deal(order.to_dict()) else: self.acc.receive_simpledeal(code=code, trade_time=self.running_time, trade_towards=towards, trade_amount=volume, trade_price=price, order_id=order_id, realorder_id=order_id, trade_id=order_id) self.on_deal({ 'code': code, 'trade_time': self.running_time, 'trade_towards': towards, 'trade_amount': volume, 'trade_price': price, 'order_id': order_id, 'realorder_id': order_id, 'trade_id': order_id }) self.positions = self.acc.get_position(code) def update_account(self): if self.running_mode == 'sim': QA.QA_util_log_info('{} UPDATE ACCOUNT'.format( str(datetime.datetime.now()))) self.accounts = self.acc.account_msg self.orders = self.acc.orders if isinstance(self.code, str): self.positions = self.acc.get_position(self.code) else: pass self.trades = self.acc.trades self.updatetime = self.acc.dtstr self.on_sync() elif self.running_mode == 'backtest': if isinstance(self.code, str): self.positions = self.acc.get_position(self.code) else: pass def get_exchange(self, code): return self.market_preset.get_exchange(code) def get_positions(self, code): if self.running_mode == 'sim': self.update_account() return self.acc.get_position(code) elif self.running_mode == 'backtest': return self.acc.get_position(code) def get_cash(self): if self.running_mode == 'sim': self.update_account() return self.accounts.get('available', '') elif self.running_mode == 'backtest': return self.acc.cash_available def run(self): while True: time.sleep(self.risk_check_gap) self.risk_check()
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() self.database = pymongo.MongoClient(mongo_ip).QAREALTIME self.client = self.database.account self.subscriber_client = self.database.subscribe 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)
from QUANTAXIS.QIFI.QifiAccount import QIFI_Account if __name__ == '__main__': account = QIFI_Account(username='******', password='******', model='BACKTEST', nodatabase=False, dbname='ck', clickhouse_port=9000, clickhouse_user='******', clickhouse_password='') """ sendorder """ account.initial() order = account.send_order("000001", 200, 20, 1, datetime='2021-09-30') """ cancel order """ #cancel = account.cancel_order(order['order_id']) """ make deal """ account.make_deal(order) res = list(account.position_qifimsg.values()) print(res[0])
class QAStrategyStockBase(QAStrategyCTABase): def __init__(self, code=['000001'], frequence='1min', strategy_id='QA_STRATEGY', risk_check_gap=1, portfolio='default', start='2019-01-01', end='2019-10-21', send_wx=False, market_type='stock_cn', data_host=eventmq_ip, data_port=eventmq_port, data_user=eventmq_username, data_password=eventmq_password, trade_host=eventmq_ip, trade_port=eventmq_port, trade_user=eventmq_username, trade_password=eventmq_password, taskid=None, mongo_ip=mongo_ip): super().__init__(code=code, frequence=frequence, strategy_id=strategy_id, risk_check_gap=risk_check_gap, portfolio=portfolio, start=start, end=end, send_wx=send_wx, data_host=eventmq_ip, data_port=eventmq_port, data_user=eventmq_username, data_password=eventmq_password, trade_host=eventmq_ip, trade_port=eventmq_port, trade_user=eventmq_username, trade_password=eventmq_password, taskid=taskid, mongo_ip=mongo_ip) self.code = code self.send_wx = send_wx 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 def upcoming_data(self, new_bar): """upcoming_bar : Arguments: new_bar {json} -- [description] """ self._market_data = pd.concat([self._old_data, new_bar]) # QA.QA_util_log_info(self._market_data) if self.isupdate: self.update() self.isupdate = False self.update_account() # self.positions.on_price_change(float(new_bar['close'])) self.on_bar(new_bar) def ind2str(self, ind, ind_type): z = ind.tail(1).reset_index().to_dict(orient='records')[0] return json.dumps({ 'topic': ind_type, 'code': self.code, 'type': self.frequence, 'data': z }) 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] """ self.new_data = json.loads(str(body, encoding='utf-8')) self.latest_price[self.new_data['code']] = self.new_data['close'] self.running_time = self.new_data['datetime'] if self.dt != str(self.new_data['datetime'])[0:16]: # [0:16]是分钟线位数 print('update!!!!!!!!!!!!') self.dt = str(self.new_data['datetime'])[0:16] self.isupdate = True self.acc.on_price_change(self.new_data['code'], self.new_data['close']) bar = pd.DataFrame([self.new_data]).set_index( ['datetime', 'code']).loc[:, ['open', 'high', 'low', 'close', 'volume']] self.upcoming_data(bar) def _debug_sim(self): self.running_mode = 'sim' self._old_data = QA.QA_fetch_stock_min( self.code, QA.QA_util_get_last_day( QA.QA_util_get_real_date(str(datetime.date.today()))), str(datetime.datetime.now()), format='pd', frequence=self.frequence).set_index(['datetime', 'code']) self._old_data = self._old_data.loc[:, [ 'open', 'high', 'low', 'close', 'volume' ]] self.database = pymongo.MongoClient(mongo_ip).QAREALTIME self.client = self.database.account self.subscriber_client = self.database.subscribe self.acc = QIFI_Account(username=self.strategy_id, password=self.strategy_id, trade_host=mongo_ip) self.acc.initial() self.pub = publisher_routing(exchange='QAORDER_ROUTER', host=self.trade_host, port=self.trade_port, user=self.trade_user, password=self.trade_password) self.subscribe_data(self.code, self.frequence, self.data_host, self.data_port, self.data_user, self.data_password) 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) # threading.Thread(target=, daemon=True).start() self.sub.start() def run(self): while True: pass def debug(self): self.running_mode = 'backtest' self.database = pymongo.MongoClient(mongo_ip).QUANTAXIS user = QA_User(username="******", password='******') port = user.new_portfolio(self.portfolio) self.acc = port.new_accountpro(account_cookie=self.strategy_id, init_cash=self.init_cash, market_type=self.market_type, frequence=self.frequence) #self.positions = self.acc.get_position(self.code) print(self.acc) print(self.acc.market_type) data = QA.QA_quotation(self.code, self.start, self.end, source=QA.DATASOURCE.MONGO, frequence=self.frequence, market=self.market_type, output=QA.OUTPUT_FORMAT.DATASTRUCT) data.data.apply(self.x1, axis=1) def update_account(self): if self.running_mode == 'sim': QA.QA_util_log_info('{} UPDATE ACCOUNT'.format( str(datetime.datetime.now()))) self.accounts = self.acc.account_msg self.orders = self.acc.orders self.positions = self.acc.positions self.trades = self.acc.trades self.updatetime = self.acc.dtstr elif self.running_mode == 'backtest': #self.positions = self.acc.get_position(self.code) self.positions = self.acc.positions def send_order( self, direction='BUY', offset='OPEN', code=None, price=3925, volume=10, order_id='', ): towards = eval('ORDER_DIRECTION.{}_{}'.format(direction, offset)) order_id = str(uuid.uuid4()) if order_id == '' else order_id if self.market_type == QA.MARKET_TYPE.STOCK_CN: """ 在此对于股票的部分做一些转换 """ if towards == ORDER_DIRECTION.SELL_CLOSE: towards = ORDER_DIRECTION.SELL elif towards == ORDER_DIRECTION.BUY_OPEN: towards = ORDER_DIRECTION.BUY if isinstance(price, float): pass elif isinstance(price, pd.Series): price = price.values[0] if self.running_mode == 'sim': QA.QA_util_log_info( '============ {} SEND ORDER =================='.format( order_id)) QA.QA_util_log_info( 'direction{} offset {} price{} volume{}'.format( direction, offset, price, volume)) if self.check_order(direction, offset): self.last_order_towards = {'BUY': '', 'SELL': ''} self.last_order_towards[direction] = offset now = str(datetime.datetime.now()) order = self.acc.send_order(code=code, towards=towards, price=price, amount=volume, order_id=order_id) order['topic'] = 'send_order' self.pub.pub(json.dumps(order), routing_key=self.strategy_id) self.acc.make_deal(order) self.bar_order['{}_{}'.format(direction, offset)] = self.bar_id else: QA.QA_util_log_info('failed in ORDER_CHECK') elif self.running_mode == 'backtest': self.bar_order['{}_{}'.format(direction, offset)] = self.bar_id self.acc.receive_simpledeal(code=code, trade_time=self.running_time, trade_towards=towards, trade_amount=volume, trade_price=price, order_id=order_id)
class QAFeatureBacktest(): def __init__(self, feature, quantile=0.998, init_cash=50000000, rolling=5, portfolioname='feature', mongo_ip=mongo_ip, clickhouse_host=clickhouse_ip, clickhouse_port=clickhouse_port, clickhouse_user=clickhouse_user, clickhouse_password=clickhouse_password) -> None: """ feature --> standard QAFeature quantile -> prue long only upper quantile can be selected init_cash --> account backtest initcash rolling --> dategap for rolling sell clickhouse should be save data first mongoip --> use to save qifiaccount """ self.feature = feature.reset_index().drop_duplicates( ['date', 'code']).set_index(['date', 'code']).sort_index().dropna() self.featurename = feature.columns[0] self.start = str(self.feature.index.levels[0][0])[0:10] self.end = str(self.feature.index.levels[0][-1])[0:10] self.codelist = self.feature.index.levels[1].tolist() self.client = QACKClient(host=clickhouse_host, port=clickhouse_port, user=clickhouse_user, password=clickhouse_password) self.quantile = quantile self.preload = self.feature.groupby( level=0, as_index=False, group_keys=False).apply(lambda x: self.slice_feature(x)) self.datacenter = self.client.get_stock_day_qfq_adv( self.codelist, self.start, self.end) self.closepanel = self.datacenter.closepanel.bfill( ) ## 向前复权 匹配股票停牌模式 使用复牌后第一个收盘价卖出 self.account = QIFI_Account(init_cash=init_cash, username='******'.format( self.featurename, uuid4()), broker_name='feature', portfolioname=portfolioname, password='******', nodatabase=False, model='BACKTEST', trade_host=mongo_ip) self.tradetable = {} self.rolling = rolling self.cashpre = init_cash / rolling self.account.initial() def slice_feature(self, data): res = data[data > data.quantile(self.quantile)].dropna() res.index = res.index.remove_unused_levels() return res def get_feature(self, start, end=None): start = parser.parse(start).date() end = start if end is None else parser.parse(end).date() return self.feature.loc[start:end, :, :] def get_buy_list(self, date): """ date --> real date """ signaldate = QA_util_get_last_day(date) try: buy = self.preload.loc[parser.parse(signaldate).date(), :, :] buy.index = buy.index.remove_unused_levels() return buy.index.levels[1].tolist() except: return [] def get_sell_list(self, date): #signaldate = QA.QA_util_get_last_day(date, 5) try: sell = list(self.tradetable[QA_util_get_last_day( date, self.rolling - 1)].keys()) return sell except: return [] def run(self, ): """ buy nextday open sell next Nday close """ for date in QA_util_get_trade_range(self.start, self.end): buylist = self.get_buy_list(date) selllist = self.get_sell_list(date) self.tradetable[date] = {} if len(selllist) == 0: pass else: data = self.closepanel.loc[ parser.parse(date).date(), selllist].map(lambda x: round(x, 2)).to_dict() cashpre = self.cashpre / len(selllist) for code in selllist: volume = self.tradetable[QA_util_get_last_day( date, self.rolling - 1)][code] if volume < 100: pass else: order = self.account.send_order(code[0:6], volume, price=data[code], datetime=date + ' 15:00:00', towards=-1) self.account.make_deal(order) if len(buylist) != 0: d = self.datacenter.selects( buylist, date, date).open.map(lambda x: round(x, 2)) d.index = d.index.droplevel(0) data = d.to_dict() cashpre = self.cashpre / len(buylist) for code in buylist: try: volume = int( 0.01 * cashpre / data[code]) * 100 if data[code] != 0 else 0 if volume < 100: pass else: order = self.account.send_order(code[0:6], volume, price=data[code], datetime=date + ' 09:30:00', towards=1) self.account.make_deal(order) self.tradetable[date][code] = volume except: """ 主要是停牌买不入 直接放弃 此处买入未加入连续一字板的检测 rust 会增加此处的逻辑 """ pass else: pass holdinglist = [ QA_util_code_change_format(code) for code in list(self.account.positions.keys()) ] pricepanel = self.closepanel.loc[parser.parse(date).date(), holdinglist].map( lambda x: round(x, 2)) #pricepanel.index = pricepanel.index.droplevel(0) pricepanel = pricepanel.to_dict() for code in holdinglist: self.account.on_price_change(code[0:6], pricepanel[code]) self.account.settle()