class QA_Performance(): """ QA_Performance是一个绩效分析插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold QAPERFORMANCE 的评估字段 1. 对于多头开仓/ 空头开仓的分析 2. 总盈利(对于每个单笔而言) 3. 总亏损(对于每个单笔而言) 4. 总盈利/总亏损 5. 交易手数 6. 盈利比例 7. 盈利手数 8. 亏损手数 9. 持平手数 10. 平均利润 11. 平均盈利 12. 平均亏损 13. 平均盈利/平均亏损 14. 最大盈利(单笔) 15. 最大亏损(单笔) 16. 最大盈利/总盈利 17. 最大亏损/总亏损 18. 净利润/最大亏损 19. 最大连续盈利手数 20. 最大连续亏损手数 21. 平均持仓周期 22. 平均盈利周期 23. 平均亏损周期 24. 平均持平周期 25. 最大使用资金 26. 最大持仓手数 27. 交易成本合计 28. 收益率 29. 年化收益率 30. 有效收益率 31. 月度平均盈利 32. 收益曲线斜率 33. 收益曲线截距 34. 收益曲线R2值 35. 夏普比例 36. 总交易时间 37. 总持仓时间 38. 持仓时间比例 39. 最大空仓时间 40. 持仓周期 41. 资产最大升水 42. 发生时间 43. 最大升水/前期低点 44. 单日最大资产回撤比率 45. 最大资产回撤值 46. 最大资产回撤发生时间 47. 回撤值/前期高点 48. 净利润/回撤值 """ def __init__(self, target): self.target = target self._style_title = [ 'beta', 'momentum', 'size', 'earning_yield', 'volatility', 'growth', 'value', 'leverage', 'liquidity', 'reversal' ] self.market_preset = MARKET_PRESET() self.pnl = self.pnl_fifo def __repr__(self): return '< QA_PERFORMANCE ANYLYSIS PLUGIN >' def set_pnl(self, model='fifo'): if model == 'fifo': self.pnl = self.pnl_fifo elif model == 'lifo': self.pnl = self.pnl_lifo def base_message(self, pnl): return {'total_profit': round(self.total_profit(pnl), 2), # 总盈利(对于每个单笔而言) 'total_loss': round(self.total_loss(pnl), 2), # 总亏损(对于每个单笔而言) 'total_pnl': round(self.total_pnl(pnl), 2), # 总盈利/总亏损 'trading_amounts': round(self.trading_amounts(pnl), 2), # 交易手数 'profit_amounts': round(self.profit_amounts(pnl), 2), # 盈利手数 'loss_amounts': round(self.loss_amounts(pnl), 2), # 亏损手数 'even_amounts': round(self.even_amounts(pnl), 2), # 持平手数 'profit_precentage': round(self.profit_precentage(pnl), 2), 'loss_precentage': round(self.loss_precentage(pnl), 2), 'even_precentage': round(self.even_precentage(pnl), 2), 'average_profit': round(self.average_profit(pnl), 2), 'average_loss': round(self.average_loss(pnl), 2), 'average_pnl': round(self.average_pnl(pnl), 2), 'max_profit': round(self.max_profit(pnl), 2), 'max_loss': round(self.max_loss(pnl), 2), 'max_pnl': round(self.max_pnl(pnl), 2), 'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(pnl), 2), 'continue_profit_amount': round(self.continue_profit_amount(pnl), 2), 'continue_loss_amount': round(self.continue_loss_amount(pnl), 2), 'average_holdgap': self.average_holdgap(pnl), 'average_profitholdgap': self.average_profitholdgap(pnl), 'average_losssholdgap': self.average_losssholdgap(pnl)} @property def message(self): """[summary] 2. 3. 4. 5. 6. 7. 盈利手数 8. 亏损手数 9. 持平手数 10. 平均利润 11. 平均盈利 12. 平均亏损 13. 平均盈利/平均亏损 14. 最大盈利(单笔) 15. 最大亏损(单笔) 16. 最大盈利/总盈利 17. 最大亏损/总亏损 18. 净利润/最大亏损 19. 最大连续盈利手数 20. 最大连续亏损手数 21. 平均持仓周期 22. 平均盈利周期 23. 平均亏损周期 24. 平均持平周期 25. 最大使用资金 26. 最大持仓手数 27. 交易成本合计 28. 收益率 29. 年化收益率 30. 有效收益率 31. 月度平均盈利 32. 收益曲线斜率 33. 收益曲线截距 34. 收益曲线R2值 35. 夏普比例 36. 总交易时间 37. 总持仓时间 38. 持仓时间比例 39. 最大空仓时间 40. 持仓周期 41. 资产最大升水 42. 发生时间 43. 最大升水/前期低点 44. 单日最大资产回撤比率 45. 最大资产回撤值 46. 最大资产回撤发生时间 47. 回撤值/前期高点 48. 净利润/回撤值 Returns: [type] -- [description] """ return { # 总盈利(对于每个单笔而言) 'total_profit': round(self.total_profit(self.pnl), 2), 'total_loss': round(self.total_loss(self.pnl), 2), # 总亏损(对于每个单笔而言) 'total_pnl': round(self.total_pnl(self.pnl), 2), # 总盈利/总亏损 # 交易手数 'trading_amounts': round(self.trading_amounts(self.pnl), 2), 'profit_amounts': round(self.profit_amounts(self.pnl), 2), # 盈利手数 'loss_amounts': round(self.loss_amounts(self.pnl), 2), # 亏损手数 'even_amounts': round(self.even_amounts(self.pnl), 2), # 持平手数 'profit_precentage': round(self.profit_precentage(self.pnl), 2), 'loss_precentage': round(self.loss_precentage(self.pnl), 2), 'even_precentage': round(self.even_precentage(self.pnl), 2), 'average_profit': round(self.average_profit(self.pnl), 2), 'average_loss': round(self.average_loss(self.pnl), 2), 'average_pnl': round(self.average_pnl(self.pnl), 2), 'max_profit': round(self.max_profit(self.pnl), 2), 'max_loss': round(self.max_loss(self.pnl), 2), 'max_pnl': round(self.max_pnl(self.pnl), 2), 'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(self.pnl), 2), 'continue_profit_amount': round(self.continue_profit_amount(self.pnl), 2), 'continue_loss_amount': round(self.continue_loss_amount(self.pnl), 2), 'average_holdgap': self.average_holdgap(self.pnl), 'average_profitholdgap': self.average_profitholdgap(self.pnl), 'average_losssholdgap': self.average_losssholdgap(self.pnl), 'buyopen': self.base_message(self.pnl_buyopen), 'sellopen': self.base_message(self.pnl_sellopen) } @property def prefer(self): pass @property def style(self): """风格分析 """ pass @property def pnl_lifo(self): """ 使用后进先出法配对成交记录 """ X = dict( zip( self.target.code, [{'buy': LifoQueue(), 'sell': LifoQueue()} for i in range(len(self.target.code))] ) ) pair_table = [] for _, data in self.target.history_table_min.iterrows(): while True: if data.direction in[1, 2, -2]: if data.direction in [1, 2]: X[data.code]['buy'].append( (data.datetime, data.amount, data.price, data.direction) ) elif data.direction in [-2]: X[data.code]['sell'].append( (data.datetime, data.amount, data.price, data.direction) ) break elif data.direction in[-1, 3, -3]: rawoffset = 'buy' if data.direction in [-1, -3] else 'sell' l = X[data.code][rawoffset].get() if abs(l[1]) > abs(data.amount): """ if raw> new_close: """ temp = (l[0], l[1] + data.amount, l[2]) X[data.code][rawoffset].put_nowait(temp) if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2], rawoffset ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price, rawoffset ] ) break elif abs(l[1]) < abs(data.amount): data.amount = data.amount + l[1] if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], l[1], data.price, l[2], rawoffset ] ) else: pair_table.append( [ data.code, l[0], data.datetime, l[1], l[2], data.price, rawoffset ] ) else: if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2], rawoffset ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price, rawoffset ] ) break pair_title = [ 'code', 'sell_date', 'buy_date', 'amount', 'sell_price', 'buy_price', 'rawdirection' ] pnl = pd.DataFrame(pair_table, columns=pair_title) pnl = pnl.assign( unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)), pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1, sell_date=pd.to_datetime(pnl.sell_date), buy_date=pd.to_datetime(pnl.buy_date) ) pnl = pnl.assign( pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit, hold_gap=abs(pnl.sell_date - pnl.buy_date), if_buyopen=pnl.rawdirection == 'buy' ) pnl = pnl.assign( openprice=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price, opendate=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str), closeprice=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price, closedate=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str) ) return pnl.set_index('code') @property def pnl_buyopen(self): return self.pnl[self.pnl.if_buyopen] @property def pnl_sellopen(self): return self.pnl[~self.pnl.if_buyopen] @property def pnl_fifo(self): X = dict( zip( self.target.code, [{'buy': deque(), 'sell': deque()} for i in range(len(self.target.code))] ) ) pair_table = [] for _, data in self.target.history_table_min.iterrows(): while True: if data.direction in[1, 2, -2]: if data.direction in [1, 2]: X[data.code]['buy'].append( (data.datetime, data.amount, data.price, data.direction) ) elif data.direction in [-2]: X[data.code]['sell'].append( (data.datetime, data.amount, data.price, data.direction) ) break elif data.direction in[-1, 3, -3]: rawoffset = 'buy' if data.direction in [-1, -3] else 'sell' l = X[data.code][rawoffset].popleft() if abs(l[1]) > abs(data.amount): """ if raw> new_close: """ temp = (l[0], l[1] + data.amount, l[2]) X[data.code][rawoffset].appendleft(temp) if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2], rawoffset ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price, rawoffset ] ) break elif abs(l[1]) < abs(data.amount): data.amount = data.amount + l[1] if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], l[1], data.price, l[2], rawoffset ] ) else: pair_table.append( [ data.code, l[0], data.datetime, l[1], l[2], data.price, rawoffset ] ) else: if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2], rawoffset ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price, rawoffset ] ) break pair_title = [ 'code', 'sell_date', 'buy_date', 'amount', 'sell_price', 'buy_price', 'rawdirection' ] pnl = pd.DataFrame(pair_table, columns=pair_title) pnl = pnl.assign( unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)), pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1, sell_date=pd.to_datetime(pnl.sell_date), buy_date=pd.to_datetime(pnl.buy_date) ) pnl = pnl.assign( pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit, hold_gap=abs(pnl.sell_date - pnl.buy_date), if_buyopen=pnl.rawdirection == 'buy' ) pnl = pnl.assign( openprice=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price, opendate=pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str), closeprice=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price, closedate=pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str) ) return pnl.set_index('code') def plot_pnlratio(self): """ 画出pnl比率散点图 """ plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_ratio) plt.gcf().autofmt_xdate() return plt def plot_pnlmoney(self): """ 画出pnl盈亏额散点图 """ plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_money) plt.gcf().autofmt_xdate() return plt def abnormal_active(self): """ 账户的成交发生异常成交记录的分析 """ pass def brinson(self): """Brinson Model analysis """ pass def hold(self): """持仓分析 """ pass def win_rate(self): """胜率 胜率 盈利次数/总次数 """ data = self.pnl try: return round(len(data.query('pnl_money>0')) / len(data), 2) except ZeroDivisionError: return 0 @property def accumulate_return(self): """ returns a pd-Dataframe format accumulate return for different periods """ pass def save(self): """save the performance analysis result to database """ pass def profit_pnl(self, pnl): return pnl.query('pnl_money>0') def loss_pnl(self, pnl): return pnl.query('pnl_money<0') def even_pnl(self, pnl): return pnl.query('pnl_money==0') def total_profit(self, pnl): if len(self.profit_pnl(pnl)) > 0: return self.profit_pnl(pnl).pnl_money.sum() else: return 0 def total_loss(self, pnl): if len(self.loss_pnl(pnl)) > 0: return self.loss_pnl(pnl).pnl_money.sum() else: return 0 def total_pnl(self, pnl): try: return abs(self.total_profit(pnl) / self.total_loss(pnl)) except ZeroDivisionError: return 0 def trading_amounts(self, pnl): return len(pnl) def profit_amounts(self, pnl): return len(self.profit_pnl(pnl)) def loss_amounts(self, pnl): return len(self.loss_pnl(pnl)) def even_amounts(self, pnl): return len(self.even_pnl(pnl)) def profit_precentage(self, pnl): try: return self.profit_amounts(pnl) / self.trading_amounts(pnl) except ZeroDivisionError: return 0 def loss_precentage(self, pnl): try: return self.loss_amounts(pnl) / self.trading_amounts(pnl) except ZeroDivisionError: return 0 def even_precentage(self, pnl): try: return self.even_amounts(pnl) / self.trading_amounts(pnl) except ZeroDivisionError: return 0 def average_loss(self, pnl): if len(self.loss_pnl(pnl)) > 0: return self.loss_pnl(pnl).pnl_money.mean() else: return 0 def average_profit(self, pnl): if len(self.profit_pnl(pnl)) > 0: return self.profit_pnl(pnl).pnl_money.mean() else: return 0 def average_pnl(self, pnl): if len(self.loss_pnl(pnl)) > 0 and len(self.profit_pnl(pnl)) > 0: try: return abs(self.average_profit(pnl) / self.average_loss(pnl)) except ZeroDivisionError: return 0 else: return 0 def max_profit(self, pnl): if len(self.profit_pnl(pnl)) > 0: return self.profit_pnl(pnl).pnl_money.max() else: return 0 def max_loss(self, pnl): if len(self.loss_pnl(pnl)) > 0: return self.loss_pnl(pnl).pnl_money.min() else: return 0 def max_pnl(self, pnl): try: return abs(self.max_profit(pnl) / self.max_loss(pnl)) except ZeroDivisionError: return 0 def netprofio_maxloss_ratio(self, pnl): if len(self.loss_pnl(pnl)) > 0: try: return abs(pnl.pnl_money.sum() / self.max_loss(pnl)) except ZeroDivisionError: return 0 else: return 0 def continue_profit_amount(self, pnl): w = [] w1 = 0 for _, item in pnl.pnl_money.iteritems(): if item > 0: w1 += 1 elif item < 0: w.append(w1) w1 = 0 if len(w) == 0: return 0 else: return max(w) def continue_loss_amount(self, pnl): l = [] l1 = 0 for _, item in pnl.pnl_money.iteritems(): if item > 0: l1 += 1 elif item < 0: l.append(l1) l1 = 0 if len(l) == 0: return 0 else: return max(l) def average_holdgap(self, pnl): if len(pnl.hold_gap) > 0: return str(pnl.hold_gap.mean()) else: return 'no trade' def average_profitholdgap(self, pnl): if len(self.profit_pnl(pnl).hold_gap) > 0: return str(self.profit_pnl(pnl).hold_gap.mean()) else: return 'no trade' def average_losssholdgap(self, pnl): if len(self.loss_pnl(pnl).hold_gap) > 0: return str(self.loss_pnl(pnl).hold_gap.mean()) else: return 'no trade' def average_evenholdgap(self, pnl): if len(self.even_pnl(pnl).hold_gap) > 0: return self.even_pnl(pnl).hold_gap.mean() else: return 'no trade' @property def max_cashused(self): return self.target.init_cash - min(self.target.cash) @property def total_taxfee(self): return self.target.history_table_min.commission.sum( ) + self.target.history_table_min.tax.sum()
class QA_Position(): """一个持仓模型:/兼容股票期货/兼容保证金持仓 兼容快期DIFF协议 基础字段 code [str] 品种名称 volume_long_today [float] 今仓 多单持仓 volume_long_his [float] 昨仓 多单持仓 volume_short_today [float] 今仓 空单持仓 volume_short_his [float] 昨仓 空单持仓 volume_long_frozen_his [float] 多单昨日冻结 volume_long_frozen_today [float] 多单今日冻结 volume_short_frozen_his [float] 空单昨日冻结 volume_short_frozen_today [float] 空单今日冻结 margin_long [float] 多单保证金 margin_short [float] 空单保证金 open_price_long [float] 多单开仓价 open_price_short [float] 空单开仓价 position_price_long [float] 逐日盯市的前一交易日的结算价 position_price_short [float] 逐日盯市的前一交易日的结算价 open_cost_long [float] 多单开仓成本(指的是保证金冻结) open_cost_short [float] 空单开仓成本 position_cost_long [float] 多单持仓成本(指的是基于逐日盯市制度下的成本价) position_cost_short [float] 空单持仓成本 market_type=MARKET_TYPE.STOCK_CN [enum] 市场类别 exchange_id=EXCHANGE_ID.SZSE [enum] 交易所id(支持股票/期货) name=None [str] 名称 功能: 1/ 支持当价格变化后的 持仓的自行计算更新 2/ 支持调仓模型(未加入) 3/ 支持仓位风控(未加入) 4/ 支持资金分配和PMS内部资金结算(moneypreset) PMS 内部可以预分配一个资金限额, 方便pms实时计算属于PMS的收益 兼容QA_Account的创建/拆入Positions库 QAPosition 不对订单信息做正确性保证, 需要自行在外部构建 OMS系统 {QACEPEngine/QAAccountPro} """ def __init__( self, code='000001', account_cookie='quantaxis', portfolio_cookie='portfolio', username='******', moneypreset=100000, # 初始分配资金 frozen=None, moneypresetLeft=None, volume_long_today=0, volume_long_his=0, volume_short_today=0, volume_short_his=0, volume_long_frozen_his=0, volume_long_frozen_today=0, volume_short_frozen_his=0, volume_short_frozen_today=0, margin_long=0, margin_short=0, open_price_long=0, open_price_short=0, position_price_long=0, # 逐日盯市的前一交易日的结算价 position_price_short=0, # 逐日盯市的前一交易日的结算价 open_cost_long=0, open_cost_short=0, position_cost_long=0, position_cost_short=0, position_id=None, market_type=None, exchange_id=None, trades=None, orders=None, name=None, commission=0, auto_reload=False, allow_exceed=False, spms_id=None, oms_id=None, *args, **kwargs): self.code = code self.account_cookie = account_cookie self.portfolio_cookie = portfolio_cookie self.username = username self.time = '' self.market_preset = MARKET_PRESET().get_code(self.code) self.position_id = str( uuid.uuid4()) if position_id is None else position_id self.moneypreset = moneypreset self.moneypresetLeft = self.moneypreset if moneypresetLeft is None else moneypresetLeft """{'name': '原油', 'unit_table': 1000, 'price_tick': 0.1, 'buy_frozen_coeff': 0.1, 'sell_frozen_coeff': 0.1, 'exchange': 'INE', 'commission_coeff_peramount': 0, 'commission_coeff_pervol': 20.0, 'commission_coeff_today_peramount': 0, 'commission_coeff_today_pervol': 0.0} """ self.rule = 'FIFO' self.name = name if market_type is None: self.market_type = MARKET_TYPE.FUTURE_CN if re.search( r'[a-zA-z]+', self.code) else MARKET_TYPE.STOCK_CN self.exchange_id = exchange_id self.volume_long_his = volume_long_his self.volume_long_today = volume_long_today self.volume_short_his = volume_short_his self.volume_short_today = volume_short_today self.volume_long_frozen_his = volume_long_frozen_his self.volume_long_frozen_today = volume_long_frozen_today self.volume_short_frozen_his = volume_short_frozen_his self.volume_short_frozen_today = volume_short_frozen_today self.margin_long = margin_long self.margin_short = margin_short self.open_price_long = open_price_long self.open_price_short = open_price_short self.position_price_long = open_price_long if position_price_long == 0 else position_price_long self.position_price_short = open_price_short if position_price_short == 0 else position_price_short self.open_cost_long = open_cost_long if open_cost_long != 0 else open_price_long * \ self.volume_long*self.market_preset.get('unit_table', 1) self.open_cost_short = open_cost_short if open_cost_short != 0 else open_price_short * \ self.volume_short*self.market_preset.get('unit_table', 1) self.position_cost_long = position_cost_long if position_cost_long != 0 else self.position_price_long * \ self.volume_long*self.market_preset.get('unit_table', 1) self.position_cost_short = position_cost_short if position_cost_short != 0 else self.position_price_short * \ self.volume_short*self.market_preset.get('unit_table', 1) self.last_price = 0 self.commission = commission self.trades = [] if trades is None else trades self.orders = {} if orders is None else orders self.frozen = {} if frozen is None else frozen self.spms_id = spms_id self.oms_id = oms_id if auto_reload: self.reload() self.allow_exceed = allow_exceed def __repr__(self): return '< QAPOSITION {} amount {}/{} >'.format(self.code, self.volume_long, self.volume_short) def read_diff(self, diff_slice): """[summary] Arguments: diff_slice {dict} -- [description] {'user_id': '100002', 'exchange_id': 'SHFE', 'instrument_id': 'rb1905', 'volume_long_today': 0, 'volume_long_his': 0, 'volume_long': 0, 'volume_long_frozen_today': 0, 'volume_long_frozen_his': 0, 'volume_long_frozen': 0, 'volume_short_today': 0, 'volume_short_his': 0, 'volume_short': 0, 'volume_short_frozen_today': 0, 'volume_short_frozen_his': 0, 'volume_short_frozen': 0, 'open_price_long': 4193.0, 'open_price_short': 4192.0, 'open_cost_long': 0.0, 'open_cost_short': 0.0, 'position_price_long': 4193.0, 'position_price_short': 4192.0, 'position_cost_long': 0.0, 'position_cost_short': 0.0, 'last_price': 4137.0, 'float_profit_long': 0.0, 'float_profit_short': 0.0, 'float_profit': 0.0, 'position_profit_long': 0.0, 'position_profit_short': 0.0, 'position_profit': 0.0, 'margin_long': 0.0, 'margin_short': 0.0, 'margin': 0.0} Returns: QA_Position -- [description] """ self.account_cookie = diff_slice['user_id'] self.code = diff_slice['instrument_id'] self.volume_long_today = diff_slice['volume_long_today'] self.volume_long_his = diff_slice['volume_long_his'] self.volume_long_frozen_today = diff_slice['volume_long_frozen_today'] self.volume_long_frozen_his = diff_slice['volume_long_frozen_his'] self.volume_short_today = diff_slice['volume_short_today'] self.volume_short_his = diff_slice['volume_short_his'] self.volume_short_frozen_today = diff_slice[ 'volume_short_frozen_today'] self.volume_short_frozen_his = diff_slice['volume_short_frozen_his'] self.open_price_long = diff_slice['open_price_long'] self.open_price_short = diff_slice['open_price_short'] self.open_cost_long = diff_slice['open_cost_long'] self.open_cost_short = diff_slice['open_cost_short'] self.position_price_long = diff_slice['position_price_long'] self.position_price_short = diff_slice['position_price_short'] self.position_cost_long = diff_slice['position_cost_long'] self.position_cost_short = diff_slice['position_cost_short'] self.margin_long = diff_slice['margin_long'] self.margin_short = diff_slice['margin_short'] self.exchange_id = diff_slice['exchange_id'] self.market_type = MARKET_TYPE.FUTURE_CN return self @property def volume_long(self): return self.volume_long_today + self.volume_long_his @property def volume_short(self): return self.volume_short_his + self.volume_short_today @property def volume_long_frozen(self): return self.volume_long_frozen_his + self.volume_long_frozen_today @property def volume_short_frozen(self): return self.volume_short_frozen_his + self.volume_short_frozen_today @property def margin(self): return self.margin_long + self.margin_short @property def float_profit_long(self): if self.market_preset is not None: return self.last_price * self.volume_long * self.market_preset.get( 'unit_table', 1) - self.open_cost_long @property def float_profit_short(self): if self.market_preset is not None: return self.open_cost_short - self.last_price * self.volume_short * self.market_preset.get( 'unit_table', 1) @property def float_profit(self): return self.float_profit_long + self.float_profit_short @property def position_profit_long(self): if self.market_preset is not None: return self.last_price * self.volume_long * self.market_preset.get( 'unit_table', 1) - self.position_cost_long @property def position_profit_short(self): if self.market_preset is not None: return self.position_cost_short - self.last_price * self.volume_short * self.market_preset.get( 'unit_table', 1) @property def position_profit(self): return self.position_profit_long + self.position_profit_short @property def static_message(self): return { # 基础字段 'code': self.code, # 品种名称 'instrument_id': self.code, 'user_id': self.account_cookie, 'portfolio_cookie': self.portfolio_cookie, 'username': self.username, 'position_id': self.position_id, 'account_cookie': self.account_cookie, 'frozen': self.frozen, 'name': self.name, 'spms_id': self.spms_id, 'oms_id': self.oms_id, 'market_type': self.market_type, 'exchange_id': self.exchange_id, # 交易所ID 'moneypreset': self.moneypreset, 'moneypresetLeft': self.moneypresetLeft, 'lastupdatetime': str(self.time), # 持仓量 'volume_long_today': self.volume_long_today, 'volume_long_his': self.volume_long_his, 'volume_long': self.volume_long, 'volume_short_today': self.volume_short_today, 'volume_short_his': self.volume_short_his, 'volume_short': self.volume_short, # 平仓委托冻结(未成交) 'volume_long_frozen_today': self.volume_long_frozen_today, 'volume_long_frozen_his': self.volume_long_frozen_his, 'volume_long_frozen': self.volume_long_frozen, 'volume_short_frozen_today': self.volume_short_frozen_today, 'volume_short_frozen_his': self.volume_short_frozen_his, 'volume_short_frozen': self.volume_short_frozen, # 保证金 'margin_long': self.margin_long, # 多头保证金 'margin_short': self.margin_short, 'margin': self.margin, # 持仓字段 'position_price_long': self.position_price_long, # 多头成本价 'position_cost_long': self.position_cost_long, # 多头总成本( 总市值) 'position_price_short': self.position_price_short, 'position_cost_short': self.position_cost_short, # 平仓字段 'open_price_long': self.open_price_long, # 多头开仓价 'open_cost_long': self.open_cost_long, # 多头开仓成本 'open_price_short': self.open_price_short, # 空头开仓价 'open_cost_short': self.open_cost_short, # 空头成本 # 历史字段 'trades': self.trades, 'orders': self.orders } @property def hold_detail(self): return { # 持仓量 'volume_long_today': self.volume_long_today, 'volume_long_his': self.volume_long_his, 'volume_long': self.volume_long, 'volume_short_today': self.volume_short_today, 'volume_short_his': self.volume_short_his, 'volume_short': self.volume_short } @property def realtime_message(self): return { # 扩展字段 "last_price": self.last_price, # //多头浮动盈亏 ps.last_price * ps.volume_long * ps.ins->volume_multiple - ps.open_cost_long; "float_profit_long": self.float_profit_long, # //空头浮动盈亏 ps.open_cost_short - ps.last_price * ps.volume_short * ps.ins->volume_multiple; "float_profit_short": self.float_profit_short, # //浮动盈亏 = float_profit_long + float_profit_short "float_profit": self.float_profit, "position_profit_long": self.position_profit_long, # //多头持仓盈亏 "position_profit_short": self.position_profit_short, # //空头持仓盈亏 # //持仓盈亏 = position_profit_long + position_profit_short "position_profit": self.position_profit } @property def message(self): msg = self.static_message msg.update(self.realtime_message) return msg def order_check(self, amount: float, price: float, towards: int, order_id: str) -> bool: res = False if towards == ORDER_DIRECTION.BUY_CLOSE: # print('buyclose') #print(self.volume_short - self.volume_short_frozen) # print(amount) if (self.volume_short - self.volume_short_frozen) >= amount: # check self.volume_short_frozen_today += amount res = True else: print('BUYCLOSE 仓位不足') elif towards == ORDER_DIRECTION.BUY_CLOSETODAY: if (self.volume_short_today - self.volume_short_frozen_today) >= amount: self.volume_short_frozen_today += amount res = True else: print('BUYCLOSETODAY 今日仓位不足') elif towards == ORDER_DIRECTION.SELL_CLOSE: # print('sellclose') #print(self.volume_long - self.volume_long_frozen) # print(amount) if (self.volume_long - self.volume_long_frozen) >= amount: self.volume_long_frozen_today += amount res = True else: print('SELL CLOSE 仓位不足') elif towards == ORDER_DIRECTION.SELL_CLOSETODAY: if (self.volume_long_today - self.volume_short_frozen_today) >= amount: # print('sellclosetoday') #print(self.volume_long_today - self.volume_long_frozen) # print(amount) self.volume_long_frozen_today += amount return True else: print('SELLCLOSETODAY 今日仓位不足') elif towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN, ORDER_DIRECTION.BUY ]: """ 冻结的保证金 """ moneyneed = float(amount) * float(price) * float( self.market_preset.get('unit_table', 1)) * float( self.market_preset.get('buy_frozen_coeff', 1)) if (self.moneypresetLeft > moneyneed) or self.allow_exceed: self.moneypresetLeft -= moneyneed self.frozen[order_id] = moneyneed res = True else: print('开仓保证金不足 TOWARDS{} Need{} HAVE{}'.format( towards, moneyneed, self.moneypresetLeft)) return res def send_order(self, amount: float, price: float, towards: int): order_id = str(uuid.uuid4()) if self.order_check(amount, price, towards, order_id): #print('order check success') order = { 'position_id': str(self.position_id), 'account_cookie': self.account_cookie, 'instrument_id': self.code, 'towards': int(towards), 'exchange_id': str(self.exchange_id), 'order_time': str(self.time), 'volume': float(amount), 'price': float(price), 'order_id': order_id, 'status': ORDER_STATUS.NEW } self.orders[order_id] = order return order else: print(RuntimeError('ORDER CHECK FALSE: {}'.format(self.code))) return False def update_pos(self, price, amount, towards): """支持股票/期货的更新仓位 Arguments: price {[type]} -- [description] amount {[type]} -- [description] towards {[type]} -- [description] margin: 30080 margin_long: 0 margin_short: 30080 open_cost_long: 0 open_cost_short: 419100 open_price_long: 4193 open_price_short: 4191 position_cost_long: 0 position_cost_short: 419100 position_price_long: 4193 position_price_short: 4191 position_profit: -200 position_profit_long: 0 position_profit_short: -200 """ self.on_pirce_change(price) temp_cost = float(amount)*float(price) * \ float(self.market_preset.get('unit_table', 1)) profit = 0 if towards == ORDER_DIRECTION.BUY: # 股票模式/ 期货买入开仓 marginValue = temp_cost self.margin_long += marginValue # 重算开仓均价 self.open_price_long = (self.open_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 重算持仓均价 self.position_price_long = ( self.position_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 增加今仓数量 ==> 会自动增加volume_long self.volume_long_today += amount # self.open_cost_long += temp_cost self.position_cost_long += temp_cost elif towards == ORDER_DIRECTION.SELL: # 股票卖出模式: # 今日买入仓位不能卖出 if self.volume_long_his > amount: self.position_cost_long = self.position_cost_long * \ (self.volume_long - amount)/self.volume_long self.open_cost_long = self.open_cost_long * \ (self.volume_long-amount)/self.volume_long self.volume_long_his -= amount self.volume_long_frozen_today -= amount marginValue = -1 * (self.position_price_long * amount) profit = (price - self.position_price_long) * amount self.moneypresetLeft += (-marginValue + profit) elif towards == ORDER_DIRECTION.BUY_OPEN: # 增加保证金 marginValue = temp_cost * \ self.market_preset['buy_frozen_coeff'] self.margin_long += marginValue # 重算开仓均价 self.open_price_long = (self.open_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 重算持仓均价 self.position_price_long = ( self.position_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 增加今仓数量 ==> 会自动增加volume_long self.volume_long_today += amount # self.open_cost_long += temp_cost self.position_cost_long += temp_cost self.moneypresetLeft -= marginValue elif towards == ORDER_DIRECTION.SELL_OPEN: # 增加保证金 """ 1. 增加卖空保证金 2. 重新计算 开仓成本 3. 重新计算 持仓成本 4. 增加开仓cost 5. 增加持仓cost 6. 增加空单仓位 """ marginValue = temp_cost * \ self.market_preset['sell_frozen_coeff'] self.margin_short += marginValue # 重新计算开仓/持仓成本 self.open_price_short = ( self.open_price_short * self.volume_short + amount * price) / (amount + self.volume_short) self.position_price_short = ( self.position_price_short * self.volume_short + amount * price) / (amount + self.volume_short) self.open_cost_short += temp_cost self.position_cost_short += temp_cost self.volume_short_today += amount self.moneypresetLeft -= marginValue elif towards == ORDER_DIRECTION.BUY_CLOSETODAY: if self.volume_short_today > amount: self.position_cost_short = self.position_cost_short * \ (self.volume_short-amount)/self.volume_short self.open_cost_short = self.open_cost_short * \ (self.volume_short-amount)/self.volume_short self.volume_short_today -= amount self.volume_short_frozen_today += amount # close_profit = (self.position_price_short - price) * volume * position->ins->volume_multiple; marginValue = -(self.position_price_short * amount*self.market_preset.get('unit_table') *\ self.market_preset['sell_frozen_coeff']) profit = (self.position_price_short - price ) * amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) # 释放保证金 # TODO # self.margin_short #self.open_cost_short = price* amount elif towards == ORDER_DIRECTION.SELL_CLOSETODAY: if self.volume_long_today > amount: self.position_cost_long = self.position_cost_long * \ (self.volume_long - amount)/self.volume_long self.open_cost_long = self.open_cost_long * \ (self.volume_long-amount)/self.volume_long self.volume_long_today -= amount self.volume_long_frozen_today += amount marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\ self.market_preset['buy_frozen_coeff']) profit = (price - self.position_price_long) * \ amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) elif towards == ORDER_DIRECTION.BUY_CLOSE: # 有昨仓先平昨仓 self.position_cost_short = self.position_cost_short * \ (self.volume_short-amount)/self.volume_short self.open_cost_short = self.open_cost_short * \ (self.volume_short-amount)/self.volume_short if self.volume_short_his >= amount: self.volume_short_his -= amount else: self.volume_short_today -= (amount - self.volume_short_his) self.volume_short_his = 0 self.volume_short_frozen_today -= amount marginValue = -1*(self.position_price_short * amount*self.market_preset.get('unit_table') *\ self.market_preset['sell_frozen_coeff']) profit = (self.position_price_short - price) * amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) elif towards == ORDER_DIRECTION.SELL_CLOSE: # 有昨仓先平昨仓 self.position_cost_long = self.position_cost_long * \ (self.volume_long - amount)/self.volume_long self.open_cost_long = self.open_cost_long * \ (self.volume_long-amount)/self.volume_long if self.volume_long_his >= amount: self.volume_long_his -= amount else: self.volume_long_today -= (amount - self.volume_long_his) self.volume_long_his = 0 self.volume_long_frozen_today -= amount marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\ self.market_preset['buy_frozen_coeff']) profit = (price - self.position_price_long) * \ amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) # 计算收益/成本 return marginValue, profit def settle(self): """收盘后的结算事件 """ self.volume_long_his += self.volume_long_today self.volume_long_today = 0 self.volume_long_frozen_today = 0 self.volume_short_his += self.volume_short_today self.volume_short_today = 0 self.volume_short_frozen_today = 0 @property def curpos(self): return { 'volume_long': self.volume_long, 'volume_short': self.volume_short, 'volume_long_frozen': self.volume_long_frozen, 'volume_short_frozen': self.volume_short_frozen } @property def close_available(self): """可平仓数量 Returns: [type] -- [description] """ return { 'volume_long': self.volume_long - self.volume_long_frozen, 'volume_short': self.volume_short - self.volume_short_frozen } def change_moneypreset(self, money): self.moneypreset = money def save(self): """save&update save data to mongodb | update """ print(self.static_message) save_position(self.static_message) def reload(self): res = DATABASE.positions.find_one({ 'account_cookie': self.account_cookie, 'portfolio_cookie': self.portfolio_cookie, 'username': self.username, 'position_id': self.position_id }) if res is None: self.save() else: self.loadfrommessage(res) def calc_commission( self, trade_price, trade_amount, trade_towards, ): if self.market_type == MARKET_TYPE.FUTURE_CN: # 期货不收税 # 双边手续费 也没有最小手续费限制 value = trade_price * trade_amount * \ self.market_preset.get_unit(code) commission_fee_preset = self.market_preset.get_code(code) if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN ]: commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_peramount'] * \ abs(value) elif trade_towards in [ ORDER_DIRECTION.BUY_CLOSETODAY, ORDER_DIRECTION.SELL_CLOSETODAY ]: commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_today_peramount'] * \ abs(value) return commission_fee def loadfrommessage(self, message): self.__init__( code=message['code'], account_cookie=message['account_cookie'], frozen=message['frozen'], portfolio_cookie=message['portfolio_cookie'], username=message['username'], moneypreset=message['moneypreset'], # 初始分配资金 moneypresetLeft=message['moneypresetLeft'], volume_long_today=message['volume_long_today'], volume_long_his=message['volume_long_his'], volume_short_today=message['volume_short_today'], volume_short_his=message['volume_short_his'], volume_long_frozen_his=message['volume_long_frozen_his'], volume_long_frozen_today=message['volume_long_frozen_today'], volume_short_frozen_his=message['volume_short_frozen_his'], volume_short_frozen_today=message['volume_short_frozen_today'], margin_long=message['margin_long'], margin_short=message['margin_short'], open_price_long=message['open_price_long'], open_price_short=message['open_price_short'], # 逐日盯市的前一交易日的结算价 position_price_long=message['position_price_long'], # 逐日盯市的前一交易日的结算价 position_price_short=message['position_price_short'], open_cost_long=message['open_cost_long'], open_cost_short=message['open_cost_short'], position_cost_long=message['position_cost_long'], position_cost_short=message['position_cost_short'], position_id=message['position_id'], market_type=message['market_type'], exchange_id=message['exchange_id'], trades=message['trades'], orders=message['orders'], commission=message['commission'], name=message['name']) return self def on_order(self, order: QA_Order): """这里是一些外部操作导致的POS变化 - 交易过程的外部手动交易 - 风控状态下的监控外部交易 order_id 是外部的 trade_id 不一定存在 """ if order['order_id'] not in self.frozen.keys(): print('OUTSIDE ORDER') # self.frozen[order['order_id']] = order[] # 回放订单/注册进订单系统 order = self.send_order( order.get('amount', order.get('volume')), order['price'], eval('ORDER_DIRECTION.{}_{}'.format(order.get('direction'), order.get('offset')))) self.orders[order]['status'] = ORDER_STATUS.QUEUED def on_transaction(self, transaction: dict): towards = transaction.get( 'towards', eval('ORDER_DIRECTION.{}_{}'.format(transaction.get('direction'), transaction.get('offset')))) transaction['towards'] = towards # TODO: # 在这里可以加入更多关于PMS交易的代码 try: self.update_pos( transaction['price'], transaction.get('amount', transaction.get('volume')), towards) self.moneypresetLeft += self.frozen.get(transaction['order_id'], 0) # 当出现外部交易的时候, 直接在frozen中注册订单 self.frozen[transaction['order_id']] = 0 self.orders[transaction['order_id']] = ORDER_STATUS.SUCCESS_ALL self.trades.append(transaction) except Exception as e: raise e def on_pirce_change(self, price): self.last_price = price def on_bar(self, bar): """只订阅这个code的数据 Arguments: bar {[type]} -- [description] """ self.last_price = bar['close'] # print(self.realtime_message) pass def on_tick(self, tick): """只订阅当前code的tick Arguments: tick {[type]} -- [description] """ self.last_price = tick['LastPrice'] # print(self.realtime_message) pass def on_signal(self, signal): raise NotImplementedError('此接口为内部接口 为CEP专用') def callback_sub(self): raise NotImplementedError('此接口为内部接口 为CEP专用') def callback_pub(self): raise NotImplementedError('此接口为内部接口 为CEP专用')
class QA_Account(QA_Worker): """QA_Account User-->Portfolio-->Account/Strategy ::::::::::::::::::::::::::::::::::::::::::::::::: :: :: Portfolio 1 -- Account/Strategy 1 :: :: USER :: -- Account/Strategy 2 :: :: :: Portfolio 2 -- Account/Strategy 3 :: ::::::::::::::::::::::::::::::::::::::::::::::::: 2018/1/5 再次修改 改版本去掉了多余的计算 精简账户更新 ====================== - 不再计算总资产/不再计算当前持仓/不再计算交易对照明细表 - 不再动态计算账户股票/期货市值 - 只维护 cash/history两个字段 剩下的全部惰性计算 QA_Account 是QUANTAXIS的最小不可分割单元之一 QA_Account是账户类 需要兼容股票/期货/指数 QA_Account继承自QA_Worker 可以被事件驱动 QA_Account可以直接被QA_Strategy继承 有三类输入: 信息类: 账户绑定的策略名/账户的用户名/账户类别/账户识别码/账户的broker 资产类: 现金/可用现金/交易历史/交易对照表 规则类: 是否允许卖空/是否允许t0结算 方法: 惰性计算:最新持仓/最新总资产/最新现金/持仓面板 生成订单/接受交易结果数据 接收新的数据/on_bar/on_tick方法/缓存新数据的market_data @royburns 1.添加注释 2018/05/18 T0交易的sell_available和正常的sell_available不一样: T0交易中, 当买入一笔/卖出一笔, 当天操作额度都会下降 T0的订单-账户对应系统 @2018/06/11 QA_Account不会基于行情计算市值,因此都只会对应记录证券数量和现金资产 @2018/12/23 当我们继承/复用 QA_Account 的时候, 我们需要实现什么 - init_cash - init_hold - broker @2018/12/24 账户需要追踪 @2018/12/31 账户需要截面数据 需要在任意截面的基础上往下做 基础的信息截面 ==> init_cash/init_hold | history的初始值 任意时间点的信息截面 """ def __init__(self, strategy_name=None, user_cookie=None, portfolio_cookie=None, account_cookie=None, market_type=MARKET_TYPE.STOCK_CN, frequence=FREQUENCE.DAY, broker=BROKER_TYPE.BACKETEST, init_hold={}, init_cash=1000000, commission_coeff=0.00025, tax_coeff=0.001, margin_level={}, allow_t0=False, allow_sellopen=False, allow_margin=False, running_environment=RUNNING_ENVIRONMENT.BACKETEST): """ :param [str] strategy_name: 策略名称 :param [str] user_cookie: 用户cookie :param [str] portfolio_cookie: 组合cookie :param [str] account_cookie: 账户cookie :param [dict] init_hold 初始化时的股票资产 :param [float] init_cash: 初始化资金 :param [float] commission_coeff: 交易佣金 :默认 万2.5 float 类型 :param [float] tax_coeff: 印花税 :默认 千1.5 float 类型 :param [Bool] margin_level: 保证金比例 默认{} :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :param [Bool] allow_margin: 是否允许保证金交易 默认False ### 注意 >>>>>>>>>>>>> 在期货账户中: allow_t0/ allow_sellopen 是必须打开的 allow_margin 是作为保证金账户的开关 默认关闭 可以打开 则按照market_preset中的保证金比例来计算 具体可以参见: https://github.com/QUANTAXIS/QUANTAXIS/blob/master/EXAMPLE/test_backtest/FUTURE/TEST_%E4%BF%9D%E8%AF%81%E9%87%91%E8%B4%A6%E6%88%B7.ipynb >>>>>>>>>>>>> :param [QA.PARAM] market_type: 市场类别 默认QA.MARKET_TYPE.STOCK_CN A股股票 :param [QA.PARAM] frequence: 账户级别 默认日线QA.FREQUENCE.DAY :param [QA.PARAM] broker: BROEKR类 默认回测 QA.BROKER_TYPE.BACKTEST :param [QA.PARAM] running_environment 当前运行环境 默认Backtest # 2018/06/11 init_assets 从float变为dict,并且不作为输入,作为只读属性 # :param [float] init_assets: 初始资产 默认 1000000 元 (100万) init_assets:{ cash: xxx, stock: {'000001':2000}, init_date: '2018-02-05', init_datetime: '2018-02-05 15:00:00' } # 2018/06/11 取消在初始化的时候的cash和history输入 # :param [list] cash: 可用现金 默认 是 初始资产 list 类型 # :param [list] history: 交易历史 # 2018/11/9 修改保证金交易 # 我们把冻结的保证金 看做是未来的已实现交易: # 如==> 当前的一手空单 认为是未来的卖出成交(已知价格 不知时间) # 因此我们如此对于保证金交易进行评估: # 账户买入: 多单开仓: cash 下降x 保证金增加x 增加一手未来的卖出合约(持仓) ==> 平仓: cash上升 保证金恢复 cash + frozen(平仓释放) + 未平仓位 cash, available_cash frozen{ RB1901: { towards 2: {avg_money : xxx, amount: xxx, queue: collection.deque()}, towards -2: {avg_money, amount, queue: collection.deque()} }, IF1901: { towards 2: {avg_money, amount,queue: collection.deque()}, towards -2: {avg_money, amount,queue: collection.deque()} } } } hold: { RB1901: { 1, amount, # 多单待平仓 -1, amount # 空单待平仓 } } """ super().__init__() # warnings.warn('QUANTAXIS 1.0.46 has changed the init_assets ==> init_cash, please pay attention to this change if you using init_cash to initial an account class,\ # ', DeprecationWarning, stacklevel=2) self._history_headers = [ 'datetime', 'code', 'price', 'amount', 'cash', 'order_id', 'realorder_id', 'trade_id', 'account_cookie', 'commission', 'tax', 'message', 'frozen' ] ######################################################################## # 信息类: self.strategy_name = strategy_name self.user_cookie = user_cookie self.portfolio_cookie = portfolio_cookie self.account_cookie = QA_util_random_with_topic( 'Acc') if account_cookie is None else account_cookie self.market_type = market_type self.broker = broker self.frequence = frequence self.running_environment = running_environment ######################################################################## self._market_data = None self._currenttime = None self.commission_coeff = commission_coeff self.tax_coeff = tax_coeff self.datetime = None self.running_time = datetime.datetime.now() self.quantaxis_version = __version__ ######################################################################## # 资产类 self.orders = QA_OrderQueue() # 历史委托单 self.init_cash = init_cash self.init_hold = pd.Series(init_hold, name='amount') if isinstance( init_hold, dict) else init_hold self.init_hold.index.name = 'code' self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 可用资金 self.sell_available = copy.deepcopy(self.init_hold) self.buy_available = copy.deepcopy(self.init_hold) self.history = [] self.time_index = [] # 在回测中, 每日结算后更新 # 真实交易中, 为每日初始化/每次重新登录后的同步信息 self.static_balance = { 'static_assets': [], 'cash': [], 'frozen': [], 'hold': [], 'date': [] } # 日结算 self.today_trade = {'last': [], 'current': []} self.today_orders = {'last': [], 'current': []} ######################################################################## # 规则类 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) # 期货: allow_t0 True allow_sellopen True # self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.allow_margin = allow_margin self.margin_level = margin_level # 保证金比例 if self.market_type is MARKET_TYPE.FUTURE_CN: self.allow_t0 = True self.allow_sellopen = True if self.allow_t0 and self.allow_sellopen or self.market_type is MARKET_TYPE.FUTURE_CN: self.load_marketpreset() """期货的多开/空开 ==> 资金冻结进保证金 frozen 对应平仓的时候, 释放保证金 1. frozen 是一个dict : {[code]:queue} key是标的 value是对应的交易queue """ self.frozen = {} # 冻结资金(保证金) def __repr__(self): return '< QA_Account {}>'.format(self.account_cookie) @property def message(self): 'the standard message which can be transfer' return { 'source': 'account', 'account_cookie': self.account_cookie, 'portfolio_cookie': self.portfolio_cookie, 'user_cookie': self.user_cookie, 'broker': self.broker, 'market_type': self.market_type, 'strategy_name': self.strategy_name, 'current_time': str(self._currenttime), 'allow_sellopen': self.allow_sellopen, 'allow_t0': self.allow_t0, 'margin_level': self.margin_level, 'init_assets': self.init_assets, 'init_cash': self.init_cash, 'init_hold': self.init_hold.to_dict(), 'commission_coeff': self.commission_coeff, 'tax_coeff': self.tax_coeff, 'cash': self.cash, 'history': self.history, 'trade_index': self.time_index, 'running_time': str(datetime.datetime.now()) if self.running_time is None else str(self.running_time), 'quantaxis_version': self.quantaxis_version, 'running_environment': self.running_environment, 'start_date': self.start_date, 'end_date': self.end_date } def load_marketpreset(self): """加载市场表 """ self.market_preset = MARKET_PRESET() @property def init_hold_with_account(self): """带account_id的初始化持仓 Returns: [type] -- [description] """ return self.init_hold.reset_index().assign( account_cookie=self.account_cookie).set_index( ['code', 'account_cookie']) @property def init_assets(self): """初始化账户资产 Returns: dict -- 2keys-cash,hold """ return {'cash': self.init_cash, 'hold': self.init_hold.to_dict()} @property def code(self): """ 该账户曾交易代码 用set 去重 """ return list(set([item[1] for item in self.history])) @property def date(self): """账户运行的日期 Arguments: self {[type]} -- [description] Returns: [type] -- [description] """ if self.datetime is not None: return str(self.datetime)[0:10] else: return None @property def poisitions(self): raise NotImplementedError @property def start_date(self): """账户的起始交易日期(只在回测中使用) Raises: RuntimeWarning -- [description] Returns: [type] -- [description] """ if len(self.time_index) > 0: return str(min(self.time_index))[0:10] else: print( RuntimeWarning( 'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE')) @property def end_date(self): """账户的交易结束日期(只在回测中使用) Raises: RuntimeWarning -- [description] Returns: [type] -- [description] """ if len(self.time_index) > 0: return str(max(self.time_index))[0:10] else: print( RuntimeWarning( 'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE')) @property def market_data(self): return self._market_data @property def trade_range(self): return QA_util_get_trade_range(self.start_date, self.end_date) @property def trade_day(self): return list( pd.Series(self.time_index).apply(lambda x: str(x)[0:10]).unique()) @property def history_table(self): '交易历史的table' if len(self.history) > 0: lens = len(self.history[0]) else: lens = len(self._history_headers) return pd.DataFrame(data=self.history, columns=self._history_headers[:lens]).sort_index() @property def today_trade_table(self): return pd.DataFrame(data=self.today_trade['current'], columns=self._history_headers).sort_index() @property def cash_table(self): '现金的table' _cash = pd.DataFrame(data=[self.cash[1::], self.time_index], index=['cash', 'datetime']).T _cash = _cash.assign(date=_cash.datetime.apply( lambda x: pd.to_datetime(str(x)[0:10]))).assign( account_cookie=self.account_cookie) # .sort_values('datetime') return _cash.set_index(['datetime', 'account_cookie'], drop=False) """ 实验性质 @2018-06-09 # 对于账户持仓的分解 1. 真实持仓hold: 正常模式/TZero模式: hold = 历史持仓(init_hold)+ 初始化账户后发生的所有交易导致的持仓(hold_available) 动态持仓(初始化账户后的持仓)hold_available: self.history 计算而得 2. 账户的可卖额度(sell_available) 正常模式: sell_available 结算前: init_hold+ 买卖交易(卖-) 结算后: init_hold+ 买卖交易(买+ 卖-) TZero模式: sell_available 结算前: init_hold - 买卖交易占用的额度(abs(买+ 卖-)) 结算过程 是为了补平(等于让hold={}) 结算后: init_hold """ @property def hold(self): """真实持仓 """ return pd.concat([self.init_hold, self.hold_available]).groupby('code').sum().replace( 0, np.nan).dropna().sort_index() @property def hold_available_temp(self): """可用持仓 """ return self._table.groupby('code').amount.sum().replace( 0, np.nan).dropna().sort_index() @property def hold_available(self): """可用持仓 """ return self.history_table.groupby('code').amount.sum().replace( 0, np.nan).dropna().sort_index() # @property # def order_table(self): # """return order trade list""" # return self.orders.trade_list @property def trade(self): """每次交易的pivot表 Returns: pd.DataFrame 此处的pivot_table一定要用np.sum """ return self.history_table.pivot_table( index=['datetime', 'account_cookie'], columns='code', values='amount', aggfunc=np.sum).fillna(0).sort_index() @property def daily_cash(self): '每日交易结算时的现金表' res = self.cash_table.drop_duplicates(subset='date', keep='last') return pd.concat([res.set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\ .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie'], drop=False).sort_index() @property def daily_hold(self): '每日交易结算时的持仓表' data = self.trade.cumsum() if len(data) < 1: return None else: # print(data.index.levels[0]) data = data.assign(account_cookie=self.account_cookie).assign( date=pd.to_datetime(data.index.levels[0]).date) data.date = pd.to_datetime(data.date) data = data.set_index(['date', 'account_cookie']) res = data[~data.index.duplicated(keep='last')].sort_index() # 这里会导致股票停牌时的持仓也被计算 但是计算market_value的时候就没了 return pd.concat([res.reset_index().set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\ .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie']).sort_index() # 计算assets的时候 需要一个market_data=QA.QA_fetch_stock_day_adv(list(data.columns),data.index[0],data.index[-1]) # (market_data.to_qfq().pivot('close')*data).sum(axis=1)+user_cookie.get_account(a_1).daily_cash.set_index('date').cash @property def latest_cash(self): 'return the lastest cash 可用资金' return self.cash[-1] @property def current_time(self): 'return current time (in backtest/real environment)' return self._currenttime def hold_table(self, datetime=None): "到某一个时刻的持仓 如果给的是日期,则返回当日开盘前的持仓" if datetime is None: hold_available = self.history_table.set_index( 'datetime').sort_index().groupby( 'code').amount.sum().sort_index() else: hold_available = self.history_table.set_index( 'datetime').sort_index().loc[:datetime].groupby( 'code').amount.sum().sort_index() return pd.concat([self.init_hold, hold_available ]).groupby('code').sum().sort_index().apply( lambda x: x if x > 0 else None).dropna() def hold_price(self, datetime=None): """计算持仓成本 如果给的是日期,则返回当日开盘前的持仓 Keyword Arguments: datetime {[type]} -- [description] (default: {None}) Returns: [type] -- [description] """ def weights(x): if sum(x['amount']) != 0: return np.average(x['price'], weights=x['amount'], returned=True) else: return np.nan if datetime is None: return self.history_table.set_index( 'datetime', drop=False).sort_index().groupby('code').apply( weights).dropna() else: return self.history_table.set_index( 'datetime', drop=False).sort_index().loc[:datetime].groupby( 'code').apply(weights).dropna() # @property def hold_time(self, datetime=None): """持仓时间 Keyword Arguments: datetime {[type]} -- [description] (default: {None}) """ def weights(x): if sum(x['amount']) != 0: return pd.Timestamp(self.datetime) - pd.to_datetime( x.datetime.max()) else: return np.nan if datetime is None: return self.history_table.set_index( 'datetime', drop=False).sort_index().groupby('code').apply( weights).dropna() else: return self.history_table.set_index( 'datetime', drop=False).sort_index().loc[:datetime].groupby( 'code').apply(weights).dropna() def reset_assets(self, init_cash=None): 'reset_history/cash/' self.sell_available = copy.deepcopy(self.init_hold) self.history = [] self.init_cash = init_cash self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 在途资金 def receive_simpledeal(self, code, trade_price, trade_amount, trade_towards, trade_time, message=None, order_id=None, trade_id=None, realorder_id=None): """快速撮合成交接口 此接口是一个直接可以成交的接口, 所以务必确保给出的信息是可以成交的 此接口涉及的是 1. 股票/期货的成交 2. 历史记录的增加 3. 现金/持仓/冻结资金的处理 Arguments: code {[type]} -- [description] trade_price {[type]} -- [description] trade_amount {[type]} -- [description] trade_towards {[type]} -- [description] trade_time {[type]} -- [description] Keyword Arguments: message {[type]} -- [description] (default: {None}) 2018/11/7 @yutiansut 修复一个bug: 在直接使用该快速撮合接口的时候, 期货卖出会扣减保证金, 买回来的时候应该反算利润 如 3800卖空 3700买回平仓 应为100利润 @2018-12-31 保证金账户ok @2019/1/3 一些重要的意思 frozen = self.market_preset.get_frozen(code) # 保证金率 unit = self.market_preset.get_unit(code) # 合约乘数 raw_trade_money = trade_price*trade_amount*market_towards # 总市值 value = raw_trade_money * unit # 合约总价值 trade_money = value * frozen # 交易保证金 """ self.datetime = trade_time market_towards = 1 if trade_towards > 0 else -1 # value 合约价值 unit 合约乘数 if self.allow_margin: frozen = self.market_preset.get_frozen(code) # 保证金率 unit = self.market_preset.get_unit(code) # 合约乘数 raw_trade_money = trade_price * trade_amount * market_towards # 总市值 value = raw_trade_money * unit # 合约总价值 trade_money = value * frozen # 交易保证金 else: trade_money = trade_price * trade_amount * market_towards raw_trade_money = trade_money value = trade_money unit = 1 frozen = 1 # 计算费用 # trade_price if self.market_type == MARKET_TYPE.FUTURE_CN: # 期货不收税 # 双边手续费 也没有最小手续费限制 commission_fee_preset = self.market_preset.get_code(code) if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN ]: commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_peramount'] * \ abs(value) elif trade_towards in [ ORDER_DIRECTION.BUY_CLOSETODAY, ORDER_DIRECTION.SELL_CLOSETODAY ]: commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_today_peramount'] * \ abs(value) tax_fee = 0 # 买入不收印花税 elif self.market_type == MARKET_TYPE.STOCK_CN: commission_fee = self.commission_coeff * \ abs(trade_money) commission_fee = 5 if commission_fee < 5 else commission_fee if int(trade_towards) > 0: tax_fee = 0 # 买入不收印花税 else: tax_fee = self.tax_coeff * abs(trade_money) # 结算交易 if self.cash[-1] > trade_money + commission_fee + tax_fee: self.time_index.append(trade_time) # TODO: 目前还不支持期货的锁仓 if self.allow_sellopen: if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN ]: # 开仓单占用现金 计算avg # 初始化 if code in self.frozen.keys(): if trade_towards in self.frozen[code].keys(): pass else: self.frozen[code][trade_towards] = { 'money': 0, 'amount': 0, 'avg_price': 0 } else: self.frozen[code] = { ORDER_DIRECTION.BUY_OPEN: { 'money': 0, 'amount': 0, 'avg_price': 0 }, ORDER_DIRECTION.SELL_OPEN: { 'money': 0, 'amount': 0, 'avg_price': 0 } } """[summary] # frozen的计算 # money 冻结的资金 # amount 冻结的数量 2018-12-31 """ self.frozen[code][trade_towards]['money'] = ( (self.frozen[code][trade_towards]['money'] * self.frozen[code][trade_towards]['amount']) + abs(trade_money)) / ( self.frozen[code][trade_towards]['amount'] + trade_amount) self.frozen[code][trade_towards]['avg_price'] = ( (self.frozen[code][trade_towards]['avg_price'] * self.frozen[code][trade_towards]['amount']) + abs(raw_trade_money)) / ( self.frozen[code][trade_towards]['amount'] + trade_amount) self.frozen[code][trade_towards]['amount'] += trade_amount self.cash.append(self.cash[-1] - abs(trade_money) - commission_fee - tax_fee) elif trade_towards in [ ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE ]: # 平仓单释放现金 # if trade_towards == ORDER_DIRECTION.BUY_CLOSE: # 卖空开仓 平仓买入 # self.cash if trade_towards == ORDER_DIRECTION.BUY_CLOSE: # 买入平仓 之前是空开 # self.frozen[code][ORDER_DIRECTION.SELL_OPEN]['money'] -= trade_money self.frozen[code][ORDER_DIRECTION. SELL_OPEN]['amount'] -= trade_amount frozen_part = self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['money'] * trade_amount # 账户的现金+ 冻结的的释放 + 买卖价差* 杠杆 self.cash.append(self.cash[-1] + frozen_part + (frozen_part - trade_money) / frozen - commission_fee - tax_fee) if self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['money'] = 0 self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['avg_price'] = 0 elif trade_towards == ORDER_DIRECTION.SELL_CLOSE: # 卖出平仓 之前是多开 # self.frozen[code][ORDER_DIRECTION.BUY_OPEN]['money'] -= trade_money self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['amount'] -= trade_amount frozen_part = self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['money'] * trade_amount self.cash.append(self.cash[-1] + frozen_part + (abs(trade_money) - frozen_part) / frozen - commission_fee - tax_fee) if self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['money'] = 0 self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['avg_price'] = 0 else: # 不允许卖空开仓的==> 股票 self.cash.append(self.cash[-1] - trade_money - tax_fee - commission_fee) if self.allow_t0 or trade_towards == ORDER_DIRECTION.SELL: self.sell_available[code] = self.sell_available.get( code, 0) + trade_amount * market_towards self.buy_available = self.sell_available self.cash_available = self.cash[-1] frozen_money = abs(trade_money) if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN ] else 0 self.history.append([ trade_time, code, trade_price, market_towards * trade_amount, self.cash[-1], order_id, realorder_id, trade_id, self.account_cookie, commission_fee, tax_fee, message, frozen_money ]) else: # print(self.cash[-1]) self.cash_available = self.cash[-1] #print('NOT ENOUGH MONEY FOR {}'.format(order_id)) def receive_deal(self, code: str, trade_id: str, order_id: str, realorder_id: str, trade_price: float, trade_amount: int, trade_towards: int, trade_time: str, message=None): """更新deal Arguments: code {str} -- [description] trade_id {str} -- [description] order_id {str} -- [description] realorder_id {str} -- [description] trade_price {float} -- [description] trade_amount {int} -- [description] trade_towards {int} -- [description] trade_time {str} -- [description] Returns: [type] -- [description] """ print('receive deal') trade_time = str(trade_time) code = str(code) trade_price = float(trade_price) trade_towards = int(trade_towards) realorder_id = str(realorder_id) trade_id = str(trade_id) trade_amount = int(trade_amount) order_id = str(order_id) market_towards = 1 if trade_towards > 0 else -1 """2019/01/03 直接使用快速撮合接口了 2333 这两个接口现在也没啥区别了.... 太绝望了 """ self.receive_simpledeal(code, trade_price, trade_amount, trade_towards, trade_time, message=message, order_id=order_id, trade_id=trade_id, realorder_id=realorder_id) def send_order(self, code=None, amount=None, time=None, towards=None, price=None, money=None, order_model=None, amount_model=None, *args, **kwargs): """ ATTENTION CHANGELOG 1.0.28 修改了Account的send_order方法, 区分按数量下单和按金额下单两种方式 - AMOUNT_MODEL.BY_PRICE ==> AMOUNT_MODEL.BY_MONEY # 按金额下单 - AMOUNT_MODEL.BY_AMOUNT # 按数量下单 在按金额下单的时候,应给予 money参数 在按数量下单的时候,应给予 amount参数 python code: Account=QA.QA_Account() Order_bymoney=Account.send_order(code='000001', price=11, money=0.3*Account.cash_available, time='2018-05-09', towards=QA.ORDER_DIRECTION.BUY, order_model=QA.ORDER_MODEL.MARKET, amount_model=QA.AMOUNT_MODEL.BY_MONEY ) Order_byamount=Account.send_order(code='000001', price=11, amount=100, time='2018-05-09', towards=QA.ORDER_DIRECTION.BUY, order_model=QA.ORDER_MODEL.MARKET, amount_model=QA.AMOUNT_MODEL.BY_AMOUNT ) :param code: 证券代码 :param amount: 买卖 数量多数股 :param time: Timestamp 对象 下单时间 :param towards: int , towards>0 买入 towards<0 卖出 :param price: 买入,卖出 标的证券的价格 :param money: 买卖 价格 :param order_model: 类型 QA.ORDER_MODE :param amount_model:类型 QA.AMOUNT_MODEL :return: QA_Order | False @2018/12/23 send_order 是QA的标准返回, 如需对接其他接口, 只需要对于QA_Order做适配即可 @2018/12/27 在判断账户为期货账户(及 允许双向交易) @2018/12/30 保证金账户的修改 1. 保证金账户冻结的金额 2. 保证金账户的结算 3. 保证金账户的判断 """ wrong_reason = None assert code is not None and time is not None and towards is not None and order_model is not None and amount_model is not None # 🛠todo 移到Utils类中, 时间转换 # date 字符串 2011-10-11 长度10 date = str(time)[0:10] if len(str(time)) == 19 else str(time) # time 字符串 20011-10-11 09:02:00 长度 19 time = str(time) if len(str(time)) == 19 else '{} 09:31:00'.format( str(time)[0:10]) # 🛠todo 移到Utils类中, amount_to_money 成交量转金额 # BY_MONEY :: amount --钱 如10000元 因此 by_money里面 需要指定价格,来计算实际的股票数 # by_amount :: amount --股数 如10000股 if self.allow_margin: amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (self.market_preset.get_unit(code) * self.market_preset.get_frozen(code) * price * (1 + self.commission_coeff)) / 100) * 100 else: amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (price * (1 + self.commission_coeff)) / 100) * 100 # 🛠todo 移到Utils类中, money_to_amount 金额转成交量 if self.allow_margin: money = amount * price * self.market_preset.get_unit(code)*self.market_preset.get_frozen(code) * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money else: money = amount * price * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money # flag 判断买卖 数量和价格以及买卖方向是否正确 flag = False assert (int(towards) != 0) if int(towards) in [1, 2, 3]: # 是买入的情况(包括买入.买开.买平) if self.cash_available >= money: if self.market_type is MARKET_TYPE.STOCK_CN: # 如果是股票 买入的时候有100股的最小限制 amount = int(amount / 100) * 100 self.cash_available -= money flag = True if self.running_environment == RUNNING_ENVIRONMENT.TZERO: if self.buy_available.get(code, 0) >= amount: flag = True self.cash_available -= money self.buy_available[code] -= amount else: flag = False wrong_reason = 'T0交易买入超出限额' if self.market_type == MARKET_TYPE.FUTURE_CN: # 如果有负持仓-- 允许卖空的时候 if towards == 3: # 多平 _hold = self.sell_available.get(code, 0) # 假设有负持仓: # amount为下单数量 如 账户原先-3手 现在平1手 #left_amount = amount+_hold if _hold < 0 else amount _money = abs( float(amount * price * (1 + self.commission_coeff))) print(_hold) if self.cash_available >= _money: if _hold < 0: self.cash_available -= _money flag = True else: wrong_reason = '空单仓位不足' else: wrong_reason = '平多剩余资金不够' if towards == 2: self.cash_available -= money flag = True else: wrong_reason = 'QAACCOUNT: 可用资金不足 cash_available {} code {} time {} amount {} towards {}'.format( self.cash_available, code, time, amount, towards) elif int(towards) in [-1, -2, -3]: # 是卖出的情况(包括卖出,卖出开仓allow_sellopen如果允许. 卖出平仓) # print(self.sell_available[code]) _hold = self.sell_available.get(code, 0) # _hold 是你的持仓 # 如果你的hold> amount>0 # 持仓数量>卖出数量 if _hold >= amount: self.sell_available[code] -= amount # towards = ORDER_DIRECTION.SELL flag = True # 如果持仓数量<卖出数量 else: # 如果是允许卖空开仓 实际计算时 先减去持仓(正持仓) 再计算 负持仓 就按原先的占用金额计算 if self.allow_sellopen and towards == -2: if self.cash_available >= money: # 卖空的市值小于现金(有担保的卖空), 不允许裸卖空 # self.cash_available -= money flag = True else: print('sellavailable', _hold) print('amount', amount) print('aqureMoney', money) print('cash', self.cash_available) wrong_reason = "卖空资金不足/不允许裸卖空" else: wrong_reason = "卖出仓位不足" if flag and (amount > 0): _order = QA_Order(user_cookie=self.user_cookie, strategy=self.strategy_name, frequence=self.frequence, account_cookie=self.account_cookie, code=code, market_type=self.market_type, date=date, datetime=time, sending_time=time, callback=self.receive_deal, amount=amount, price=price, order_model=order_model, towards=towards, money=money, amount_model=amount_model, commission_coeff=self.commission_coeff, tax_coeff=self.tax_coeff, *args, **kwargs) # init # 历史委托order状态存储, 保存到 QA_Order 对象中的队列中 self.datetime = time self.orders.insert_order(_order) return _order else: print('ERROR : CODE {} TIME {} AMOUNT {} TOWARDS {}'.format( code, time, amount, towards)) print(wrong_reason) return False def cancel_order(self, order): if order.towards in [ ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE ]: if order.amount_model is AMOUNT_MODEL.BY_MONEY: self.cash_available += order.money elif order.amount_model is AMOUNT_MODEL.BY_AMOUNT: self.cash_available += order.price * order.amount elif order.towards in [ ORDER_DIRECTION.SELL, ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN ]: self.sell_available[order.code] += order.amount # self.sell_available[] @property def close_positions_order(self): """平仓单 Raises: RuntimeError -- if ACCOUNT.RUNNING_ENVIRONMENT is NOT TZERO Returns: list -- list with order """ order_list = [] time = '{} 15:00:00'.format(self.date) if self.running_environment == RUNNING_ENVIRONMENT.TZERO: for code, amount in self.hold_available.iteritems(): order = False if amount < 0: # 先卖出的单子 买平 order = self.send_order( code=code, price=0, amount=abs(amount), time=time, towards=ORDER_DIRECTION.BUY_CLOSE, order_model=ORDER_MODEL.CLOSE, amount_model=AMOUNT_MODEL.BY_AMOUNT) elif amount > 0: # 先买入的单子, 卖平 order = self.send_order( code=code, price=0, amount=abs(amount), time=time, towards=ORDER_DIRECTION.SELL_CLOSE, order_model=ORDER_MODEL.CLOSE, amount_model=AMOUNT_MODEL.BY_AMOUNT) if order: order_list.append(order) return order_list else: raise RuntimeError( 'QAACCOUNT with {} environments cannot use this methods'. format(self.running_environment)) def settle(self): """ 股票/期货的日结算 股票的结算: 结转股票可卖额度 T0的结算: 结转T0的额度 期货的结算: 结转静态资金 """ if self.running_environment == RUNNING_ENVIRONMENT.TZERO and self.hold_available.sum( ) != 0: raise RuntimeError('QAACCOUNT: 该T0账户未当日仓位,请平仓 {}'.format( self.hold_available.to_dict())) if self.market_type == MARKET_TYPE.FUTURE_CN: self.static_balance['frozen'].append( sum([ rx['money'] * rx['amount'] for var in self.frozen.values() for rx in var.values() ])) self.static_balance['cash'].append(self.cash[-1]) self.static_balance['hold'].append(self.hold.to_dict()) self.static_balance['date'].append(self.date) """静态权益的结算 只关心开仓价/ 不做盯市制度 动态权益的结算需要关心 """ self.static_balance['static_assets'].append( self.static_balance['cash'][-1] + self.static_balance['frozen'][-1]) self.sell_available = self.hold self.buy_available = self.hold self.datetime = '{} 09:30:00'.format(QA_util_get_next_day( self.date)) if self.date is not None else None def on_bar(self, event): ''' 策略事件 :param event: :return: ''' 'while updating the market data' print("on_bar account {} ".format(self.account_cookie), event.market_data) def on_tick(self, event): ''' 策略事件 :param event: :return: ''' 'on tick event' print("on_tick ", event.market_data) pass def from_message(self, message): """resume the account from standard message 这个是从数据库恢复账户时需要的""" self.account_cookie = message.get('account_cookie', None) self.portfolio_cookie = message.get('portfolio_cookie', None) self.user_cookie = message.get('user_cookie', None) self.broker = message.get('broker', None) self.market_type = message.get('market_type', None) self.strategy_name = message.get('strategy_name', None) self._currenttime = message.get('current_time', None) self.allow_sellopen = message.get('allow_sellopen', False) self.allow_t0 = message.get('allow_t0', False) self.margin_level = message.get('margin_level', False) self.init_cash = message.get('init_cash', message.get('init_assets', 1000000)) # 兼容修改 self.init_hold = pd.Series(message.get('init_hold', {}), name='amount') self.init_hold.index.name = 'code' self.commission_coeff = message.get('commission_coeff', 0.00015) self.tax_coeff = message.get('tax_coeff', 0.0015) self.history = message['history'] self.cash = message['cash'] self.time_index = message['trade_index'] self.running_time = message.get('running_time', None) self.quantaxis_version = message.get('quantaxis_version', None) self.running_environment = message.get('running_environment', RUNNING_ENVIRONMENT.BACKETEST) self.settle() return self @property def table(self): """ 打印出account的内容 """ return pd.DataFrame([ self.message, ]).set_index('account_cookie', drop=False).T def run(self, event): ''' 这个方法是被 QA_ThreadEngine 处理队列时候调用的, QA_Task 中 do 方法调用 run (在其它线程中) 'QA_WORKER method 重载' :param event: 事件类型 QA_Event :return: ''' 'QA_WORKER method' if event.event_type is ACCOUNT_EVENT.SETTLE: self.settle() # elif event.event_type is ACCOUNT_EVENT.UPDATE: # self.receive_deal(event.message) elif event.event_type is ACCOUNT_EVENT.MAKE_ORDER: """generate order if callback callback the order if not return back the order """ data = self.send_order(code=event.code, amount=event.amount, time=event.time, amount_model=event.amount_model, towards=event.towards, price=event.price, order_model=event.order_model) if event.callback: event.callback(data) else: return data elif event.event_type is ENGINE_EVENT.UPCOMING_DATA: """update the market_data 1. update the inside market_data struct 2. tell the on_bar methods # 这样有点慢 """ self._currenttime = event.market_data.datetime[0] if self._market_data is None: self._market_data = event.market_data else: self._market_data = self._market_data + event.market_data self.on_bar(event) if event.callback: event.callback(event) def save(self): """ 存储账户信息 """ save_account(self.message) def sync_account(self, sync_message): """同步账户 Arguments: sync_message {[type]} -- [description] """ self.init_hold = sync_message['hold_available'] self.init_cash = sync_message['cash_available'] self.sell_available = copy.deepcopy(self.init_hold) self.history = [] self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 在途资金 def change_cash(self, money): """ 外部操作|高危| """ res = self.cash[-1] + money if res >= 0: # 高危操作 self.cash[-1] = res def get_orders(self, if_today=True): ''' 返回当日委托/历史委托 :param if_today: true 只返回今天的订单 :return: QA_OrderQueue ''' # 🛠todo 筛选其它不是今天的订单返回 return self.orders def get_history(self, start, end): """返回历史成交 Arguments: start {str} -- [description] end {str]} -- [description] """ return self.history_table.set_index('datetime', drop=False).loc[slice( pd.Timestamp(start), pd.Timestamp(end))]
class QA_Performance(): """ QA_Performance是一个绩效分析插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold QAPERFORMANCE 的评估字段 1. 对于多头开仓/ 空头开仓的分析 2. 总盈利(对于每个单笔而言) 3. 总亏损(对于每个单笔而言) 4. 总盈利/总亏损 5. 交易手数 6. 盈利比例 7. 盈利手数 8. 亏损手数 9. 持平手数 10. 平均利润 11. 平均盈利 12. 平均亏损 13. 平均盈利/平均亏损 14. 最大盈利(单笔) 15. 最大亏损(单笔) 16. 最大盈利/总盈利 17. 最大亏损/总亏损 18. 净利润/最大亏损 19. 最大连续盈利手数 20. 最大连续亏损手数 21. 平均持仓周期 22. 平均盈利周期 23. 平均亏损周期 24. 平均持平周期 25. 最大使用资金 26. 最大持仓手数 27. 交易成本合计 28. 收益率 29. 年化收益率 30. 有效收益率 31. 月度平均盈利 32. 收益曲线斜率 33. 收益曲线截距 34. 收益曲线R2值 35. 夏普比例 36. 总交易时间 37. 总持仓时间 38. 持仓时间比例 39. 最大空仓时间 40. 持仓周期 41. 资产最大升水 42. 发生时间 43. 最大升水/前期低点 44. 单日最大资产回撤比率 45. 最大资产回撤值 46. 最大资产回撤发生时间 47. 回撤值/前期高点 48. 净利润/回撤值 """ def __init__(self, target): self.target = target self._style_title = [ 'beta', 'momentum', 'size', 'earning_yield', 'volatility', 'growth', 'value', 'leverage', 'liquidity', 'reversal' ] self.market_preset = MARKET_PRESET() self.pnl = self.pnl_fifo def __repr__(self): return '< QA_PERFORMANCE ANYLYSIS PLUGIN >' def set_pnl(self, model='fifo'): if model == 'fifo': self.pnl = self.pnl_fifo elif model == 'lifo': self.pnl = self.pnl_lifo def base_message(self, pnl): return {'total_profit': round(self.total_profit(pnl), 2), # 总盈利(对于每个单笔而言) 'total_loss': round(self.total_loss(pnl), 2), # 总亏损(对于每个单笔而言) 'total_pnl': round(self.total_pnl(pnl), 2), # 总盈利/总亏损 'trading_amounts': round(self.trading_amounts(pnl), 2), # 交易手数 'profit_amounts': round(self.profit_amounts(pnl), 2), # 盈利手数 'loss_amounts': round(self.loss_amounts(pnl), 2), # 亏损手数 'even_amounts': round(self.even_amounts(pnl), 2), # 持平手数 'profit_precentage': round(self.profit_precentage(pnl), 2), 'loss_precentage': round(self.loss_precentage(pnl), 2), 'even_precentage': round(self.even_precentage(pnl), 2), 'average_profit': round(self.average_profit(pnl), 2), 'average_loss': round(self.average_loss(pnl), 2), 'average_pnl': round(self.average_pnl(pnl), 2), 'max_profit': round(self.max_profit(pnl), 2), 'max_loss': round(self.max_loss(pnl), 2), 'max_pnl': round(self.max_pnl(pnl), 2), 'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(pnl), 2), 'continue_profit_amount': round(self.continue_profit_amount(pnl), 2), 'continue_loss_amount': round(self.continue_loss_amount(pnl), 2), 'average_holdgap': self.average_holdgap(pnl), 'average_profitholdgap': self.average_profitholdgap(pnl), 'average_losssholdgap': self.average_losssholdgap(pnl)} @property def message(self): """[summary] 2. 3. 4. 5. 6. 7. 盈利手数 8. 亏损手数 9. 持平手数 10. 平均利润 11. 平均盈利 12. 平均亏损 13. 平均盈利/平均亏损 14. 最大盈利(单笔) 15. 最大亏损(单笔) 16. 最大盈利/总盈利 17. 最大亏损/总亏损 18. 净利润/最大亏损 19. 最大连续盈利手数 20. 最大连续亏损手数 21. 平均持仓周期 22. 平均盈利周期 23. 平均亏损周期 24. 平均持平周期 25. 最大使用资金 26. 最大持仓手数 27. 交易成本合计 28. 收益率 29. 年化收益率 30. 有效收益率 31. 月度平均盈利 32. 收益曲线斜率 33. 收益曲线截距 34. 收益曲线R2值 35. 夏普比例 36. 总交易时间 37. 总持仓时间 38. 持仓时间比例 39. 最大空仓时间 40. 持仓周期 41. 资产最大升水 42. 发生时间 43. 最大升水/前期低点 44. 单日最大资产回撤比率 45. 最大资产回撤值 46. 最大资产回撤发生时间 47. 回撤值/前期高点 48. 净利润/回撤值 Returns: [type] -- [description] """ return { # 总盈利(对于每个单笔而言) 'total_profit': round(self.total_profit(self.pnl), 2), 'total_loss': round(self.total_loss(self.pnl), 2), # 总亏损(对于每个单笔而言) 'total_pnl': round(self.total_pnl(self.pnl), 2), # 总盈利/总亏损 # 交易手数 'trading_amounts': round(self.trading_amounts(self.pnl), 2), 'profit_amounts': round(self.profit_amounts(self.pnl), 2), # 盈利手数 'loss_amounts': round(self.loss_amounts(self.pnl), 2), # 亏损手数 'even_amounts': round(self.even_amounts(self.pnl), 2), # 持平手数 'profit_precentage': round(self.profit_precentage(self.pnl), 2), 'loss_precentage': round(self.loss_precentage(self.pnl), 2), 'even_precentage': round(self.even_precentage(self.pnl), 2), 'average_profit': round(self.average_profit(self.pnl), 2), 'average_loss': round(self.average_loss(self.pnl), 2), 'average_pnl': round(self.average_pnl(self.pnl), 2), 'max_profit': round(self.max_profit(self.pnl), 2), 'max_loss': round(self.max_loss(self.pnl), 2), 'max_pnl': round(self.max_pnl(self.pnl), 2), 'netprofio_maxloss_ratio': round(self.netprofio_maxloss_ratio(self.pnl), 2), 'continue_profit_amount': round(self.continue_profit_amount(self.pnl), 2), 'continue_loss_amount': round(self.continue_loss_amount(self.pnl), 2), 'average_holdgap': self.average_holdgap(self.pnl), 'average_profitholdgap': self.average_profitholdgap(self.pnl), 'average_losssholdgap': self.average_losssholdgap(self.pnl), 'buyopen': self.base_message(self.pnl_buyopen), 'sellopen': self.base_message(self.pnl_sellopen) } @property def prefer(self): pass @property def style(self): """风格分析 """ pass @property def pnl_lifo(self): """ 使用后进先出法配对成交记录 """ X = dict( zip( self.target.code, [LifoQueue() for i in range(len(self.target.code))] ) ) pair_table = [] for _, data in self.target.history_table_min.iterrows(): while True: if X[data.code].qsize() == 0: X[data.code].put((data.datetime, data.amount, data.price)) break else: l = X[data.code].get() if (l[1] * data.amount) < 0: # 原有多仓/ 平仓 或者原有空仓/平仓 if abs(l[1]) > abs(data.amount): temp = (l[0], l[1] + data.amount, l[2]) X[data.code].put_nowait(temp) if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2] ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price ] ) break elif abs(l[1]) < abs(data.amount): data.amount = data.amount + l[1] if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], l[1], data.price, l[2] ] ) else: pair_table.append( [ data.code, l[0], data.datetime, l[1], l[2], data.price ] ) else: if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2] ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price ] ) break else: X[data.code].put_nowait(l) X[data.code].put_nowait( (data.datetime, data.amount, data.price) ) break pair_title = [ 'code', 'sell_date', 'buy_date', 'amount', 'sell_price', 'buy_price' ] pnl = pd.DataFrame(pair_table, columns=pair_title).set_index('code') pnl = pnl.assign( unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)), pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1, sell_date=pd.to_datetime(pnl.sell_date), buy_date=pd.to_datetime(pnl.buy_date) ) pnl = pnl.assign( pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit, hold_gap=abs(pnl.sell_date - pnl.buy_date), if_buyopen=(pnl.sell_date - pnl.buy_date) > datetime.timedelta(days=0) ) pnl = pnl.assign( openprice=pnl.if_buyopen.apply( lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price, opendate=pnl.if_buyopen.apply( lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str), closeprice=pnl.if_buyopen.apply( lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price, closedate=pnl.if_buyopen.apply( lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str)) return pnl.set_index('code') @property def pnl_buyopen(self): return self.pnl[self.pnl.if_buyopen] @property def pnl_sellopen(self): return self.pnl[~self.pnl.if_buyopen] @property def pnl_fifo(self): X = dict( zip( self.target.code, [deque() for i in range(len(self.target.code))] ) ) pair_table = [] for _, data in self.target.history_table_min.iterrows(): while True: if len(X[data.code]) == 0: X[data.code].append( (data.datetime, data.amount, data.price) ) break else: l = X[data.code].popleft() if (l[1] * data.amount) < 0: # 原有多仓/ 平仓 或者原有空仓/平仓 if abs(l[1]) > abs(data.amount): temp = (l[0], l[1] + data.amount, l[2]) X[data.code].appendleft(temp) if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2] ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price ] ) break elif abs(l[1]) < abs(data.amount): data.amount = data.amount + l[1] if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], l[1], data.price, l[2] ] ) else: pair_table.append( [ data.code, l[0], data.datetime, l[1], l[2], data.price ] ) else: if data.amount < 0: pair_table.append( [ data.code, data.datetime, l[0], abs(data.amount), data.price, l[2] ] ) break else: pair_table.append( [ data.code, l[0], data.datetime, abs(data.amount), l[2], data.price ] ) break else: X[data.code].appendleft(l) X[data.code].appendleft( (data.datetime, data.amount, data.price) ) break pair_title = [ 'code', 'sell_date', 'buy_date', 'amount', 'sell_price', 'buy_price' ] pnl = pd.DataFrame(pair_table, columns=pair_title) pnl = pnl.assign( unit=pnl.code.apply(lambda x: self.market_preset.get_unit(x)), pnl_ratio=(pnl.sell_price / pnl.buy_price) - 1, sell_date=pd.to_datetime(pnl.sell_date), buy_date=pd.to_datetime(pnl.buy_date) ) pnl = pnl.assign( pnl_money=(pnl.sell_price - pnl.buy_price) * pnl.amount * pnl.unit, hold_gap=abs(pnl.sell_date - pnl.buy_date), if_buyopen=(pnl.sell_date - pnl.buy_date) > datetime.timedelta(days=0) ) pnl = pnl.assign( openprice=pnl.if_buyopen.apply( lambda pnl: 1 if pnl else 0) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_price, opendate=pnl.if_buyopen.apply( lambda pnl: 1 if pnl else 0) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 0 if pnl else 1) * pnl.sell_date.map(str), closeprice=pnl.if_buyopen.apply( lambda pnl: 0 if pnl else 1) * pnl.buy_price + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_price, closedate=pnl.if_buyopen.apply( lambda pnl: 0 if pnl else 1) * pnl.buy_date.map(str) + pnl.if_buyopen.apply(lambda pnl: 1 if pnl else 0) * pnl.sell_date.map(str)) return pnl.set_index('code') def plot_pnlratio(self): """ 画出pnl比率散点图 """ plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_ratio) plt.gcf().autofmt_xdate() return plt def plot_pnlmoney(self): """ 画出pnl盈亏额散点图 """ plt.scatter(x=self.pnl.sell_date.apply(str), y=self.pnl.pnl_money) plt.gcf().autofmt_xdate() return plt def abnormal_active(self): """ 账户的成交发生异常成交记录的分析 """ pass def brinson(self): """Brinson Model analysis """ pass def hold(self): """持仓分析 """ pass def win_rate(self): """胜率 胜率 盈利次数/总次数 """ data = self.pnl try: return round(len(data.query('pnl_money>0')) / len(data), 2) except ZeroDivisionError: return 0 @property def accumulate_return(self): """ returns a pd-Dataframe format accumulate return for different periods """ pass def save(self): """save the performance analysis result to database """ pass def profit_pnl(self, pnl): return pnl.query('pnl_money>0') def loss_pnl(self, pnl): return pnl.query('pnl_money<0') def even_pnl(self, pnl): return pnl.query('pnl_money==0') def total_profit(self, pnl): if len(self.profit_pnl(pnl))>0: return self.profit_pnl(pnl).pnl_money.sum() else: return 0 def total_loss(self, pnl): if len(self.loss_pnl(pnl))>0: return self.loss_pnl(pnl).pnl_money.sum() else: return 0 def total_pnl(self, pnl): try: return abs(self.total_profit(pnl) / self.total_loss(pnl)) except ZeroDivisionError: return 0 def trading_amounts(self, pnl): return len(pnl) def profit_amounts(self, pnl): return len(self.profit_pnl(pnl)) def loss_amounts(self, pnl): return len(self.loss_pnl(pnl)) def even_amounts(self, pnl): return len(self.even_pnl(pnl)) def profit_precentage(self, pnl): try: return self.profit_amounts(pnl) / self.trading_amounts(pnl) except ZeroDivisionError: return 0 def loss_precentage(self, pnl): try: return self.loss_amounts(pnl) / self.trading_amounts(pnl) except ZeroDivisionError: return 0 def even_precentage(self, pnl): try: return self.even_amounts(pnl) / self.trading_amounts(pnl) except ZeroDivisionError: return 0 def average_loss(self, pnl): if len(self.loss_pnl(pnl))>0: return self.loss_pnl(pnl).pnl_money.mean() else: return 0 def average_profit(self, pnl): if len(self.profit_pnl(pnl))>0: return self.profit_pnl(pnl).pnl_money.mean() else: return 0 def average_pnl(self, pnl): if len(self.loss_pnl(pnl))>0 and len(self.profit_pnl(pnl))>0: try: return abs(self.average_profit(pnl) / self.average_loss(pnl)) except ZeroDivisionError: return 0 else: return 0 def max_profit(self, pnl): if len(self.profit_pnl(pnl))>0: return self.profit_pnl(pnl).pnl_money.max() else: return 0 def max_loss(self, pnl): if len(self.loss_pnl(pnl))>0: return self.loss_pnl(pnl).pnl_money.min() else: return 0 def max_pnl(self, pnl): try: return abs(self.max_profit(pnl) / self.max_loss(pnl)) except ZeroDivisionError: return 0 def netprofio_maxloss_ratio(self, pnl): if len(self.loss_pnl(pnl))>0: try: return abs(pnl.pnl_money.sum() / self.max_loss(pnl)) except ZeroDivisionError: return 0 else: return 0 def continue_profit_amount(self, pnl): w = [] w1 = 0 for _, item in pnl.pnl_money.iteritems(): if item > 0: w1 += 1 elif item < 0: w.append(w1) w1 = 0 if len(w) == 0: return 0 else: return max(w) def continue_loss_amount(self, pnl): l = [] l1 = 0 for _, item in pnl.pnl_money.iteritems(): if item > 0: l1 += 1 elif item < 0: l.append(l1) l1 = 0 if len(l) == 0: return 0 else: return max(l) def average_holdgap(self, pnl): if len(pnl.hold_gap)>0: return str(pnl.hold_gap.mean()) else: return 'no trade' def average_profitholdgap(self, pnl): if len(self.profit_pnl(pnl).hold_gap)>0: return str(self.profit_pnl(pnl).hold_gap.mean()) else: return 'no trade' def average_losssholdgap(self, pnl): if len(self.loss_pnl(pnl).hold_gap)>0: return str(self.loss_pnl(pnl).hold_gap.mean()) else: return 'no trade' def average_evenholdgap(self, pnl): if len(self.even_pnl(pnl).hold_gap)>0: return self.even_pnl(pnl).hold_gap.mean() else: return 'no trade' @property def max_cashused(self): return self.target.init_cash - min(self.target.cash) @property def total_taxfee(self): return self.target.history_table_min.commission.sum( ) + self.target.history_table_min.tax.sum()