def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True): """ if_qf选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } self.market_data = QA_fetch_stock_day_adv(self.account.code, self.account.start_date, self.account.end_date) self.if_fq = if_fq self._assets = (self.market_value.sum(axis=1) + self.account.daily_cash.set_index('date').cash).fillna( method='pad') self.time_gap = QA_util_get_trade_gap(self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets
def test_quotation_base_class_sub_(self): qaDAStruct0 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-10") qaDAStruct0.show() qaDAStruct1 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-05") qaDAStruct2 = QA_fetch_stock_day_adv('300439', start="2018-01-06", end="2018-01-10") qaDAStruct3 = qaDAStruct1 + qaDAStruct2 qaDAStruct4 = qaDAStruct3 - qaDAStruct1 #qaDAStruct5 = qaDAStruct3 - qaDAStruct2 list1 = [] for iRow1 in qaDAStruct4: list1.append(iRow1) list2 = [] for iRow2 in qaDAStruct2: list2.append(iRow2) len1 = len(list1) len2 = len(list2) for iIndex in range(len1): aRow = list1[iIndex] bRow = list2[iIndex] # 循环变量是相等的 v = aRow.equals(bRow) self.assertEqual(v, True)
def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True, market_data=None): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = {MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv} if self.account.market_type == MARKET_TYPE.STOCK_CN: self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) elif self.account.market_type == MARKET_TYPE.FUTURE_CN: self.market_data = market_data self.if_fq = if_fq if self.market_value is not None: self._assets = (self.market_value.sum( axis=1) + self.account.daily_cash.set_index('date').cash).fillna(method='pad') else: self._assets = self.account.daily_cash.set_index( 'date').cash.fillna(method='pad') self.time_gap = QA_util_get_trade_gap( self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets
def QA_etl_stock_day(type = "day", mark_day = str(datetime.date.today())): if type == "all": data = QA_fetch_stock_day_adv(list(QA_fetch_stock_list_adv()['code'])).data.reset_index() QA_util_sql_store_mysql(data, "stock_market_day",if_exists='replace') elif type == "day": data = QA_fetch_stock_day_adv(list(QA_fetch_stock_list_adv()['code']), mark_day) if data is None: print("We have no MARKET data for the day {}".format(str(datetime.date.today()))) else: data = data.data.reset_index() QA_util_sql_store_mysql(data, "stock_market_day",if_exists='append')
def test_quotation_base_class_iter_(self): qaDAStruct = QA_fetch_stock_day_adv('300439') for iRow in qaDAStruct: print(iRow) iterObj = qaDAStruct.__iter__() a = type(iterObj) print(a) i = iterObj.__next__() print(i) i = iterObj.__next__() print(i) i = iterObj.__next__() print(i)
def test_quotation_base_class_iter_(self): qaDAStruct = QA_fetch_stock_day_adv('300439') for iRow in qaDAStruct: print(iRow) iterObj = qaDAStruct.__iter__() a = type(iterObj) print(a) i = iterObj.__next__() print(i) i = iterObj.__next__() print(i) i = iterObj.__next__() print(i)
def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN): self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = {MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv} self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) self._assets = ((self.market_data.to_qfq().pivot('close') * self.account.daily_hold).sum( axis=1) + self.account.daily_cash.set_index('date').cash).fillna(method='pad') self.time_gap = QA_util_get_trade_gap( self.account.start_date, self.account.end_date) self.init_assets = self.account.init_assets
def subscribe_callback(self, code): """ 订阅回调 :param code: :return: """ if not isinstance(code, str): logger.error('not string , %s' % code) return today = datetime.datetime(self.cur_year, self.cur_month, self.cur_day).isoformat()[:10] end_date = QA_util_get_pre_trade_date(cursor_date=today, n=1)[:10] if code not in self.code_list: self.code_list.append(code) # ETF or Stock, 获取前天的收盘价格 logger.info("try fetch %s ,%s" % (code, end_date)) if code.startswith('5') or code.startswith('1'): _data = QA_fetch_index_day_adv(code, end_date, end_date) else: _data = QA_fetch_stock_day_adv(code, end_date, end_date) if _data is not None: self.pre_market_data = concat( [self.pre_market_data, _data.data.reset_index()]) logger.info("fetch %s" % _data.data.to_csv(header=False))
def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = {MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv} self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) self.if_fq = if_fq self._assets = (self.market_value.sum( axis=1) + self.account.daily_cash.set_index('date').cash).fillna(method='pad') self.time_gap = QA_util_get_trade_gap( self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets
def __QA_backtest_prepare(self): """ 这是模型内部的 初始化,主要是初始化一些账户和市场资产 写成了私有函数 @yutiansut 2017/7/20 """ if len(str(self.strategy_start_date))==10: self.strategy_start_time=str(self.strategy_start_date)+' 15:00:00' elif len(str(self.strategy_start_date))==19: self.strategy_start_time=str(self.strategy_start_date) self.strategy_start_date=str(self.strategy_start_date)[0:10] else: QA_util_log_info('Wrong start date format') if len(str(self.strategy_end_date))==10: self.strategy_end_time=str(self.strategy_end_date)+' 15:00:00' elif len(str(self.strategy_end_date))==19: self.strategy_end_time=str(self.strategy_end_date) self.strategy_end_date=str(self.strategy_end_date)[0:10] else: QA_util_log_info('Wrong end date format') # 重新初始账户资产 self.market = QA_Market(self.commission_fee_coeff) self.setting.QA_setting_init() self.account.init() self.start_real_date = QA_util_get_real_date( self.strategy_start_date, self.trade_list, 1) self.start_real_time=str(self.start_real_date)+' '+self.strategy_start_time.split(' ')[1] self.start_real_id = self.trade_list.index(self.start_real_date) self.end_real_date = QA_util_get_real_date( self.strategy_end_date, self.trade_list, -1) self.end_real_id = self.trade_list.index(self.end_real_date) self.end_real_time=str(self.end_real_date)+' '+self.strategy_end_time.split(' ')[1] # 重新初始化账户的cookie self.account.account_cookie = str(random.random()) # 初始化股票池的市场数据 if self.benchmark_type in ['I','index']: self.benchmark_data = QA_fetch_index_day_adv( self.benchmark_code, self.start_real_date, self.end_real_date) elif self.benchmark_type in ['S','stock']: self.benchmark_data = QA_fetch_stock_day_adv( self.benchmark_code, self.start_real_date, self.end_real_date) if self.backtest_type in ['day', 'd', '0x00']: self.market_data = QA_fetch_stocklist_day_adv( self.strategy_stock_list, self.trade_list[self.start_real_id - int( self.strategy_gap+1)], self.trade_list[self.end_real_id]).to_qfq() elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']: self.market_data = QA_fetch_stocklist_min_adv( self.strategy_stock_list, QA_util_time_gap(self.start_real_time,self.strategy_gap+1,'<',self.backtest_type), QA_util_time_gap(self.end_real_time,1,'>',self.backtest_type), self.backtest_type).to_qfq() elif self.backtest_type in ['index_day']: self.market_data = QA_fetch_index_day_adv(self.strategy_stock_list, self.trade_list[self.start_real_id - int( self.strategy_gap+1)], self.end_real_date) elif self.backtest_type in ['index_1min', 'index_5min', 'index_15min', 'index_30min', 'index_60min']: self.market_data = QA_fetch_index_min_adv( self.strategy_stock_list, QA_util_time_gap(self.start_real_time,self.strategy_gap+1,'<',self.backtest_type.split('_')[1]), QA_util_time_gap(self.end_real_time,1,'>',self.backtest_type.split('_')[1]), self.backtest_type.split('_')[1])
def test_getAdv_diffQA(self): """和QA返回的数据对比一致性 """ code = '000001' days = 365 * 1.2 start = (datetime.datetime.now() - datetime.timedelta(days)).date() end = (datetime.datetime.now() - datetime.timedelta(0)).date() data = TDX.getAdv(code, start, end).data self.assertTrue(len(data) > 0, "返回数据数量应该大于0。") data2 = QA_fetch_stock_day_adv(code, start, end).data if len(data) > len(data2): data = data[:len(data2)] data = data.reset_index() data['date'] = data['date'].astype('datetime64') data = data.set_index(['date', 'code']) # column顺序重拍 cols = data2.columns.tolist() data = data[cols] self.assertTrue( len(data) == len(data2), "和QA返回的数据,长度不一致 {} {}".format(len(data), len(data2))) # 两种方式检测numpy数据一致性 obo = self.differOneByOne(data, data2) # todo index不同 self.assertTrue(data.equals(data2), "和QA返回的数据不一致{}".format(obo))
def __init__( self, market_type, frequence, start, end, code_list, commission_fee, ): self.user = QA_User() self.if_settled = False self.account = None self.portfolio = None self.market = QA_Market() self.market_type = market_type self.frequence = frequence self.broker = QA_BacktestBroker(commission_fee) self.broker_name = 'backtest_broker' self.start = start self.end = end self.code_list = code_list if self.market_type is MARKET_TYPE.STOCK_CN and self.frequence is FREQUENCE.DAY: self.ingest_data = QA_fetch_stock_day_adv( self.code_list, self.start, self.end).to_qfq().panel_gen elif self.market_type is MARKET_TYPE.STOCK_CN and self.frequence[ -3:] == 'min': self.ingest_data = QA_fetch_stock_min_adv( self.code_list, self.start, self.end, self.frequence).to_qfq().panel_gen
def __init__( self, market_type, frequence, start, end, code_list, commission_fee, username='******', password='******', portfolio_cookie='qatestportfolio' ): """ :param market_type: 回测的市场 MARKET_TYPE.STOCK_CN , :param frequence: 'day' '1min' '5min' '15min' '30min' '60min' :param start: 开始日期 :param end: 结束日期 :param code_list: 股票代码池 :param commission_fee: 交易佣金 """ self.user = QA_User(username=username, password=password) self.if_settled = False self.account = None self.portfolio = self.user.new_portfolio(portfolio_cookie) # 🛠todo market_type 应该放在 QA_Market对象里的一个属性 self.market = QA_Market(if_start_orderthreading=True) self.market_type = market_type self.frequence = frequence self.broker = QA_BacktestBroker(commission_fee) self.broker_name = 'backtest_broker' self.start = start self.end = end self.code_list = code_list # 🛠todo 检查start日期和结束end日期是否正确 # 🛠todo 检查code list 是否合法 # 根据 市场类型,回测周期频率, 和股票代码列表 获取回测数据 if self.market_type is MARKET_TYPE.STOCK_CN and self.frequence is FREQUENCE.DAY: # 获取日线级别的回测数据 self.ingest_data = QA_fetch_stock_day_adv( self.code_list, self.start, self.end ).to_qfq().panel_gen elif self.market_type is MARKET_TYPE.STOCK_CN and self.frequence[ -3:] == 'min': # 获取分钟级别的回测数据 self.ingest_data = QA_fetch_stock_min_adv( self.code_list, self.start, self.end, self.frequence ).to_qfq().panel_gen else: QA_util_log_info("{} 的市场类型没有实现!".format(market_type))
def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True, market_data=None, auto_reload=False): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.client = DATABASE.risk self.client.create_index([("account_cookie", ASCENDING), ("user_cookie", ASCENDING), ("portfolio_cookie", ASCENDING)], unique=True) if auto_reload: pass else: self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } if market_data == None: if self.account.market_type == MARKET_TYPE.STOCK_CN: self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) elif self.account.market_type == MARKET_TYPE.FUTURE_CN: self.market_data = QA_fetch_future_day_adv( self.account.code, self.account.start_date, self.account.end_date) else: self.market_data = market_data self.if_fq = if_fq if self.account.market_type == MARKET_TYPE.FUTURE_CN: self.if_fq = False # 如果是期货, 默认设为FALSE if self.market_value is not None: self._assets = ( self.market_value.sum(axis=1) + self.account.daily_cash.set_index('date').cash).fillna( method='pad') else: self._assets = self.account.daily_cash.set_index( 'date').cash.fillna(method='pad') self.time_gap = QA_util_get_trade_gap(self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets
def test_GetItem(self): print("ok get item") qaDAStruct0 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-10") #for iRow int qaDAStruct0.index: closePrices = qaDAStruct0.__getitem__('close') print(closePrices) # 🛠todo 测试 __getattr__ # 🛠todo 测试 ix # 🛠todo 测试 iloc # 🛠todo 测试 loc # 🛠todo 测试 iloc# # 🛠todo 测试 iloc# # 🛠todo 测试 iloc
def ETL_stock_day(codes, start=None, end=None): if start is None: start = '2008-01-01' if end is None: end = QA_util_today_str() if start != end: rng = pd.Series(pd.date_range(start, end, freq='D')).apply(lambda x: str(x)[0:10]) else: rng = str(start)[0:10] start_date = QA_util_get_pre_trade_date(start,100) data = QA_fetch_stock_day_adv(codes,start_date,end) res1 = data.to_qfq().data res1.columns = [x + '_qfq' for x in res1.columns] data = data.data.join(res1).fillna(0).reset_index() res = data.groupby('code').apply(pct) res = res.reset_index(level = 0,drop = True).reset_index().set_index(['date','code']).loc[rng].replace([np.inf, -np.inf], 0) res = res.where((pd.notnull(res)), None) return(res)
def get_data(self, code, start, end, if_fq): if if_fq: data = QA_util_to_json_from_pandas( QA_fetch_stock_day_adv(code, start, end).to_qfq().data) self.write({'result': data}) else: data = QA_util_to_json_from_pandas( QA_fetch_stock_day(code, start, end, format='pd')) self.write({'result': data})
def test_quotation_base_class_sub_(self): qaDAStruct0 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-10") qaDAStruct0.show() qaDAStruct1 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-05") qaDAStruct2 = QA_fetch_stock_day_adv('300439', start="2018-01-06", end="2018-01-10") qaDAStruct3 = qaDAStruct1 + qaDAStruct2 qaDAStruct4 = qaDAStruct3 - qaDAStruct1 #qaDAStruct5 = qaDAStruct3 - qaDAStruct2 list1 = [] for iRow1 in qaDAStruct4: list1.append(iRow1) list2 = [] for iRow2 in qaDAStruct2: list2.append(iRow2) len1 = len(list1) len2 = len(list2) for iIndex in range(len1): aRow = list1[iIndex] bRow = list2[iIndex] # ✅ 循环变量是相等的 v = aRow.equals(bRow) self.assertEqual(v, True)
def do0_ReverseAttributes_test(self): qaDAStruct = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-10") rev_qaDAStruct = reversed(qaDAStruct) list1 = [] for iRow in qaDAStruct: print(iRow) list1.append(iRow) print('--------------') list2 = [] for iRowRev in rev_qaDAStruct: print(iRowRev) list2.append(iRowRev) print('--------------')
def test_quotation_base_class_add_(self): qaDAStruct0 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-10") qaDAStruct0.show() qaDAStruct1 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-05") qaDAStruct2 = QA_fetch_stock_day_adv('300439', start="2018-01-06", end="2018-01-10") qaDAStruct3 = qaDAStruct1 + qaDAStruct2 qaDAStruct3.show() # 🛠todo 进一步研究为何不相等 b = qaDAStruct0().equals(qaDAStruct3()) #self.assertEqual(b, True) # 🛠todo 进一步研究为何不相等 # 为何这个就不写 , 是不是 比较 __eq__的问题 # self.assertEqual( qaDAStruct0 , qaDAStruct3) list1 = [] for iRow1 in qaDAStruct0: list1.append(iRow1) list2 = [] for iRow2 in qaDAStruct0: list2.append(iRow2) len1 = len(list1) len2 = len(list2) for iIndex in range(len1): aRow = list1[iIndex] bRow = list2[iIndex] # 循环变量是相等的 v = aRow.equals(bRow) self.assertEqual(v, True)
def test_getAdv_diffQA(self): """和QA返回的数据对比一致性 """ code = '000001' days = 365 * 1.2 start = datetime.datetime.now() - datetime.timedelta(days) end = datetime.datetime.now() - datetime.timedelta(0) df = qm.getAdv(code, start, end) self.assertTrue(len(df) > 0, "返回数据数量应该大于0。") df2 = QA_fetch_stock_day_adv(code, start, end) self.assertTrue(len(df) == len(df2), "和QA返回的数据,长度不一致") # 两种方式检测numpy数据一致性 obo = self.differOneByOne(df.data, df2.data) self.assertTrue(np.array_equal(df, df2), "和QA返回的数据不一致{}".format(obo))
def test_quotation_base_class_reverse_(self): qaDAStruct = QA_fetch_stock_day_adv('300439',start="2018-01-01", end="2018-01-10") rev_qaDAStruct = reversed(qaDAStruct) list1 = [] for iRow in qaDAStruct: print(iRow) list1.append(iRow) print('--------------') list2 = [] for iRowRev in rev_qaDAStruct: print(iRowRev) list2.append(iRowRev) print('--------------') #没有 reverse pass
def __init__( self, market_type, frequence, start, end, code_list, commission_fee, ): """ :param market_type: 回测的市场 MARKET_TYPE.STOCK_CN , :param frequence: 'day' '1min' '5min' '15min' '30min' '60min' :param start: 开始日期 :param end: 结束日期 :param code_list: 股票代码池 :param commission_fee: 交易佣金 """ self.user = QA_User() self.if_settled = False self.account = None self.portfolio = None self.market = QA_Market() self.market_type = market_type self.frequence = frequence self.broker = QA_BacktestBroker(commission_fee) self.broker_name = 'backtest_broker' self.start = start self.end = end self.code_list = code_list if self.market_type is MARKET_TYPE.STOCK_CN and self.frequence is FREQUENCE.DAY: self.ingest_data = QA_fetch_stock_day_adv( self.code_list, self.start, self.end).to_qfq().panel_gen elif self.market_type is MARKET_TYPE.STOCK_CN and self.frequence[ -3:] == 'min': self.ingest_data = QA_fetch_stock_min_adv( self.code_list, self.start, self.end, self.frequence).to_qfq().panel_gen else: QA_util_log_info("{} 的市场类型没有实现!".format(market_type))
def get(self): """ 采用了get_arguents来获取参数 默认参数: code-->000001 start-->2017-01-01 end-->today """ code = self.get_argument('code', default='000001') start = self.get_argument('start', default='2017-01-01') end = self.get_argument('end', default=str(datetime.date.today())) if_fq = self.get_argument('if_fq', default=False) if if_fq: data = QA_util_to_json_from_pandas( QA_fetch_stock_day_adv(code, start, end).to_qfq().data) self.write({'result': data}) else: data = QA_util_to_json_from_pandas( QA_fetch_stock_day(code, start, end, format='pd')) self.write({'result': data})
def test_quotation_base_class_add_(self): qaDAStruct0 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-10") qaDAStruct0.show() qaDAStruct1 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-05") qaDAStruct2 = QA_fetch_stock_day_adv('300439', start="2018-01-06", end="2018-01-10") qaDAStruct3 = qaDAStruct1 + qaDAStruct2 qaDAStruct3.show() # 🛠todo 进一步研究为何不相等 b = qaDAStruct0().equals(qaDAStruct3()) #self.assertEqual(b, True) # 🛠todo 进一步研究为何不相等 # 为何这个就不写 , 是不是 比较 __eq__的问题 # self.assertEqual( qaDAStruct0 , qaDAStruct3) list1 = [] for iRow1 in qaDAStruct0: list1.append(iRow1) list2 = [] for iRow2 in qaDAStruct0: list2.append(iRow2) len1 = len(list1) len2 = len(list2) for iIndex in range(len1): aRow = list1[iIndex] bRow = list2[iIndex] #✅ 循环变量是相等的 v = aRow.equals(bRow) self.assertEqual(v, True)
class QA_Risk(): """QARISK 是一个风险插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold """ def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN): self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = {MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv} self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) self._assets = ((self.market_data.to_qfq().pivot('close') * self.account.daily_hold).sum( axis=1) + self.account.daily_cash.set_index('date').cash).fillna(method='pad') self.time_gap = QA_util_get_trade_gap( self.account.start_date, self.account.end_date) self.init_assets = self.account.init_assets def __repr__(self): return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >' def __call__(self): return pd.DataFrame([self.message]) @property def assets(self): x1 = self._assets.reset_index() return x1.assign(date=pd.to_datetime(x1.date)).set_index('date')[0] @property def max_dropback(self): """最大回撤 """ return round(float(max([(self.assets.iloc[idx] - self.assets.iloc[idx::].min())/self.assets.iloc[idx] for idx in range(len(self.assets))])),2) @property def profit(self): return round(float(self.calc_profit(self.assets)),2) @property def profit_pct(self): """利润 """ return self.calc_profitpctchange(self.assets) @property def annualize_return(self): """年化收益 Returns: [type] -- [description] """ return round(float(self.calc_annualize_return(self.assets, self.time_gap)),2) @property def volatility(self): """波动率 Returns: [type] -- [description] """ return round(float(self.profit_pct.std() * math.sqrt(250)),2) @property @lru_cache() def message(self): return { 'account_cookie': self.account.account_cookie, 'portfolio_cookie': self.account.portfolio_cookie, 'user_cookie': self.account.user_cookie, 'annualize_return': self.annualize_return, 'profit': self.profit, 'max_dropback': self.max_dropback, 'time_gap': self.time_gap, 'volatility': self.volatility, 'benchmark_code': self.benchmark_code, 'bm_annualizereturn':self.benchmark_annualize_return, 'beta': self.beta, 'alpha': self.alpha, 'sharpe': self.sharpe, 'init_assets': round(float(self.init_assets),2), 'last_assets': round(float(self.assets.iloc[-1]),2) } @property def benchmark_data(self): """ 基准组合的行情数据(一般是组合,可以调整) """ return self.fetch[self.benchmark_type]( self.benchmark_code, self.account.start_date, self.account.end_date) @property def benchmark_assets(self): """ 基准组合的账户资产队列 """ return (self.benchmark_data.open / float(self.benchmark_data.open.iloc[0]) * float(self.init_assets)) @property def benchmark_annualize_return(self): """基准组合的年化收益 Returns: [type] -- [description] """ return round(float(self.calc_annualize_return(self.benchmark_assets, self.time_gap)),2) @property def benchmark_profitpct(self): """ benchmark 基准组合的收益百分比计算 """ return self.calc_profitpctchange(self.benchmark_assets) @property def beta(self): """ beta比率 组合的系统性风险 """ return round(float(self.calc_beta(self.profit_pct.dropna(), self.benchmark_profitpct.dropna())),2) @property def alpha(self): """ alpha比率 与市场基准收益无关的超额收益率 """ return round(float(self.calc_alpha(self.annualize_return, self.benchmark_annualize_return, self.beta, 0.05)),2) @property def sharpe(self): """ 夏普比率 """ return round(float(self.calc_sharpe(self.annualize_return, self.volatility, 0.05)),2) @property def sortino(self): """ 索提诺比率 投资组合收益和下行风险比值 """ pass @property def calmar(self): """ 卡玛比率 """ pass def set_benchmark(self, code, market_type): self.benchmark_code = code self.benchmark_type = market_type def calc_annualize_return(self, assets, days): return (float(assets.iloc[-1]) / float(assets.iloc[0]) - 1)/(float(days) / 250) def calc_profitpctchange(self, assets): return self.assets[::-1].pct_change() def calc_beta(self, assest_profit, benchmark_profit): calc_cov = np.cov(assest_profit, benchmark_profit) beta = calc_cov[0, 1] / calc_cov[1, 1] return beta def calc_alpha(self, annualized_returns, benchmark_annualized_returns, beta, r=0.05): alpha = (annualized_returns - r) - (beta) * \ (benchmark_annualized_returns - r) return alpha def calc_profit(self, assets): """ 计算账户收益 期末资产/期初资产 -1 """ return (float(assets.iloc[-1]) / float(assets.iloc[0])) - 1 def calc_sharpe(self, annualized_returns, volatility_year, r=0.05): """ 计算夏普比率 r是无风险收益 """ return (annualized_returns - r) / volatility_year def save(self): """save to mongodb """ save_riskanalysis(self.message) def plot_assets_curve(self, length=14, height=12): """ 资金曲线叠加图 """ plt.figure(figsize=(length, 1.5)) plt.subplot(211) plt.title('BASIC INFO',fontsize=12) plt.axis([0, length, 0, 0.6]) plt.axis('off') i=0 for item in ['account_cookie','portfolio_cookie','user_cookie']: plt.text(i, 0.5, '{} : {}'.format(item,self.message[item]), fontsize=10, rotation=0, wrap=True) i+=(length/2.8) i=0 for item in ['benchmark_code','time_gap','max_dropback']: plt.text(i, 0.4, '{} : {}'.format(item,self.message[item]),fontsize=10, ha='left', rotation=0, wrap=True) i+=(length/2.8) i=0 for item in ['annualize_return','bm_annualizereturn','profit']: plt.text(i, 0.3, '{} : {} %'.format(item,self.message.get(item,0)),fontsize=10, ha='left', rotation=0, wrap=True) i+=length/2.8 i=0 for item in ['init_assets','last_assets','volatility']: plt.text(i, 0.2, '{} : {} '.format(item,self.message[item]),fontsize=10, ha='left', rotation=0, wrap=True) i+=length/2.8 i=0 for item in ['alpha','beta','sharpe']: plt.text(i, 0.1, '{} : {}'.format(item,self.message[item]), ha='left', fontsize=10,rotation=0, wrap=True) i+=length/2.8 #plt.figure(figsize=(length, height)) plt.subplot(212) plt.style.use('ggplot') self.assets.plot() self.benchmark_assets.xs(self.benchmark_code, level=1).plot() asset_p = mpatches.Patch( color='red', label='{}'.format(self.account.account_cookie)) asset_b = mpatches.Patch( label='benchmark {}'.format(self.benchmark_code)) plt.legend(handles=[asset_p, asset_b], loc=1) plt.title('ASSET AND BENCKMARK') plt.show()
class QA_Risk(): """QARISK 是一个风险插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold TODO: 资金利用率 反应资金的利用程度 股票周转率 反应股票的持仓天数 预期PNL/统计学PNL """ def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True, market_data=None): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } if self.account.market_type == MARKET_TYPE.STOCK_CN: self.market_data = QA_fetch_stock_day_adv(self.account.code, self.account.start_date, self.account.end_date) elif self.account.market_type == MARKET_TYPE.FUTURE_CN: self.market_data = market_data self.if_fq = if_fq self._assets = (self.market_value.sum(axis=1) + self.account.daily_cash.set_index('date').cash).fillna( method='pad') self.time_gap = QA_util_get_trade_gap(self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets def __repr__(self): return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >' def __call__(self): return pd.DataFrame([self.message]) @property @lru_cache() def market_value(self): """每日每个股票持仓市值表 Returns: pd.DataFrame -- 市值表 """ if self.if_fq: return self.market_data.to_qfq().pivot('close').fillna( method='ffill') * self.account.daily_hold else: return self.market_data.pivot('close').fillna( method='ffill') * self.account.daily_hold @property @lru_cache() def daily_market_value(self): """每日持仓总市值表 Returns: pd.DataFrame -- 市值表 """ return self.market_value.sum(axis=1) @property def assets(self): x1 = self._assets.reset_index() return x1.assign(date=pd.to_datetime(x1.date)).set_index('date')[0] @property def max_dropback(self): """最大回撤 """ return round( float( max([(self.assets.iloc[idx] - self.assets.iloc[idx::].min()) / self.assets.iloc[idx] for idx in range(len(self.assets))])), 2) @property def total_commission(self): """总手续费 """ return -abs(round(self.account.history_table.commission.sum(), 2)) @property def total_tax(self): """总印花税 """ return -abs(round(self.account.history_table.tax.sum(), 2)) @property def profit_construct(self): """利润构成 Returns: dict -- 利润构成表 """ return { 'total_buyandsell': round(self.profit_money - self.total_commission - self.total_tax, 2), 'total_tax': self.total_tax, 'total_commission': self.total_commission, 'total_profit': self.profit_money } @property def profit_money(self): """盈利额 Returns: [type] -- [description] """ return round(self.assets.iloc[-1] - self.init_cash, 2) @property def profit(self): """盈利率(百分比) Returns: [type] -- [description] """ return round(float(self.calc_profit(self.assets)), 2) @property def profit_pct(self): """利润 """ return self.calc_profitpctchange(self.assets) @property def annualize_return(self): """年化收益 Returns: [type] -- [description] """ return round( float(self.calc_annualize_return(self.assets, self.time_gap)), 2) @property def volatility(self): """波动率 Returns: [type] -- [description] """ return round(float(self.profit_pct.std() * math.sqrt(250)), 2) @property @lru_cache() def message(self): return { 'account_cookie': self.account.account_cookie, 'portfolio_cookie': self.account.portfolio_cookie, 'user_cookie': self.account.user_cookie, 'annualize_return': round(self.annualize_return, 2), 'profit': round(self.profit, 2), 'max_dropback': self.max_dropback, 'time_gap': self.time_gap, 'volatility': self.volatility, 'benchmark_code': self.benchmark_code, 'bm_annualizereturn': self.benchmark_annualize_return, 'bn_profit': self.benchmark_profit, 'beta': self.beta, 'alpha': self.alpha, 'sharpe': self.sharpe, 'init_cash': "%0.2f" % (float(self.init_cash)), 'last_assets': "%0.2f" % (float(self.assets.iloc[-1])), 'total_tax': self.total_tax, 'total_commission': self.total_commission, 'profit_money': self.profit_money # 'init_assets': round(float(self.init_assets), 2), # 'last_assets': round(float(self.assets.iloc[-1]), 2) } @property def benchmark_data(self): """ 基准组合的行情数据(一般是组合,可以调整) """ return self.fetch[self.benchmark_type](self.benchmark_code, self.account.start_date, self.account.end_date) @property def benchmark_assets(self): """ 基准组合的账户资产队列 """ return (self.benchmark_data.close / float(self.benchmark_data.open.iloc[0]) * float(self.init_cash)) @property def benchmark_profit(self): """ 基准组合的收益 """ return round(float(self.calc_profit(self.benchmark_assets)), 2) @property def benchmark_annualize_return(self): """基准组合的年化收益 Returns: [type] -- [description] """ return round( float( self.calc_annualize_return(self.benchmark_assets, self.time_gap)), 2) @property def benchmark_profitpct(self): """ benchmark 基准组合的收益百分比计算 """ return self.calc_profitpctchange(self.benchmark_assets) @property def beta(self): """ beta比率 组合的系统性风险 """ return round( float( self.calc_beta(self.profit_pct.dropna(), self.benchmark_profitpct.dropna())), 2) @property def alpha(self): """ alpha比率 与市场基准收益无关的超额收益率 """ return round( float( self.calc_alpha(self.annualize_return, self.benchmark_annualize_return, self.beta, 0.05)), 2) @property def sharpe(self): """ 夏普比率 """ return round( float( self.calc_sharpe(self.annualize_return, self.volatility, 0.05)), 2) @property def sortino(self): """ 索提诺比率 投资组合收益和下行风险比值 """ pass @property def calmar(self): """ 卡玛比率 """ pass def set_benchmark(self, code, market_type): self.benchmark_code = code self.benchmark_type = market_type def calc_annualize_return(self, assets, days): return round((float(assets.iloc[-1]) / float(assets.iloc[0]) - 1) / (float(days) / 250), 2) def calc_profitpctchange(self, assets): return self.assets[::-1].pct_change() def calc_beta(self, assest_profit, benchmark_profit): calc_cov = np.cov(assest_profit, benchmark_profit) beta = calc_cov[0, 1] / calc_cov[1, 1] return beta def calc_alpha(self, annualized_returns, benchmark_annualized_returns, beta, r=0.05): alpha = (annualized_returns - r) - (beta) * \ (benchmark_annualized_returns - r) return alpha def calc_profit(self, assets): """ 计算账户收益 期末资产/期初资产 -1 """ return (float(assets.iloc[-1]) / float(self.init_cash)) - 1 def calc_sharpe(self, annualized_returns, volatility_year, r=0.05): """ 计算夏普比率 r是无风险收益 """ # 会出现0 if volatility_year == 0: return 0 return (annualized_returns - r) / volatility_year @property def max_holdmarketvalue(self): """最大持仓市值 Returns: [type] -- [description] """ return self.daily_market_value.max() @property def min_holdmarketvalue(self): """最小持仓市值 Returns: [type] -- [description] """ return self.daily_market_value.min() @property def average_holdmarketvalue(self): """平均持仓市值 Returns: [type] -- [description] """ return self.daily_market_value.mean() @property def max_cashhold(self): """最大闲置资金 """ return self.account.daily_cash.cash.max() @property def min_cashhold(self): """最小闲置资金 """ return self.account.daily_cash.cash.min() @property def average_cashhold(self): """平均闲置资金 Returns: [type] -- [description] """ return self.account.daily_cash.cash.mean() def save(self): """save to mongodb """ save_riskanalysis(self.message) def plot_assets_curve(self, length=14, height=12): """ 资金曲线叠加图 @Roy T.Burns 2018/05/29 修改百分比显示错误 """ plt.style.use('ggplot') plt.figure(figsize=(length, height)) plt.subplot(211) plt.title('BASIC INFO', fontsize=12) plt.axis([0, length, 0, 0.6]) plt.axis('off') i = 0 for item in ['account_cookie', 'portfolio_cookie', 'user_cookie']: plt.text(i, 0.5, '{} : {}'.format(item, self.message[item]), fontsize=10, rotation=0, wrap=True) i += (length / 2.8) i = 0 for item in ['benchmark_code', 'time_gap', 'max_dropback']: plt.text(i, 0.4, '{} : {}'.format(item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True) i += (length / 2.8) i = 0 for item in ['annualize_return', 'bm_annualizereturn', 'profit']: plt.text(i, 0.3, '{} : {} %'.format(item, self.message.get(item, 0) * 100), fontsize=10, ha='left', rotation=0, wrap=True) i += length / 2.8 i = 0 for item in ['init_cash', 'last_assets', 'volatility']: plt.text(i, 0.2, '{} : {} '.format(item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True) i += length / 2.8 i = 0 for item in ['alpha', 'beta', 'sharpe']: plt.text(i, 0.1, '{} : {}'.format(item, self.message[item]), ha='left', fontsize=10, rotation=0, wrap=True) i += length / 2.8 plt.subplot(212) self.assets.plot() self.benchmark_assets.xs(self.benchmark_code, level=1).plot() asset_p = mpatches.Patch(color='red', label='{}'.format( self.account.account_cookie)) asset_b = mpatches.Patch( label='benchmark {}'.format(self.benchmark_code)) plt.legend(handles=[asset_p, asset_b], loc=1) plt.title('ASSET AND BENCKMARK') return plt def plot_dailyhold(self, start=None, end=None): """ 使用热力图画出每日持仓 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 8)) sns.heatmap(self.account.daily_hold.reset_index().drop( 'account_cookie', axis=1).set_index('date').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax) ax.set_title('HOLD TABLE --ACCOUNT: {}'.format( self.account.account_cookie)) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') return plt def plot_signal(self, start=None, end=None): """ 使用热力图画出买卖信号 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 18)) sns.heatmap(self.account.trade.reset_index().drop( 'account_cookie', axis=1).set_index('datetime').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax) ax.set_title('SIGNAL TABLE --ACCOUNT: {}'.format( self.account.account_cookie)) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') return plt def generate_plots(self): """ 生成图像 """ self.plot_assets_curve() self.plot_dailyhold() self.plot_signal()
def get_trade_marketdata(self, rx,gap=3): data=QA_fetch_stock_day_adv(rx.code.values[0], QA_util_date_gap(rx.date.values[0], gap, methods='lt'), QA_util_date_gap(rx.sell_date.values[0][-1], gap, methods='gt')) data['tradesignal']='N' return data
def predict(trading_date, strategy_id='机器学习1号', account1='name:client-1', working_dir=working_dir, ui_log=None, exceptions=exceptions): try: QA_util_log_info( '##JOB01 Now Got Account Info ==== {}'.format(str(trading_date)), ui_log) client = get_Client() account1 = account1 account_info = client.get_account(account1) print(account_info) sub_accounts = client.get_positions(account1)['sub_accounts'] try: frozen = float( client.get_positions(account1)['positions'].set_index( '证券代码').loc[exceptions]['市值'].sum()) except: frozen = 0 sub_accounts = sub_accounts - frozen except: send_email('错误报告', '云服务器错误,请检查', trading_date) send_actionnotice(strategy_id, '错误报告:{}'.format(trading_date), '云服务器错误,请检查', direction='HOLD', offset='HOLD', volume=None) try: QA_util_log_info( '##JOB02 Now Load Model ==== {}'.format(str(trading_date)), ui_log) stock_model_temp, stock_info_temp = Stock.load_model( 'stock', working_dir=working_dir) index_model_temp, index_info_temp = Index.load_model( 'index', working_dir=working_dir) safe_model_temp, safe_info_temp = Index.load_model( 'safe', working_dir=working_dir) except: send_email('错误报告', '无法正确加载模型,请检查', trading_date) send_actionnotice(strategy_id, '错误报告:{}'.format(trading_date), '无法正确加载模型,请检查', direction='HOLD', offset='HOLD', volume=None) QA_util_log_info( '##JOB03 Now Model Predict ==== {}'.format(str(trading_date)), ui_log) #index_list,index_report,index_top_report = Index.check_model(index_model_temp, QA_util_get_last_day(trading_date),QA_util_get_last_day(trading_date),index_info_temp['cols'], 'INDEXT_TARGET5', 0.3) index_tar, index_b = Index.model_predict(index_model_temp, str(trading_date[0:7]) + "-01", trading_date, index_info_temp['cols']) #safe_list,safe_report,safe_top_report = Index.check_model(safe_model_temp, QA_util_get_last_day(trading_date),QA_util_get_last_day(trading_date),safe_info_temp['cols'], 'INDEXT_TARGET', 0.3) safe_tar, safe_b = Index.model_predict(safe_model_temp, str(trading_date[0:7]) + "-01", trading_date, index_info_temp['cols']) stock_list, report, top_report = Stock.check_model( stock_model_temp, QA_util_get_last_day(trading_date), QA_util_get_last_day(trading_date), stock_info_temp['cols'], 0.42) stock_tar, stock_b = Stock.model_predict(stock_model_temp, str(trading_date[0:7]) + "-01", trading_date, stock_info_temp['cols']) tar = combine_model(index_b, stock_b, safe_b, str(trading_date[0:7]) + "-01", trading_date) QA_util_log_info( '##JOB03 Now Concat Result ==== {}'.format(str(trading_date)), ui_log) try: tar1 = tar.loc[trading_date] except: tar1 = None QA_util_log_info( '##JOB04 Now Funding Decision ==== {}'.format(str(trading_date)), ui_log) if tar1 is None: res = None else: tar2 = tar1[['Z_PROB', 'O_PROB', 'RANK']] close = QA_fetch_stock_day_adv( list(tar1.index), QA_util_get_last_day(trading_date, 60), trading_date).to_qfq().data.loc[trading_date].reset_index( 'date')['close'] info = QA_fetch_stock_fianacial_adv( list(tar1.index), trading_date, trading_date).data.reset_index('date')[['NAME', 'INDUSTRY']] res = tar2.join(close).join(info) #res = pd.concat([tar2,close,info],axis=1) avg_account = sub_accounts['总 资 产'] / tar1.shape[0] res = res.assign(tar=avg_account[0] * percent) res['cnt'] = (res['tar'] / res['close'] / 100).apply(lambda x: round(x, 0) * 100) res['real'] = res['cnt'] * res['close'] QA_util_log_info( '##JOB05 Now Current Report ==== {}'.format(str(trading_date)), ui_log) table1 = tar[tar['RANK'] <= 5].groupby('date').mean() if exceptions is not None: frozen_positions = client.get_positions(account1)['positions'][[ '证券代码', '证券名称', '股票余额', '可用余额', '冻结数量', '参考盈亏', '盈亏比例(%)' ]].set_index('证券代码').loc[exceptions] else: frozen_positions = pd.DataFrame() QA_util_log_info( '##JOB06 Now Current Holding ==== {}'.format(str(trading_date)), ui_log) positions = client.get_positions(account1)['positions'][[ '证券代码', '证券名称', '股票余额', '可用余额', '冻结数量', '参考盈亏', '盈亏比例(%)' ]] QA_util_log_info( '##JOB07 Now Message Building ==== {}'.format(str(trading_date)), ui_log) try: msg1 = '模型训练日期:{model_date}'.format(model_date=stock_info_temp['date']) body1 = build_table( table1, 'safe模型结果_{}'.format(str(QA_util_get_last_day(trading_date)))) body3 = build_table(positions, '目前持仓') body4 = build_table( pd.DataFrame(report), '指数模型结果_{}'.format(str(QA_util_get_last_day(trading_date)))) body5 = build_table( pd.DataFrame(top_report), '选股模型结果_{}'.format(str(QA_util_get_last_day(trading_date)))) #body6 = build_table(stock_list, '上一交易日模型交易清单{}'.format(str(QA_util_get_last_day(trading_date)))) body7 = build_table(frozen_positions, '目前锁定持仓') if res is not None: body2 = build_table(res, '目标持仓') msg = build_email(build_head(), msg1, body1, body3, body5, body4, body2, body7) title = '交易报告' else: msg = build_email(build_head(), msg1, body1, body3, body5, body4, body1, body7) title = '空仓交易报告' send_email(title + trading_date, msg, 'date') except: send_email('交易报告:' + trading_date, "消息构建失败", 'date') return (tar)
def get_prices( self, code_list: Union[str, Tuple[str], List[str]] = None, start_time: Union[str, datetime.datetime] = None, end_time: Union[str, datetime.datetime] = None, fq: str = None, frequence: str = None, price_type: str = None, ): """ 价格数据获取接口,单因子输入后,可以通过单因子获取股票代码,时间等参数信息 参数 --- :param code_list: 股票代码 :param start_time: 起始时间 :param end_time: 截止时间 :param fq: 复权方式 :param frequence: 时间频率 """ # 1. 股票池 if isinstance(code_list, tuple): code_list = list(code_list) # 2. 时间频率 if not frequence: frequence = self.frequence frequence = utils.get_frequence(frequence) if not start_time: start_time = self.start_time if not end_time: end_time = self.end_time if (not start_time) or (not end_time): raise ValueError("价格获取接口需要指定起始时间与结束时间") start_time = str(pd.Timestamp(start_time))[:19] end_time = str(pd.Timestamp(end_time))[:19] data = QA_fetch_stock_day_adv(code=code_list, start=start_time, end=end_time) index_data = QA_fetch_index_day_adv(code="000001", start=start_time, end=end_time) # 3. 复权 if not fq: fq = self.fq if not fq: data = data elif fq.lower() in ["pre", "qfq", "前复权"]: data = data.to_qfq() elif fq.lower() in ["post", "hfq", "后复权"]: data = data.to_hfq() elif fq.lower() in ["none", "bfq", "不复权"]: data = data # 4. 重采样 # 考虑到停牌退市等原因,重采样会有异常值,即日期与我们需要的日期不一致 # 这里采用指数作为基准,对重采样数据进行再处理 # 对于停牌数据缺失,采用前值作为填充 if frequence == "1d": data = data.data.unstack().ffill().stack() else: index_data = index_data.resample(frequence).unstack() data = data.resample(frequence).unstack().ffill() data = data.reindex(index_data.index).stack() if frequence == '1q': data.index = data.index.map(lambda x: (utils.QA_fmt_quarter(x[0]), x[1])) # 5. 价格类型 if not price_type: price_type = self.price_type if price_type.lower() is "avg": avg = data["amount"] / data["volume"] / 100.0 return avg.unstack() return data[price_type.lower()].unstack()
def __QA_backtest_prepare(self): """ 这是模型内部的 初始化,主要是初始化一些账户和市场资产 写成了私有函数 @yutiansut 2017/7/20 """ self.strategy_stock_list = np.unique( self.strategy_stock_list).tolist() # 保证不会重复 if len(str(self.strategy_start_date)) == 10: self.strategy_start_time = str( self.strategy_start_date) + ' 15:00:00' elif len(str(self.strategy_start_date)) == 19: self.strategy_start_time = str(self.strategy_start_date) self.strategy_start_date = str(self.strategy_start_date)[0:10] else: self.__QA_backtest_log_info(self, 'Wrong start date format') if len(str(self.strategy_end_date)) == 10: self.strategy_end_time = str(self.strategy_end_date) + ' 15:00:00' elif len(str(self.strategy_end_date)) == 19: self.strategy_end_time = str(self.strategy_end_date) self.strategy_end_date = str(self.strategy_end_date)[0:10] else: self.__QA_backtest_log_info(self, 'Wrong end date format') # 重新初始账户资产 self.market = QA_Market(self.commission_fee_coeff) self.setting.QA_setting_init() self.account.init() self.account_d_value.append(self.account.init_assest) self.start_real_date = QA_util_get_real_date(self.strategy_start_date, self.trade_list, 1) self.start_real_time = str( self.start_real_date) + ' ' + self.strategy_start_time.split( ' ')[1] self.start_real_id = self.trade_list.index(self.start_real_date) self.end_real_date = QA_util_get_real_date(self.strategy_end_date, self.trade_list, -1) self.end_real_id = self.trade_list.index(self.end_real_date) self.end_real_time = str(self.end_real_date) + \ ' ' + self.strategy_end_time.split(' ')[1] # 重新初始化账户的cookie self.account.account_cookie = str(random.random()) # 初始化股票池的市场数据 if self.benchmark_type in ['I', 'index']: self.benchmark_data = QA_fetch_index_day_adv( self.benchmark_code, self.trade_list[self.start_real_id - 1], self.end_real_date) elif self.benchmark_type in ['S', 'stock']: self.benchmark_data = QA_fetch_stock_day_adv( self.benchmark_code, self.trade_list[self.start_real_id - 1], self.end_real_date) if self.backtest_type in ['day', 'd', '0x00']: self.market_data = QA_fetch_stocklist_day_adv( self.strategy_stock_list, self.trade_list[self.start_real_id - int(self.strategy_gap + 1)], self.trade_list[self.end_real_id]).to_qfq() elif self.backtest_type in ['1min', '5min', '15min', '30min', '60min']: self.market_data = QA_fetch_stocklist_min_adv( self.strategy_stock_list, QA_util_time_gap(self.start_real_time, self.strategy_gap + 1, '<', self.backtest_type), QA_util_time_gap(self.end_real_time, 1, '>', self.backtest_type), self.backtest_type).to_qfq() elif self.backtest_type in ['index_day']: self.market_data = QA_fetch_index_day_adv( self.strategy_stock_list, self.trade_list[self.start_real_id - int(self.strategy_gap + 1)], self.end_real_date) elif self.backtest_type in [ 'index_1min', 'index_5min', 'index_15min', 'index_30min', 'index_60min' ]: self.market_data = QA_fetch_index_min_adv( self.strategy_stock_list, QA_util_time_gap(self.start_real_time, self.strategy_gap + 1, '<', self.backtest_type.split('_')[1]), QA_util_time_gap(self.end_real_time, 1, '>', self.backtest_type.split('_')[1]), self.backtest_type.split('_')[1]) self.market_data_dict = dict( zip(list(self.market_data.code), self.market_data.splits())) self.market_data_hashable = self.market_data.dicts self.dirs = '.{}QUANTAXIS_RESULT{}{}{}{}{}'.format( os.sep, os.sep, self.topic_name, os.sep, self.stratey_version, os.sep) os.makedirs(self.dirs, exist_ok=True)
def test_GetItem(self): print("ok get item") qaDAStruct0 = QA_fetch_stock_day_adv('300439', start="2018-01-01", end="2018-01-10") #for iRow int qaDAStruct0.index: closePrices = qaDAStruct0.__getitem__('close') print(closePrices)
def market_data(self, start, end, _type='day'): return QA_fetch_stock_day_adv(self.block_code, start, end)
class QA_Risk(): """QARISK 是一个风险插件 """ def __init__(self, account): self.account = account self.benchmark = None self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } self.market_data = QA_fetch_stock_day_adv(self.account.code, self.account.start_date, self.account.end_date) self.assets = ((self.market_data.to_qfq().pivot('close') * self.account.daily_hold).sum(axis=1) + self.account.daily_cash.set_index('date').cash).fillna( method='pad') self.time_gap = QA_util_get_trade_gap(self.account.start_date, self.account.end_date) def __repr__(self): return '< QA_RISK ANALYSIS ACCOUNT-{} >'.format( self.account.account_cookie) def __call__(self): return pd.DataFrame([self.message]) @property def max_dropback(self): """最大回撤 """ return max([ self.assets.iloc[idx::].max() - self.assets.iloc[idx::].min() for idx in range(len(self.assets)) ]) / float(self.assets.iloc[0]) @property def profit(self): """利润 """ return (float(self.assets.iloc[-1]) / float(self.assets.iloc[0])) - 1 @property def annualize_return(self): """年化收益 Returns: [type] -- [description] """ return math.pow( float(self.assets.iloc[-1]) / float(self.assets.iloc[0]), 250.0 / float(self.time_gap)) - 1.0 @property def volatility(self): """波动率 Returns: [type] -- [description] """ return self.assets.diff().std() @property def message(self): return { 'account_cookie': self.account.account_cookie, 'portfolio_cookie': self.account.portfolio_cookie, 'user_cookie': self.account.user_cookie, 'annualize_return': self.annualize_return, 'profit': self.profit, 'max_dropback': self.max_dropback, 'time_gap': self.time_gap, 'volatility': self.volatility } def set_benchmark(self, code, market_type): self.benchmark = self.fetch[market_type](code, self.account.start_date, self.account.end_date)
class QA_Risk(): """QARISK 是一个风险插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold TODO: 资金利用率 反应资金的利用程度 股票周转率 反应股票的持仓天数 预期PNL/统计学PNL """ def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True): """ if_qf选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = {MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv} self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) self.if_fq = if_fq self._assets = (self.market_value.sum( axis=1) + self.account.daily_cash.set_index('date').cash).fillna(method='pad') self.time_gap = QA_util_get_trade_gap( self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets def __repr__(self): return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >' def __call__(self): return pd.DataFrame([self.message]) @property @lru_cache() def market_value(self): """市值表 Returns: pd.DataFrame -- 市值表 """ if self.if_fq: return self.market_data.to_qfq().pivot('close') * self.account.daily_hold else: self.market_data.pivot('close') * self.account.daily_hold @property def assets(self): x1 = self._assets.reset_index() return x1.assign(date=pd.to_datetime(x1.date)).set_index('date')[0] @property def max_dropback(self): """最大回撤 """ return round(float(max([(self.assets.iloc[idx] - self.assets.iloc[idx::].min())/self.assets.iloc[idx] for idx in range(len(self.assets))])), 2) @property def total_commission(self): """总手续费 """ return -abs(round(self.account.history_table.commission.sum(), 2)) @property def total_tax(self): """总印花税 """ return -abs(round(self.account.history_table.tax.sum(), 2)) @property def profit_construct(self): """利润构成 Returns: dict -- 利润构成表 """ return { 'total_buyandsell': round(self.profit_money-self.total_commission-self.total_tax, 2), 'total_tax': self.total_tax, 'total_commission': self.total_commission, 'total_profit': self.profit_money } @property def profit_money(self): """盈利额 Returns: [type] -- [description] """ return round(self.assets.iloc[-1]-self.init_cash, 2) @property def profit(self): """盈利率(百分比) Returns: [type] -- [description] """ return round(float(self.calc_profit(self.assets)), 2) @property def profit_pct(self): """利润 """ return self.calc_profitpctchange(self.assets) @property def annualize_return(self): """年化收益 Returns: [type] -- [description] """ return round(float(self.calc_annualize_return(self.assets, self.time_gap)), 2) @property def volatility(self): """波动率 Returns: [type] -- [description] """ return round(float(self.profit_pct.std() * math.sqrt(250)), 2) @property @lru_cache() def message(self): return { 'account_cookie': self.account.account_cookie, 'portfolio_cookie': self.account.portfolio_cookie, 'user_cookie': self.account.user_cookie, 'annualize_return': round(self.annualize_return, 2), 'profit': round(self.profit, 2), 'max_dropback': self.max_dropback, 'time_gap': self.time_gap, 'volatility': self.volatility, 'benchmark_code': self.benchmark_code, 'bm_annualizereturn': self.benchmark_annualize_return, 'bn_profit': self.benchmark_profit, 'beta': self.beta, 'alpha': self.alpha, 'sharpe': self.sharpe, 'init_cash': "%0.2f" % (float(self.init_cash)), 'last_assets': "%0.2f" % (float(self.assets.iloc[-1])) #'init_assets': round(float(self.init_assets), 2), #'last_assets': round(float(self.assets.iloc[-1]), 2) } @property def benchmark_data(self): """ 基准组合的行情数据(一般是组合,可以调整) """ return self.fetch[self.benchmark_type]( self.benchmark_code, self.account.start_date, self.account.end_date) @property def benchmark_assets(self): """ 基准组合的账户资产队列 """ return (self.benchmark_data.close / float(self.benchmark_data.open.iloc[0]) * float(self.init_cash)) @property def benchmark_profit(self): """ 基准组合的收益 """ return round(float(self.calc_profit(self.benchmark_assets)), 2) @property def benchmark_annualize_return(self): """基准组合的年化收益 Returns: [type] -- [description] """ return round(float(self.calc_annualize_return(self.benchmark_assets, self.time_gap)), 2) @property def benchmark_profitpct(self): """ benchmark 基准组合的收益百分比计算 """ return self.calc_profitpctchange(self.benchmark_assets) @property def beta(self): """ beta比率 组合的系统性风险 """ return round(float(self.calc_beta(self.profit_pct.dropna(), self.benchmark_profitpct.dropna())), 2) @property def alpha(self): """ alpha比率 与市场基准收益无关的超额收益率 """ return round(float(self.calc_alpha(self.annualize_return, self.benchmark_annualize_return, self.beta, 0.05)), 2) @property def sharpe(self): """ 夏普比率 """ return round(float(self.calc_sharpe(self.annualize_return, self.volatility, 0.05)), 2) @property def sortino(self): """ 索提诺比率 投资组合收益和下行风险比值 """ pass @property def calmar(self): """ 卡玛比率 """ pass def set_benchmark(self, code, market_type): self.benchmark_code = code self.benchmark_type = market_type def calc_annualize_return(self, assets, days): return round((float(assets.iloc[-1]) / float(assets.iloc[0]) - 1)/(float(days) / 250), 2) def calc_profitpctchange(self, assets): return self.assets[::-1].pct_change() def calc_beta(self, assest_profit, benchmark_profit): calc_cov = np.cov(assest_profit, benchmark_profit) beta = calc_cov[0, 1] / calc_cov[1, 1] return beta def calc_alpha(self, annualized_returns, benchmark_annualized_returns, beta, r=0.05): alpha = (annualized_returns - r) - (beta) * \ (benchmark_annualized_returns - r) return alpha def calc_profit(self, assets): """ 计算账户收益 期末资产/期初资产 -1 """ return (float(assets.iloc[-1]) / float(self.init_cash)) - 1 def calc_sharpe(self, annualized_returns, volatility_year, r=0.05): """ 计算夏普比率 r是无风险收益 """ # 会出现0 if volatility_year == 0: return 0 return (annualized_returns - r) / volatility_year def save(self): """save to mongodb """ save_riskanalysis(self.message) def plot_assets_curve(self, length=14, height=12): """ 资金曲线叠加图 @Roy T.Burns 2018/05/29 修改百分比显示错误 """ plt.style.use('ggplot') plt.figure(figsize=(length, height)) plt.subplot(211) plt.title('BASIC INFO', fontsize=12) plt.axis([0, length, 0, 0.6]) plt.axis('off') i = 0 for item in ['account_cookie', 'portfolio_cookie', 'user_cookie']: plt.text(i, 0.5, '{} : {}'.format( item, self.message[item]), fontsize=10, rotation=0, wrap=True) i += (length/2.8) i = 0 for item in ['benchmark_code', 'time_gap', 'max_dropback']: plt.text(i, 0.4, '{} : {}'.format( item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True) i += (length/2.8) i = 0 for item in ['annualize_return', 'bm_annualizereturn', 'profit']: plt.text(i, 0.3, '{} : {} %'.format(item, self.message.get( item, 0)*100), fontsize=10, ha='left', rotation=0, wrap=True) i += length/2.8 i = 0 for item in ['init_cash', 'last_assets', 'volatility']: plt.text(i, 0.2, '{} : {} '.format( item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True) i += length/2.8 i = 0 for item in ['alpha', 'beta', 'sharpe']: plt.text(i, 0.1, '{} : {}'.format( item, self.message[item]), ha='left', fontsize=10, rotation=0, wrap=True) i += length/2.8 plt.subplot(212) self.assets.plot() self.benchmark_assets.xs(self.benchmark_code, level=1).plot() asset_p = mpatches.Patch( color='red', label='{}'.format(self.account.account_cookie)) asset_b = mpatches.Patch( label='benchmark {}'.format(self.benchmark_code)) plt.legend(handles=[asset_p, asset_b], loc=1) plt.title('ASSET AND BENCKMARK') plt.show() def plot_dailyhold(self, start=None, end=None): """ 使用热力图画出每日持仓 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 8)) sns.heatmap(self.account.daily_hold.reset_index().drop('account_cookie', axis=1).set_index( 'date').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax) ax.set_title( 'HOLD TABLE --ACCOUNT: {}'.format(self.account.account_cookie)) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') plt.show() def plot_signal(self, start=None, end=None): """ 使用热力图画出买卖信号 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 18)) sns.heatmap(self.account.trade.reset_index().drop('account_cookie', axis=1).set_index( 'datetime').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax) ax.set_title( 'SIGNAL TABLE --ACCOUNT: {}'.format(self.account.account_cookie)) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') plt.show() def generate_plots(self): """ 生成图像 """ self.plot_assets_curve() self.plot_dailyhold() self.plot_signal()
class QA_Risk(): """QARISK 是一个风险插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold """ def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN): self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } self.market_data = QA_fetch_stock_day_adv(self.account.code, self.account.start_date, self.account.end_date) self.assets = ((self.market_data.to_qfq().pivot('close') * self.account.daily_hold).sum(axis=1) + self.account.daily_cash.set_index('date').cash).fillna( method='pad') self.time_gap = QA_util_get_trade_gap(self.account.start_date, self.account.end_date) self.init_assets = self.account.init_assets def __repr__(self): return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >' def __call__(self): return pd.DataFrame([self.message]) @property def max_dropback(self): """最大回撤 """ return max([ self.assets.iloc[idx::].max() - self.assets.iloc[idx::].min() for idx in range(len(self.assets)) ]) / float(self.assets.iloc[0]) @property def profit(self): return self.calc_profit(self.assets) @property def profit_pct(self): """利润 """ return self.calc_profitpctchange(self.assets) @property def annualize_return(self): """年化收益 Returns: [type] -- [description] """ return self.calc_annualize_return(self.assets, self.time_gap) @property def volatility(self): """波动率 Returns: [type] -- [description] """ return self.profit_pct.std() * math.sqrt(250) @property def message(self): return { 'account_cookie': self.account.account_cookie, 'portfolio_cookie': self.account.portfolio_cookie, 'user_cookie': self.account.user_cookie, 'annualize_return': self.annualize_return, 'profit': self.profit, 'max_dropback': self.max_dropback, 'time_gap': self.time_gap, 'volatility': self.volatility, 'benchmark_code': self.benchmark_code, 'beta': self.beta, 'alpha': self.alpha, 'sharpe': self.sharpe, 'init_assets': self.init_assets, 'last_assets': self.assets.iloc[-1] } @property def benchmark_data(self): """ 基准组合的行情数据(一般是组合,可以调整) """ return self.fetch[self.benchmark_type](self.benchmark_code, self.account.start_date, self.account.end_date) @property def benchmark_assets(self): """ 基准组合的账户资产队列 """ return (self.benchmark_data.open / float(self.benchmark_data.open.iloc[0]) * float(self.init_assets)) @property def benchmark_annualize_return(self): """基准组合的年化收益 Returns: [type] -- [description] """ return self.calc_annualize_return(self.benchmark_assets, self.time_gap) @property def benchmark_profitpct(self): """ benchmark 基准组合的收益百分比计算 """ return self.calc_profitpctchange(self.benchmark_assets) @property def beta(self): """ beta比率 组合的系统性风险 """ return self.calc_beta(self.profit_pct.dropna(), self.benchmark_profitpct.dropna()) @property def alpha(self): """ alpha比率 与市场基准收益无关的超额收益率 """ return self.calc_alpha(self.annualize_return, self.benchmark_annualize_return, self.beta, 0.05) @property def sharpe(self): """ 夏普比率 """ return self.calc_sharpe(self.annualize_return, self.volatility, 0.05) @property def sortino(self): """ 索提诺比率 投资组合收益和下行风险比值 """ pass @property def calmar(self): """ 卡玛比率 """ pass def set_benchmark(self, code, market_type): self.benchmark_code = code self.benchmark_type = market_type def calc_annualize_return(self, assets, days): return (float(assets.iloc[-1]) / float(assets.iloc[0]) - 1) / (float(days) / 250) def calc_profitpctchange(self, assets): return self.assets[::-1].pct_change() def calc_beta(self, assest_profit, benchmark_profit): calc_cov = np.cov(assest_profit, benchmark_profit) beta = calc_cov[0, 1] / calc_cov[1, 1] return beta def calc_alpha(self, annualized_returns, benchmark_annualized_returns, beta, r=0.05): alpha = (annualized_returns - r) - (beta) * \ (benchmark_annualized_returns - r) return alpha def calc_profit(self, assets): """ 计算账户收益 期末资产/期初资产 -1 """ return (float(assets.iloc[-1]) / float(assets.iloc[0])) - 1 def calc_sharpe(self, annualized_returns, volatility_year, r=0.05): """ 计算夏普比率 r是无风险收益 """ return (annualized_returns - r) / volatility_year def save(self): """save to mongodb """ save_riskanalysis(self.message)