def calc_commission( self, trade_price, trade_amount, trade_towards, ): if self.market_type == MARKET_TYPE.FUTURE_CN: # 期货不收税 # 双边手续费 也没有最小手续费限制 value = trade_price * trade_amount * \ MARKET_PRESET().get_unit(self.code) commission_fee_preset = MARKET_PRESET().get_code(self.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 __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 __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
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='******', user_cookie = '', 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.user_cookie = user_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 + self.volume_long_frozen @property def volume_short(self): return self.volume_short_his + self.volume_short_today+ self.volume_short_frozen @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, 'user_cookie': self.user_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_price_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 self.moneypresetLeft -= marginValue 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) self.margin_long += marginValue profit = (price - self.position_price_long) * amount self.moneypresetLeft += (-marginValue + profit) else: return 0,0 elif towards == ORDER_DIRECTION.BUY_OPEN: # 增加保证金 marginValue = temp_cost * \ self.market_preset.get('buy_frozen_coeff',1) 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']) self.margin_short += marginValue 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.get('buy_frozen_coeff',1)) self.margin_long += marginValue 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',1) *\ self.market_preset['sell_frozen_coeff']) profit = (self.position_price_short - price ) * amount * self.market_preset.get('unit_table') self.margin_short += marginValue self.moneypresetLeft += (-marginValue + profit) elif towards == ORDER_DIRECTION.SELL_CLOSE: # 有昨仓先平昨仓 print(self.curpos) 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',1) *\ self.market_preset.get('buy_frozen_coeff',1)) profit = (price - self.position_price_long) * \ amount * self.market_preset.get('unit_table',1) self.margin_long += marginValue 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, 'user_cookie': self.user_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 * \ MARKET_PRESET().get_unit(self.code) commission_fee_preset = MARKET_PRESET().get_code(self.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'], user_cookie=message['user_cookie'], 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']) if self.volume_long+ self.volume_short >0: self.last_price =( self.open_price_long*self.volume_long + self.open_price_short*self.volume_short)/(self.volume_long+ self.volume_short) else: self.last_price = 0 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_price_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))]
def load_marketpreset(self): """加载市场表 """ self.market_preset = MARKET_PRESET()
def __init__(self, username, password, model="SIM", broker_name="QAPaperTrading", trade_host=mongo_ip, init_cash=1000000, taskid=str(uuid.uuid4()), nodatabase=False, trade_datetime=str(datetime.datetime.now())): """Initial QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类 1. 兼容多持仓组合 2. 动态计算权益 使用 model = SIM/ REAL来切换 qifiaccount 不去区分你的持仓是股票还是期货, 因此你可以实现跨市场的交易持仓管理 nodatabase 离线模式 """ self.user_id = username self.username = username self.password = password self.source_id = "QIFI_Account" # 识别号 self.market_preset = MARKET_PRESET() # 指的是 Account所属的账户编组(实时的时候的账户观察组) self.portfolio = "QAPaperTrade" self.model = model self.broker_name = broker_name # 所属期货公司/ 模拟的组 self.investor_name = "" # 账户所属人(实盘的开户人姓名) self.bank_password = "" self.capital_password = "" self.wsuri = "" self.bank_id = "QASIM" self.bankname = "QASIMBank" self.trade_host = trade_host if model == 'BACKTEST': self.db = pymongo.MongoClient(trade_host).karma_backtest elif model == 'MANUAL': self.db = pymongo.MongoClient(trade_host).karma_manual self.db.history.create_index([("account_cookie", pymongo.DESCENDING), ("trading_day", pymongo.DESCENDING)], unique=True, name="account_cookie_trading_day") else: self.db = pymongo.MongoClient(trade_host).karma_realtime self.pub_host = "" self.trade_host = "" self.last_updatetime = trade_datetime self.status = 200 self._trading_day = "" self.init_cash = init_cash self.pre_balance = 0 self.datetime = trade_datetime self.static_balance = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.close_profit = 0 self.premium = 0 # 本交易日内交纳的期权权利金 self.event_id = 0 self.taskid = taskid self.money = 0 # QIFI 协议 self.transfers = {} self.schedule = {} self.banks = {} self.frozen = {} self.event = {} self.positions = {} self.trades = {} self.orders = {} self.market_preset = MARKET_PRESET() self.nodatabase = nodatabase
class QA_Order(): ''' 记录order ''' def __init__( self, price=None, date=None, datetime=None, sending_time=None, trade_time=False, amount=0, market_type=None, frequence=None, towards=None, code=None, user_cookie=None, account_cookie=None, strategy=None, order_model=None, money=None, amount_model=AMOUNT_MODEL.BY_AMOUNT, broker=None, order_id=None, trade_id=False, _status=ORDER_STATUS.NEW, callback=False, commission_coeff=0.00025, tax_coeff=0.001, exchange_id=None, position_id=None, *args, **kwargs ): ''' QA_Order 对象表示一个委托业务, 有如下字段 - price 委托价格 (限价单用) - date 委托日期 (一般日线级别回测用) - datetime 当前时间 (分钟线级别和实时用) - sending_time 委托时间 (分钟线级别和实时用) - trade_time 成交时间 [list] (分钟/日线/实盘时用, 一笔订单多次成交会不断append进去) - amount 委托数量 - frequence 频率 (回测用 DAY/1min/5min/15min/30min/...) - towards 买卖方向 - code 订单的品种 - user_cookie 订单发起者 - account_cookie 订单发起账户的标识 - stratgy 策略号 - order_model 委托方式(限价/市价/下一个bar/) type str eg 'limit' - money 订单金额 - amount_model 委托量模式(按量委托/按总成交额委托) type str 'by_amount' - order_id 委托单id - trade_id 成交单id - _status 内部维护的订单状态 - callback 当订单状态改变的时候 主动回调的函数(可以理解为自动执行的OnOrderAction) - commission_coeff 手续费系数 - tax_coeff 印花税系数(股票) - exchange_id 交易所id (一般用于实盘期货) :param args: type tuple :param kwargs: type dict # 2018-08-12 把order变成一个状态机> # 以前的order只是一个信息承载的工具,现在需要让他具备状态的方法 NEW = 100 SUCCESS_ALL = 200 SUCCESS_PART = 203 # success_part 是部分成交 一个中间状态 剩余的订单还在委托队列中 QUEUED = 300 # queued 用于表示在order_queue中 实际表达的意思是订单存活 待成交 CANCEL = 400 CANCEL_PART = 402 # cancel_part是部分撤单(及 下单后成交了一部分 剩余的被撤单 这是一个最终状态) SETTLED = 500 FAILED = 600 ''' self.price = price self.datetime = None # 🛠todo 移动到 Util 类中 时间处理函数 if datetime is None and date is not None: self.date = date self.datetime = '{} 09:31:00'.format(self.date) elif date is None and datetime is not None: self.date = datetime[0:10] self.datetime = datetime elif date is not None and datetime is not None: self.date = date self.datetime = datetime else: pass self.sending_time = self.datetime if sending_time is None else sending_time # 下单时间 self.trade_time = trade_time if trade_time else [] # 成交时间 self.amount = amount # 委托数量 self.trade_amount = 0 # 成交数量 self.cancel_amount = 0 # 撤销数量 self.towards = towards # side self.code = code # 委托证券代码 self.user_cookie = user_cookie # 委托用户 self.market_type = market_type # 委托市场类别 self.frequence = frequence # 委托所在的频率(回测用) self.account_cookie = account_cookie self.strategy = strategy self.type = market_type # see below self.order_model = order_model self.amount_model = amount_model self.order_id = QA_util_random_with_topic( topic='Order' ) if order_id is None else order_id self.realorder_id = self.order_id self.commission_coeff = commission_coeff self.tax_coeff = tax_coeff self.trade_id = trade_id if trade_id else [] self.market_preset = MARKET_PRESET().get_code(self.code) self.trade_price = 0 # 成交均价 self.broker = broker self.callback = callback # 委托成功的callback self.money = money # 委托需要的金钱 self.reason = None # 原因列表 self.exchange_id = exchange_id self.time_condition = 'GFD' # 当日有效 self._status = _status self.position_id = position_id # 增加订单对于多账户以及多级别账户的支持 2018/11/12 self.mainacc_id = None if 'mainacc_id' not in kwargs.keys( ) else kwargs['mainacc_id'] self.subacc_id = None if 'subacc_id' not in kwargs.keys( ) else kwargs['subacc_id'] self.direction = 'BUY' if self.towards in [ ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE ] else 'SELL' self.offset = 'OPEN' if self.towards in [ ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN ] else 'CLOSE' @property def pending_amount(self): return self.amount - self.cancel_amount - self.trade_amount @property def __dict__(self): return { 'price': self.price, 'datetime': self.datetime, 'date': self.date, 'sending_time': self.sending_time, 'trade_time': self.trade_time, 'amount': self.amount, 'trade_amount': self.trade_amount, 'cancel_amount': self.cancel_amount, 'towards': self.towards, 'code': self.code, 'user_cookie': self.user_cookie, 'market_type': self.market_type, 'frequence': self.frequence, 'account_cookie': self.account_cookie, 'strategy': self.strategy, 'type': self.market_type, 'order_model': self.order_model, 'amount_model': self.amount_model, 'order_id': self.order_id, 'realorder_id': self.realorder_id, 'commission_coeff': self.commission_coeff, 'tax_coeff': self.tax_coeff, 'trade_id': self.trade_id, 'trade_price': self.trade_price, 'broker': self.broker, 'callback': self.callback, 'money': self.money, 'reason': self.reason, 'exchange_id': self.exchange_id, 'time_condition': self.time_condition, '_status': self.status, 'direction': self.direction, 'offset': self.offset } def __repr__(self): ''' 输出格式化对象 :return: 字符串 ''' return '< QA_Order realorder_id {} datetime:{} code:{} amount:{} price:{} towards:{} btype:{} order_id:{} account:{} status:{} >'.format( self.realorder_id, self.datetime, self.code, self.amount, self.price, self.towards, self.type, self.order_id, self.account_cookie, self.status ) def transform_dt(self, times): if isinstance(times, str): tradedt = datetime.datetime.strptime(times, '%Y-%m-%d %H:%M:%S') if len( times) == 19 else datetime.datetime.strptime(times, '%Y-%m-%d %H:%M:%S.%f') return tradedt.timestamp()*1000000000 elif isinstance(times, datetime.datetime): return tradedt.timestamp()*1000000000 @property def status(self): # 以下几个都是最终状态 并且是外部动作导致的 if self._status in [ORDER_STATUS.FAILED, ORDER_STATUS.NEXT, ORDER_STATUS.SETTLED, ORDER_STATUS.CANCEL_ALL, ORDER_STATUS.CANCEL_PART]: return self._status if self.pending_amount <= 0: self._status = ORDER_STATUS.SUCCESS_ALL return self._status elif self.pending_amount > 0 and self.trade_amount > 0: self._status = ORDER_STATUS.SUCCESS_PART return self._status elif self.trade_amount == 0: self._status = ORDER_STATUS.QUEUED return self._status def calc_commission(self, trade_price, trade_amount): if self.market_type == MARKET_TYPE.FUTURE_CN: value = trade_price * trade_amount * \ self.market_preset.get('unit_table', 1) if self.towards in [ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN]: commission_fee = self.market_preset['commission_coeff_pervol'] * trade_amount + \ self.market_preset['commission_coeff_peramount'] * \ abs(value) elif self.towards in [ORDER_DIRECTION.BUY_CLOSETODAY, ORDER_DIRECTION.SELL_CLOSETODAY]: commission_fee = self.market_preset['commission_coeff_today_pervol'] * trade_amount + \ self.market_preset['commission_coeff_today_peramount'] * \ abs(value) return commission_fee elif self.market_type == MARKET_TYPE.STOCK_CN: commission_fee = trade_price * trade_amount * self.commission_coeff return max(commission_fee, 5) def create(self): """创建订单 """ # 创建一笔订单(未进入委托队列-- 在创建的时候调用) self._status = ORDER_STATUS.NEW def cancel(self): """撤单 Arguments: amount {int} -- 撤单数量 """ self.cancel_amount = self.amount - self.trade_amount if self.trade_amount == 0: # 未交易 直接订单全撤 self._status = ORDER_STATUS.CANCEL_ALL else: # 部分交易 剩余订单全撤 self._status = ORDER_STATUS.CANCEL_PART def failed(self, reason=None): """失败订单(未成功创建入broker) Arguments: reason {str} -- 失败原因 """ # 订单创建失败(如废单/场外废单/价格高于涨停价/价格低于跌停价/通讯失败) self._status = ORDER_STATUS.FAILED self.reason = str(reason) def trade(self, trade_id, trade_price, trade_amount, trade_time): """trade 状态 Arguments: amount {[type]} -- [description] """ if self.status in [ORDER_STATUS.SUCCESS_PART, ORDER_STATUS.QUEUED]: trade_amount = int(trade_amount) trade_id = str(trade_id) if trade_amount < 1: self._status = ORDER_STATUS.NEXT return False else: if trade_id not in self.trade_id: trade_price = float(trade_price) trade_time = str(trade_time) self.trade_id.append(trade_id) self.trade_price = ( self.trade_price * self.trade_amount + trade_price * trade_amount ) / ( self.trade_amount + trade_amount ) self.trade_amount += trade_amount self.trade_time.append(trade_time) res = self.callback( self.code, trade_id, self.order_id, self.realorder_id, trade_price, trade_amount, self.towards, trade_time ) if res == 0: return self.trade_message( trade_id, trade_price, trade_amount, trade_time ) else: return False else: return False else: print( RuntimeError( 'ORDER STATUS {} CANNNOT TRADE'.format(self.status) ) ) return False def trade_message(self, trade_id, trade_price, trade_amount, trade_time): return { "user_id": self.account_cookie, # //用户ID "order_id": self.order_id, # //交易所单号 "trade_id": trade_id, # //委托单ID, 对于一个USER, trade_id 是永远不重复的 "exchange_id": self.exchange_id, # //交易所 "instrument_id": self.code, # //在交易所中的合约代码 "exchange_trade_id": trade_id, # //交易所单号 "direction": self.direction, # //下单方向 "offset": self.offset, # //开平标志 "volume": trade_amount, # //成交手数 "price": trade_price, # //成交价格 "trade_date_time": trade_time, # //成交时间, epoch nano # //成交手续费 "commission": self.calc_commission(trade_price, trade_amount), "seqno": ''} def queued(self, realorder_id): self.realorder_id = realorder_id self._status = ORDER_STATUS.QUEUED def settle(self): self._status = ORDER_STATUS.SETTLED def get(self, key, exception=None): try: if key is None: print("key is none , return none!") return None return eval('self.{}'.format(key)) except Exception as e: return exception # 🛠todo 建议取消,直接调用var def callingback(self): """回调函数 Returns: [type] -- [description] """ if self.callback: return self.callback def info(self): ''' :return: ''' return vars(self) # 对象转变成 dfs def to_df(self): return pd.DataFrame([ vars(self), ]) # 🛠todo 建议取消,直接调用var? def to_dict(self): ''' 把对象中的属性转变成字典类型 :return: dict ''' return vars(self) def to_otgdict(self): """{ "aid": "insert_order", # //必填, 下单请求 # //必填, 需要与登录用户名一致, 或为登录用户的子账户(例如登录用户为user1, 则报单 user_id 应当为 user1 或 user1.some_unit) "user_id": account_cookie, # //必填, 委托单号, 需确保在一个账号中不重复, 限长512字节 "order_id": order_id if order_id else QA.QA_util_random_with_topic('QAOTG'), "exchange_id": exchange_id, # //必填, 下单到哪个交易所 "instrument_id": code, # //必填, 下单合约代码 "direction": order_direction, # //必填, 下单买卖方向 # //必填, 下单开平方向, 仅当指令相关对象不支持开平机制(例如股票)时可不填写此字段 "offset": order_offset, "volume": volume, # //必填, 下单手数 "price_type": "LIMIT", # //必填, 报单价格类型 "limit_price": price, # //当 price_type == LIMIT 时需要填写此字段, 报单价格 "volume_condition": "ANY", "time_condition": "GFD", } """ return { "aid": "insert_order", # //必填, 下单请求 # //必填, 需要与登录用户名一致, 或为登录用户的子账户(例如登录用户为user1, 则报单 user_id 应当为 user1 或 user1.some_unit) "user_id": self.account_cookie, # //必填, 委托单号, 需确保在一个账号中不重复, 限长512字节 "order_id": self.order_id, "exchange_id": self.exchange_id, # //必填, 下单到哪个交易所 "instrument_id": self.code, # //必填, 下单合约代码 "direction": self.direction, # //必填, 下单买卖方向 # //必填, 下单开平方向, 仅当指令相关对象不支持开平机制(例如股票)时可不填写此字段 "offset": self.offset, "volume": self.amount, # //必填, 下单手数 "price_type": self.order_model, # //必填, 报单价格类型 "limit_price": self.price, # //当 price_type == LIMIT 时需要填写此字段, 报单价格 "volume_condition": "ANY", "time_condition": "GFD", } def to_qatradegatway(self): """[summary] {'topic': 'sendorder', 'account_cookie': '100004', 'strategy_id': None, 'order_direction': 'SELL', 'order_offset': 'OPEN', 'code': 'rb1910', 'price': 3745.0, 'order_time': '2019-05-08 13:55:38.000000', 'exchange_id': 'SHFE', 'volume': 1.0, 'order_id': '5ab55219-adf6-432f-90db-f1bc5f29f4e5'} 'topic': 'sendorder', 'account_cookie': acc, 'strategy_id': 'test', 'code': code, 'price': price[code], 'order_direction': 'SELL', 'order_offset': 'CLOSE', 'volume': 1, 'order_time': str(datetime.datetime.now()), 'exchange_id': 'SHFE' """ return { 'topic': 'sendorder', 'account_cookie': self.account_cookie, 'strategy_id': self.strategy, 'order_direction': self.direction, 'order_offset': self.offset, 'code': self.code.lower(), 'price': self.price, 'order_time': self.sending_time, 'exchange_id': self.get_exchange(self.code), 'volume': int(self.amount), 'order_id': self.order_id } def to_qifi(self): return { "account_cookie": self.account_cookie, "user_id": self.account_cookie, "instrument_id": self.code, "towards": self.towards, "exchange_id": self.exchange_id, "order_time": self.datetime, "volume": self.amount, "price": self.price, "order_id": self.order_id, "seqno": 1, "direction": self.direction, # // "offset": self.offset, # // "volume_orign": self.amount, "price_type": self.order_model, "limit_price": self.price, "time_condition": "GFD", "volume_condition": "ANY", "insert_date_time": self.transform_dt(self.datetime), "exchange_order_id": self.realorder_id, "status": self.status, "volume_left": self.pending_amount, "last_msg": "", "topic": "send_order" } def from_otgformat(self, otgOrder): """[summary] Arguments: otgOrder {[type]} -- [description] {'seqno': 6, 'user_id': '106184', 'order_id': 'WDRB_QA01_FtNlyBem', 'exchange_id': 'SHFE', 'instrument_id': 'rb1905', 'direction': 'SELL', 'offset': 'OPEN', 'volume_orign': 50, #(总报单手数) 'price_type': 'LIMIT', # "LIMIT" (价格类型, ANY=市价, LIMIT=限价) 'limit_price': 3432.0, # 4500.0 (委托价格, 仅当 price_type = LIMIT 时有效) 'time_condition': 'GFD',# "GFD" (时间条件, IOC=立即完成,否则撤销, GFS=本节有效, GFD=当日有效, GTC=撤销前有效, GFA=集合竞价有效) 'volume_condition': 'ANY', # "ANY" (手数条件, ANY=任何数量, MIN=最小数量, ALL=全部数量) 'insert_date_time': 1545656460000000000,# 1501074872000000000 (下单时间(按北京时间),自unix epoch(1970-01-01 00:00:00 GMT)以来的纳秒数) 'exchange_order_id': ' 3738', 'status': 'FINISHED', # "ALIVE" (委托单状态, ALIVE=有效, FINISHED=已完) 'volume_left': 0, 'last_msg': '全部成交报单已提交'} # "报单成功" (委托单状态信息) """ self.order_id = otgOrder.get('order_id') self.account_cookie = otgOrder.get('user_id') self.exchange_id = otgOrder.get('exchange_id') self.code = str(otgOrder.get('instrument_id')).upper() self.offset = otgOrder.get('offset') self.direction = otgOrder.get('direction') self.towards = eval( 'ORDER_DIRECTION.{}_{}'.format(self.direction, self.offset) ) self.amount = otgOrder.get('volume_orign') self.trade_amount = self.amount - otgOrder.get('volume_left') self.price = otgOrder.get('limit_price') self.order_model = eval( 'ORDER_MODEL.{}'.format(otgOrder.get('price_type')) ) self.time_condition = otgOrder.get('time_condition') if otgOrder.get('insert_date_time') == 0: self.datetime = 0 else: self.datetime = QA_util_stamp2datetime( int(otgOrder.get('insert_date_time')) ) self.sending_time = self.datetime self.volume_condition = otgOrder.get('volume_condition') self.message = otgOrder.get('last_msg') self._status = ORDER_STATUS.NEW if '拒绝' in self.message or '仓位不足' in self.message: # 仓位不足: 一般是平今/平昨仓位不足 self._status = ORDER_STATUS.FAILED if '已撤单' in self.message: self._status = ORDER_STATUS.CANCEL_ALL self.realorder_id = otgOrder.get('exchange_order_id') return self def from_dict(self, order_dict): ''' 从字段类型的字段 填充 对象的字段 :param order_dict: dict 类型 :return: self QA_Order ''' try: # QA_util_log_info('QA_ORDER CHANGE: from {} change to {}'.format( # self.order_id, order['order_id'])) self.price = order_dict['price'] self.date = order_dict['date'] self.datetime = order_dict['datetime'] self.sending_time = order_dict['sending_time'] # 下单时间 self.trade_time = order_dict['trade_time'] self.amount = order_dict['amount'] self.frequence = order_dict['frequence'] self.market_type = order_dict['market_type'] self.towards = order_dict['towards'] self.code = order_dict['code'] self.user_cookie = order_dict['user_cookie'] self.account_cookie = order_dict['account_cookie'] self.strategy = order_dict['strategy'] self.type = order_dict['type'] self.order_model = order_dict['order_model'] self.amount_model = order_dict['amount_model'] self.order_id = order_dict['order_id'] self.realorder_id = order_dict['realorder_id'] self.trade_id = order_dict['trade_id'] self.callback = order_dict['callback'] self.commission_coeff = order_dict['commission_coeff'] self.tax_coeff = order_dict['tax_coeff'] self.money = order_dict['money'] self._status = order_dict['_status'] self.cancel_amount = order_dict['cancel_amount'] self.trade_amount = order_dict['trade_amount'] self.trade_price = order_dict['trade_price'] self.reason = order_dict['reason'] return self except Exception as e: QA_util_log_info('Failed to tran from dict {}'.format(e))
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的收益 """ def __init__( self, code='000001', account_cookie='quantaxis', moneypreset=100000, # 初始分配资金 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, market_type=MARKET_TYPE.STOCK_CN, exchange_id=EXCHANGE_ID.SZSE, name=None, *args, **kwargs): self.code = code self.account_cookie = account_cookie self.time = '' self.market_preset = MARKET_PRESET().get_code(self.code) self.position_id = str(uuid.uuid4()) self.moneypreset = moneypreset self.moneypresetLeft = self.moneypreset """{'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 self.market_type = market_type 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.trades = {} 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, 'name': self.name, 'market_type': self.market_type, 'exchange_id': self.exchange_id, # 交易所ID # 持仓量 '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 # 空头成本 } @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 } def order_check(self, amount: float, price: float, towards: int) -> 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 elif towards == ORDER_DIRECTION.BUY_CLOSETODAY and ( self.volume_short_today - self.volume_short_frozen_today) >= amount: self.volume_short_frozen_today += amount res = True 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 elif towards == ORDER_DIRECTION.SELL_CLOSETODAY and ( 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 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: self.moneypresetLeft -= moneyneed res = True return res def send_order(self, amount: float, price: float, towards: int): if self.order_check(amount, price, towards): print('order check success') return { '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': str(uuid.uuid4()) } else: return RuntimeError('ORDER CHECK FALSE: {}'.format(self.code)) 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 """ temp_cost = float(amount)*float(price) * \ float(self.market_preset.get('unit_table', 1)) if towards == ORDER_DIRECTION.BUY: # 股票模式/ 期货买入开仓 self.volume_long_today += amount elif towards == ORDER_DIRECTION.SELL: # 股票卖出模式: # 今日买入仓位不能卖出 if self.volume_long_his > amount: self.volume_long_his -= amount elif towards == ORDER_DIRECTION.BUY_OPEN: # 增加保证金 temp_margin = temp_cost * \ self.market_preset['buy_frozen_coeff'] self.margin_long += temp_margin # 重算开仓均价 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 -= temp_margin elif towards == ORDER_DIRECTION.SELL_OPEN: # 增加保证金 """ 1. 增加卖空保证金 2. 重新计算 开仓成本 3. 重新计算 持仓成本 4. 增加开仓cost 5. 增加持仓cost 6. 增加空单仓位 """ self.margin_short += temp_cost * \ self.market_preset['sell_frozen_coeff'] # 重新计算开仓/持仓成本 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 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 = temp_cost * self.market_preset['buy_frozen_coeff'] profit = (self.position_price_short - price ) * amount * self.market_preset.get('unit_table') # 释放保证金 # 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 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 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 # 计算收益/成本 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 } @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): pass def reload(self): pass def on_order(self, order: QA_Order): pass def on_transaction(self, transaction: dict): towards = transaction.get( 'towards', eval('ORDER_DIRECTION.{}_{}'.format(transaction.get('direction'), transaction.get('offset')))) #TODO: #在这里可以加入更多关于PMS交易的代码 try: self.update_pos( transaction['price'], transaction.get('amount', transaction.get('volume')), towards) except Exception as e: raise e print(self.static_message) 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专用')
def __init__(self, code='000001', account_cookie='quantaxis', moneypreset=100000, # 初始分配资金 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_his=0, margin_short_his=0, margin_long_today=0, margin_short_today=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, market_type=MARKET_TYPE.STOCK_CN, exchange_id=EXCHANGE_ID.SZSE, name=None, ): self.code = code self.account_cookie = account_cookie self.market_preset = MARKET_PRESET().get_code(self.code) self.position_id = uuid.uuid4() self.moneypreset = moneypreset """{'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 self.market_type = market_type 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_his = margin_long_his self.margin_short_his = margin_short_his self.margin_long_today = margin_long_today self.margin_short_today = margin_short_today 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
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/ 支持仓位风控(未加入) """ def __init__(self, code='000001', account_cookie='quantaxis', moneypreset=100000, # 初始分配资金 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_his=0, margin_short_his=0, margin_long_today=0, margin_short_today=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, market_type=MARKET_TYPE.STOCK_CN, exchange_id=EXCHANGE_ID.SZSE, name=None, ): self.code = code self.account_cookie = account_cookie self.market_preset = MARKET_PRESET().get_code(self.code) self.position_id = uuid.uuid4() self.moneypreset = moneypreset """{'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 self.market_type = market_type 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_his = margin_long_his self.margin_short_his = margin_short_his self.margin_long_today = margin_long_today self.margin_short_today = margin_short_today 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 def __repr__(self): return '< QAPOSITION {} amount {}/{} >'.format(self.code, self.volume_long, self.volume_short) @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_long(self): return self.margin_long_his + self.margin_long_today @property def margin_short(self): return self.margin_short_his + self.margin_short_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, 'name': self.name, 'market_type': self.market_type, 'exchange_id': self.exchange_id, # 交易所ID # 持仓量 '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 # 空头成本 } @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 } def receive_order(self, order:QA_Order): #self.update_pos(order.) pass 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 """ temp_cost = amount*price * \ self.market_preset.get('unit_table', 1) # if towards == ORDER_DIRECTION.SELL_CLOSE: if towards == ORDER_DIRECTION.BUY: # 股票模式/ 期货买入开仓 self.volume_long_today += amount elif towards == ORDER_DIRECTION.SELL: # 股票卖出模式: # 今日买入仓位不能卖出 if self.volume_long_his > amount: self.volume_long_his -= amount elif towards == ORDER_DIRECTION.BUY_OPEN: # 增加保证金 self.margin_long += temp_cost * \ self.market_preset['buy_frozen_coeff'] # 重算开仓均价 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 elif towards == ORDER_DIRECTION.SELL_OPEN: # 增加保证金 self.margin_short += temp_cost * \ self.market_preset['sell_frozen_coeff'] # 重新计算开仓/持仓成本 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.volume_short_today += amount 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 # close_profit = (self.position_price_short - price) * volume * position->ins->volume_multiple; #self.volume_short_frozen_today += amount # 释放保证金 # 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 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 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 -= amount # 计算收益/成本 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 } @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): pass def reload(self): pass 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专用')
def __init__(self, username, password, model="SIM", broker_name="QAPaperTrading", trade_host='127.0.0.1'): """Initial QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类 1. 兼容多持仓组合 2. 动态计算权益 使用 model = SIM/ REAL来切换 """ self.user_id = username self.username = username self.password = password self.source_id = "QIFI_Account" # 识别号 self.market_preset = MARKET_PRESET() # 指的是 Account所属的账户编组(实时的时候的账户观察组) self.portfolio = "QAPaperTrade" self.model = model self.broker_name = broker_name # 所属期货公司/ 模拟的组 self.investor_name = "" # 账户所属人(实盘的开户人姓名) self.bank_password = "" self.capital_password = "" self.wsuri = "" self.bank_id = "QASIM" self.bankname = "QASIMBank" self.trade_host = trade_host self.db = pymongo.MongoClient(trade_host).QAREALTIME self.pub_host = "" self.trade_host = "" self.last_updatetime = "" self.status = 200 self.trading_day = "" self.pre_balance = 0 self.static_balance = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.close_profit = 0 self.event_id = 0 self.money = 0 # QIFI 协议 self.transfers = {} self.banks = {} self.frozen = {} self.events = {} self.positions = {} self.trades = {} self.orders = {}
def get(self): mp = MARKET_PRESET() self.write({'result': QA_util_to_json_from_pandas(mp.pdtable.T)})
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()
def __init__(self, code='000001', account_cookie='quantaxis', portfolio_cookie='portfolio', username='******', user_cookie = '', 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.user_cookie = user_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
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 QIFI_Account(): def __init__(self, username, password, model="SIM", broker_name="QAPaperTrading", trade_host=mongo_ip, init_cash=1000000, taskid=str(uuid.uuid4())): """Initial QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类 1. 兼容多持仓组合 2. 动态计算权益 使用 model = SIM/ REAL来切换 qifiaccount 不去区分你的持仓是股票还是期货, 因此你可以实现跨市场的交易持仓管理 """ self.user_id = username self.username = username self.password = password self.source_id = "QIFI_Account" # 识别号 self.market_preset = MARKET_PRESET() # 指的是 Account所属的账户编组(实时的时候的账户观察组) self.portfolio = "QAPaperTrade" self.model = model self.broker_name = broker_name # 所属期货公司/ 模拟的组 self.investor_name = "" # 账户所属人(实盘的开户人姓名) self.bank_password = "" self.capital_password = "" self.wsuri = "" self.bank_id = "QASIM" self.bankname = "QASIMBank" self.trade_host = trade_host if model == 'BACKTEST': self.db = pymongo.MongoClient(trade_host).quantaxis else: self.db = pymongo.MongoClient(trade_host).QAREALTIME self.pub_host = "" self.trade_host = "" self.last_updatetime = "" self.status = 200 self._trading_day = "" self.init_cash = init_cash self.pre_balance = 0 self.datetime = "" self.static_balance = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.close_profit = 0 self.premium = 0 # 本交易日内交纳的期权权利金 self.event_id = 0 self.taskid = taskid self.money = 0 # QIFI 协议 self.transfers = {} self.schedule = {} self.banks = {} self.frozen = {} self.event = {} self.positions = {} self.trades = {} self.orders = {} def initial(self): self.reload() if self.pre_balance == 0 and self.balance == 0 and self.model != "REAL": self.log('Create new Account') self.create_simaccount() self.sync() @property def trading_day(self): if self.model == "BACKTEST": return str(self.datetime)[0:10] else: return self._trading_day def reload(self): if self.model.upper() in ['REAL', 'SIM']: message = self.db.account.find_one({ 'account_cookie': self.user_id, 'password': self.password }) time = datetime.datetime.now() # resume/settle if time.hour <= 15: self._trading_day = time.date() else: if time.weekday() in [0, 1, 2, 3]: self._trading_day = time.date() + datetime.timedelta( days=1) elif time.weekday() in [4, 5, 6]: self._trading_day = time.date() + datetime.timedelta( days=(7 - time.weekday())) if message is not None: accpart = message.get('accounts') self.money = message.get('money') self.source_id = message.get('sourceid') self.pre_balance = accpart.get('pre_balance') self.deposit = accpart.get('deposit') self.withdraw = accpart.get('withdraw') self.withdrawQuota = accpart.get('WithdrawQuota') self.close_profit = accpart.get('close_profit') self.static_balance = accpart.get('static_balance') self.event = message.get('event') self.trades = message.get('trades') self.transfers = message.get('transfers') self.orders = message.get('orders') self.taskid = message.get('taskid', str(uuid.uuid4())) positions = message.get('positions') for position in positions.values(): self.positions[position.get( 'instrument_id')] = QA_Position().loadfrommessage( position) for order in self.open_orders: self.log('try to deal {}'.format(order)) self.make_deal(order) self.banks = message.get('banks') self.status = message.get('status') self.wsuri = message.get('wsuri') self.on_reload() if message.get('trading_day', '') == str(self._trading_day): # reload pass else: # settle self.settle() def create_fromQIFI(self, message): pass def sync(self): self.on_sync() if self.model == "BACKTEST": ## 数据库: quantaxis.history self.db.history.update( { 'account_cookie': self.user_id, 'trading_day': self.trading_day }, {'$set': self.message}, upsert=True) else: ## 数据库: QAREALTIME.account self.db.account.update( { 'account_cookie': self.user_id, 'password': self.password }, {'$set': self.message}, upsert=True) self.db.hisaccount.insert_one({ 'updatetime': self.dtstr, 'account_cookie': self.user_id, 'accounts': self.account_msg }) def settle(self): self.log('settle') if self.model == "BACKTEST": ## 数据库: quantaxis.history self.db.history.update( { 'account_cookie': self.user_id, 'trading_day': self.trading_day }, {'$set': self.message}, upsert=True) else: self.db.hisaccount.update_one( { 'account_cookie': self.user_id, 'trading_day': self.trading_day }, {'$set': self.message}, upsert=True) self.pre_balance += (self.deposit - self.withdraw + self.close_profit) self.static_balance = self.pre_balance self.close_profit = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.premium = 0 self.money += self.frozen_margin self.orders = {} self.frozen = {} self.trades = {} self.transfers = {} self.event = {} self.event_id = 0 for item in self.positions.values(): item.settle() # sell first >> second buy ==> for make sure have enough cash buy_order_sche = [] for order in self.schedule.values(): if order['towards'] > 0: # buy order buy_order_sche.append(order) else: self.send_order(order['code'], order['amount'], order['price'], order['towards'], order['order_id']) for order in buy_order_sche: self.send_order(order['code'], order['amount'], order['price'], order['towards'], order['order_id']) self.schedule = {} def on_sync(self): pass def on_reload(self): pass @property def dtstr(self): return str(datetime.datetime.now()).replace('.', '_') def ask_deposit(self, money): self.deposit += money self.money += money self.transfers[str(self.event_id)] = { "datetime": 433241234123, # // 转账时间, epoch nano "currency": "CNY", # 币种 "amount": money, # 涉及金额 "error_id": 0, # 转账结果代码 "error_msg": "成功", # 转账结果代码 } self.event[self.dtstr] = "转账成功 {}".format(money) def ask_withdraw(self, money): if self.withdrawQuota > money: self.withdrawQuota -= money self.withdraw += money self.transfers[str(self.event_id)] = { "datetime": 433241234123, # // 转账时间, epoch nano "currency": "CNY", # 币种 "amount": -money, # 涉及金额 "error_id": 0, # 转账结果代码 "error_msg": "成功", # 转账结果代码 } self.event[self.dtstr] = "转账成功 {}".format(-money) else: self.event[self.dtstr] = "转账失败: 余额不足 left {} ask {}".format( self.withdrawQuota, money) def create_simaccount(self): self._trading_day = str(datetime.date.today()) self.wsuri = "ws://www.yutiansut.com:7988" self.pre_balance = 0 self.static_balance = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.user_id = self.user_id self.password = self.password self.money = 0 self.close_profit = 0 self.event_id = 0 self.transfers = {} self.banks = {} self.event = {} self.positions = {} self.trades = {} self.orders = {} self.banks[str(self.bank_id)] = { "id": self.bank_id, "name": self.bankname, "bank_account": "", "fetch_amount": 0.0, "qry_count": 0 } self.ask_deposit(self.init_cash) def create_backtestaccount(self): """ 生成一个回测的账户 回测账户的核心事件轴是数据的datetime, 基于数据的datetime来进行账户的更新 """ self._trading_day = "" self.pre_balance = self.init_cash self.static_balance = self.init_cash self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.user_id = self.user_id self.password = self.password self.money = self.init_cash self.close_profit = 0 self.event_id = 0 self.transfers = {} self.banks = {} self.event = {} self.positions = {} self.trades = {} self.orders = {} self.banks[str(self.bank_id)] = { "id": self.bank_id, "name": self.bankname, "bank_account": "", "fetch_amount": 0.0, "qry_count": 0 } # self.ask_deposit(self.init_cash) def add_position(self, position): if position.instrument_id not in self.positions.keys(): self.positions[position.instrument_id] = position return 0 else: return 1 def drop_position(self, position): pass def log(self, message): print(message) #self.event[self.dtstr] = message @property def open_orders(self): return [ item for item in self.orders.values() if item['volume_left'] > 0 ] @property def message(self): return { # // 账户号(兼容QUANTAXIS QAAccount)// 实盘的时候是 账户id "account_cookie": self.user_id, "password": self.password, "databaseip": self.trade_host, "model": self.model, "ping_gap": 5, "portfolio": self.portfolio, "broker_name": self.broker_name, # // 接入商名称 "capital_password": self.capital_password, # // 资金密码 (实盘用) "bank_password": self.bank_password, # // 银行密码(实盘用) "bankid": self.bank_id, # // 银行id "investor_name": self.investor_name, # // 开户人名称 "money": self.money, # // 当前可用现金 "pub_host": self.pub_host, "trade_host": self.trade_host, "taskid": self.taskid, "sourceid": self.source_id, "updatetime": str(self.last_updatetime), "wsuri": self.wsuri, "bankname": self.bankname, "trading_day": str(self.trading_day), "status": self.status, "accounts": self.account_msg, "trades": self.trades, "positions": self.position_msg, "orders": self.orders, "event": self.event, "transfers": self.transfers, "banks": self.banks, "frozen": self.frozen, "settlement": {}, } @property def account_msg(self): return { "user_id": self.user_id, "currency": "CNY", "pre_balance": self.pre_balance, "deposit": self.deposit, "withdraw": self.withdraw, "WithdrawQuota": self.withdrawQuota, "close_profit": self.close_profit, "commission": self.commission, "premium": self.premium, "static_balance": self.static_balance, "position_profit": self.position_profit, "float_profit": self.float_profit, "balance": self.balance, "margin": self.margin, "frozen_margin": self.frozen_margin, "frozen_commission": 0.0, "frozen_premium": 0.0, "available": self.available, "risk_ratio": 1 - self.available / self.balance } @property def position_msg(self): return dict( zip(self.positions.keys(), [item.message for item in self.positions.values()])) @property def position_profit(self): return sum( [position.position_profit for position in self.positions.values()]) @property def float_profit(self): return sum( [position.float_profit for position in self.positions.values()]) @property def frozen_margin(self): return sum([item.get('money') for item in self.frozen.values()]) def transform_dt(self, times): if isinstance(times, str): tradedt = datetime.datetime.strptime( times, '%Y-%m-%d %H:%M:%S') if len( times) == 19 else datetime.datetime.strptime( times.replace('_', '.'), '%Y-%m-%d %H:%M:%S.%f') return tradedt.timestamp() * 1000000000 elif isinstance(times, datetime.datetime): return tradedt.timestamp() * 1000000000 # 惰性计算 @property def available(self): return self.money @property def margin(self): """保证金 """ return sum([position.margin for position in self.positions.values()]) @property def commission(self): """本交易日内交纳的手续费 """ return sum( [position.commission for position in self.positions.values()]) @property def balance(self): """动态权益 Arguments: self {[type]} -- [description] """ return self.static_balance + self.deposit - self.withdraw + self.float_profit + self.close_profit def order_check(self, code: str, amount: int, price: float, towards: int, order_id: str) -> bool: """ order_check是账户自身的逻辑, 你可以重写这个代码 Attention: 需要注意的是 如果你修改了此部分代码 请注意如果你做了对于账户的资金的预操作请在结束的时候恢复 :::如: 下单失败-> 请恢复账户的资金和仓位 --> return Bool """ res = False qapos = self.get_position(code) self.log(qapos.curpos) self.log(qapos.close_available) if towards == ORDER_DIRECTION.BUY_CLOSE: # self.log("buyclose") # self.log(self.volume_short - self.volume_short_frozen) # self.log(amount) if (qapos.volume_short - qapos.volume_short_frozen) >= amount: # check qapos.volume_short_frozen_today += amount #qapos.volume_short_today -= amount res = True else: self.log("BUYCLOSE 仓位不足") elif towards == ORDER_DIRECTION.BUY_CLOSETODAY: if (qapos.volume_short_today - qapos.volume_short_frozen_today) >= amount: qapos.volume_short_frozen_today += amount #qapos.volume_short_today -= amount res = True else: self.log("BUYCLOSETODAY 今日仓位不足") elif towards in [ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL]: # self.log("sellclose") # self.log(self.volume_long - self.volume_long_frozen) # self.log(amount) if (qapos.volume_long - qapos.volume_long_frozen) >= amount: qapos.volume_long_frozen_today += amount #qapos.volume_long_today -= amount res = True else: self.log("SELL CLOSE 仓位不足") elif towards == ORDER_DIRECTION.SELL_CLOSETODAY: if (qapos.volume_long_today - qapos.volume_long_frozen_today) >= amount: # self.log("sellclosetoday") # self.log(self.volume_long_today - self.volume_long_frozen) # self.log(amount) qapos.volume_long_frozen_today += amount #qapos.volume_long_today -= amount return True else: self.log("SELLCLOSETODAY 今日仓位不足") elif towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN, ORDER_DIRECTION.BUY ]: """ 冻结的保证金 """ coeff = float(price) * float( self.market_preset.get_code(code).get( "unit_table", 1)) * float( self.market_preset.get_code(code).get( "buy_frozen_coeff", 1)) moneyneed = coeff * amount if self.available > moneyneed: self.money -= moneyneed self.frozen[order_id] = { 'amount': amount, 'coeff': coeff, 'money': moneyneed } res = True else: self.log("开仓保证金不足 TOWARDS{} Need{} HAVE{}".format( towards, moneyneed, self.available)) return res def send_order(self, code: str, amount: float, price: float, towards: int, order_id: str = '', datetime: str = ''): order_id = str(uuid.uuid4()) if order_id == '' else order_id if self.order_check(code, amount, price, towards, order_id): self.log("order check success") direction, offset = parse_orderdirection(towards) self.event_id += 1 order = { "account_cookie": self.user_id, "user_id": self.user_id, "instrument_id": code, "towards": int(towards), "exchange_id": self.market_preset.get_exchange(code), "order_time": self.dtstr, "volume": int(amount), "price": float(price), "order_id": order_id, "seqno": self.event_id, "direction": direction, "offset": offset, "volume_orign": int(amount), "price_type": "LIMIT", "limit_price": float(price), "time_condition": "GFD", "volume_condition": "ANY", "insert_date_time": self.transform_dt(self.dtstr), 'order_time': self.dtstr, "exchange_order_id": str(uuid.uuid4()), "status": "ALIVE", "volume_left": int(amount), "last_msg": "已报" } self.orders[order_id] = order self.log('下单成功 {}'.format(order_id)) self.sync() self.on_ordersend(order) return order else: self.log(RuntimeError("ORDER CHECK FALSE: {}".format(code))) return False def on_ordersend(self, order): pass def cancel_order(self, order_id): """Initial 撤单/ 释放冻结/ """ od = self.orders[order_id] od['last_msg'] = '已撤单' od['status'] = "CANCEL" od['volume_left'] = 0 if od['offset'] in ['CLOSE', 'CLOSETODAY']: pos = self.positions[od['instrument_id']] if od['direction'] == 'BUY': pos.volume_short_frozen_today += od['volume_left'] else: pos.volume_long_frozen_today += od['volume_left'] else: frozen = self.frozen[order_id] self.money += frozen['money'] frozen['amount'] = 0 frozen['money'] = 0 self.frozen[order_id] = frozen self.orders[order_id] = od self.log('撤单成功 {}'.format(order_id)) def make_deal(self, order: dict): if isinstance(order, dict): self.receive_deal(order["instrument_id"], trade_price=order["limit_price"], trade_time=self.dtstr, trade_amount=order["volume_left"], trade_towards=order["towards"], order_id=order['order_id'], trade_id=str(uuid.uuid4())) def receive_deal(self, code, trade_price, trade_amount, trade_towards, trade_time, message=None, order_id=None, trade_id=None, realorder_id=None): if order_id in self.orders.keys(): # update order od = self.orders[order_id] frozen = self.frozen.get(order_id, { 'order_id': order_id, 'money': 0, 'price': 0 }) vl = od.get('volume_left', 0) if trade_amount == vl: self.money += frozen['money'] frozen['amount'] = 0 frozen['money'] = 0 od['last_msg'] = '全部成交' od["status"] = "FINISHED" self.log('全部成交 {}'.format(order_id)) elif trade_amount < vl: frozen['amount'] = vl - trade_amount release_money = trade_amount * frozen.get('coeff', 1) self.money += release_money frozen['money'] -= release_money od['last_msg'] = '部分成交' od["status"] = "ALIVE" self.log('部分成交 {}'.format(order_id)) od['volume_left'] -= trade_amount self.orders[order_id] = od self.frozen[order_id] = frozen # update trade self.event_id += 1 trade_id = str(uuid.uuid4()) if trade_id is None else trade_id self.trades[trade_id] = { "seqno": self.event_id, "user_id": self.user_id, "trade_id": trade_id, "exchange_id": od['exchange_id'], "instrument_id": od['instrument_id'], "order_id": order_id, "exchange_trade_id": trade_id, "direction": od['direction'], "offset": od['offset'], "volume": trade_amount, "price": trade_price, "trade_time": trade_time, "commission": float(0), "trade_date_time": self.transform_dt(trade_time) } # update accounts print('update trade') margin, close_profit = self.get_position(code).update_pos( trade_price, trade_amount, trade_towards) self.money -= (margin - close_profit) self.close_profit += close_profit self.sync() def get_position(self, code: str = None) -> QA_Position: if code is None: return list(self.positions.values())[0] else: if code not in self.positions.keys(): self.positions[code] = QA_Position(code=code) return self.positions[code] def query_trade(self): pass def on_tick(self, tick): pass def on_bar(self, bar): pass def on_price_change(self, code, price, datetime=None): if code in self.positions.keys(): try: pos = self.get_position(code) if pos.last_price == price: pass else: pos.last_price = price self.sync() except Exception as e: self.log(e) if datetime: self.datetime = datetime def order_schedule(self, code: str, amount: float, price: float, towards: int, order_id: str = ''): """ 预调仓接口 """ if order_id == '': order_id = str(uuid.uuid4()) orderx = { 'code': code, 'amount': amount, 'price': price, 'towards': towards, 'order_id': order_id } self.schedule[order_id] = orderx
def __init__( self, code='000001', account_cookie='quantaxis', moneypreset=100000, # 初始分配资金 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, market_type=MARKET_TYPE.STOCK_CN, exchange_id=EXCHANGE_ID.SZSE, name=None, *args, **kwargs): self.code = code self.account_cookie = account_cookie self.time = '' self.market_preset = MARKET_PRESET().get_code(self.code) self.position_id = str(uuid.uuid4()) self.moneypreset = moneypreset self.moneypresetLeft = self.moneypreset """{'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 self.market_type = market_type 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.trades = {}
def __init__(self, username, password, model="SIM", broker_name="QAPaperTrading", trade_host=mongo_ip, init_cash=1000000, taskid=str(uuid.uuid4())): """Initial QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类 1. 兼容多持仓组合 2. 动态计算权益 使用 model = SIM/ REAL来切换 qifiaccount 不去区分你的持仓是股票还是期货, 因此你可以实现跨市场的交易持仓管理 """ self.user_id = username self.username = username self.password = password self.source_id = "QIFI_Account" # 识别号 self.market_preset = MARKET_PRESET() # 指的是 Account所属的账户编组(实时的时候的账户观察组) self.portfolio = "QAPaperTrade" self.model = model self.broker_name = broker_name # 所属期货公司/ 模拟的组 self.investor_name = "" # 账户所属人(实盘的开户人姓名) self.bank_password = "" self.capital_password = "" self.wsuri = "" self.bank_id = "QASIM" self.bankname = "QASIMBank" self.trade_host = trade_host if model == 'BACKTEST': self.db = pymongo.MongoClient(trade_host).quantaxis else: self.db = pymongo.MongoClient(trade_host).QAREALTIME self.pub_host = "" self.trade_host = "" self.last_updatetime = "" self.status = 200 self._trading_day = "" self.init_cash = init_cash self.pre_balance = 0 self.datetime = "" self.static_balance = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.close_profit = 0 self.premium = 0 # 本交易日内交纳的期权权利金 self.event_id = 0 self.taskid = taskid self.money = 0 # QIFI 协议 self.transfers = {} self.schedule = {} self.banks = {} self.frozen = {} self.event = {} self.positions = {} self.trades = {} self.orders = {}
def __init__( self, price=None, date=None, datetime=None, sending_time=None, trade_time=False, amount=0, market_type=None, frequence=None, towards=None, code=None, user_cookie=None, account_cookie=None, strategy=None, order_model=None, money=None, amount_model=AMOUNT_MODEL.BY_AMOUNT, broker=None, order_id=None, trade_id=False, _status=ORDER_STATUS.NEW, callback=False, commission_coeff=0.00025, tax_coeff=0.001, exchange_id=None, position_id=None, *args, **kwargs ): ''' QA_Order 对象表示一个委托业务, 有如下字段 - price 委托价格 (限价单用) - date 委托日期 (一般日线级别回测用) - datetime 当前时间 (分钟线级别和实时用) - sending_time 委托时间 (分钟线级别和实时用) - trade_time 成交时间 [list] (分钟/日线/实盘时用, 一笔订单多次成交会不断append进去) - amount 委托数量 - frequence 频率 (回测用 DAY/1min/5min/15min/30min/...) - towards 买卖方向 - code 订单的品种 - user_cookie 订单发起者 - account_cookie 订单发起账户的标识 - stratgy 策略号 - order_model 委托方式(限价/市价/下一个bar/) type str eg 'limit' - money 订单金额 - amount_model 委托量模式(按量委托/按总成交额委托) type str 'by_amount' - order_id 委托单id - trade_id 成交单id - _status 内部维护的订单状态 - callback 当订单状态改变的时候 主动回调的函数(可以理解为自动执行的OnOrderAction) - commission_coeff 手续费系数 - tax_coeff 印花税系数(股票) - exchange_id 交易所id (一般用于实盘期货) :param args: type tuple :param kwargs: type dict # 2018-08-12 把order变成一个状态机> # 以前的order只是一个信息承载的工具,现在需要让他具备状态的方法 NEW = 100 SUCCESS_ALL = 200 SUCCESS_PART = 203 # success_part 是部分成交 一个中间状态 剩余的订单还在委托队列中 QUEUED = 300 # queued 用于表示在order_queue中 实际表达的意思是订单存活 待成交 CANCEL = 400 CANCEL_PART = 402 # cancel_part是部分撤单(及 下单后成交了一部分 剩余的被撤单 这是一个最终状态) SETTLED = 500 FAILED = 600 ''' self.price = price self.datetime = None # 🛠todo 移动到 Util 类中 时间处理函数 if datetime is None and date is not None: self.date = date self.datetime = '{} 09:31:00'.format(self.date) elif date is None and datetime is not None: self.date = datetime[0:10] self.datetime = datetime elif date is not None and datetime is not None: self.date = date self.datetime = datetime else: pass self.sending_time = self.datetime if sending_time is None else sending_time # 下单时间 self.trade_time = trade_time if trade_time else [] # 成交时间 self.amount = amount # 委托数量 self.trade_amount = 0 # 成交数量 self.cancel_amount = 0 # 撤销数量 self.towards = towards # side self.code = code # 委托证券代码 self.user_cookie = user_cookie # 委托用户 self.market_type = market_type # 委托市场类别 self.frequence = frequence # 委托所在的频率(回测用) self.account_cookie = account_cookie self.strategy = strategy self.type = market_type # see below self.order_model = order_model self.amount_model = amount_model self.order_id = QA_util_random_with_topic( topic='Order' ) if order_id is None else order_id self.realorder_id = self.order_id self.commission_coeff = commission_coeff self.tax_coeff = tax_coeff self.trade_id = trade_id if trade_id else [] self.market_preset = MARKET_PRESET().get_code(self.code) self.trade_price = 0 # 成交均价 self.broker = broker self.callback = callback # 委托成功的callback self.money = money # 委托需要的金钱 self.reason = None # 原因列表 self.exchange_id = exchange_id self.time_condition = 'GFD' # 当日有效 self._status = _status self.position_id = position_id # 增加订单对于多账户以及多级别账户的支持 2018/11/12 self.mainacc_id = None if 'mainacc_id' not in kwargs.keys( ) else kwargs['mainacc_id'] self.subacc_id = None if 'subacc_id' not in kwargs.keys( ) else kwargs['subacc_id'] self.direction = 'BUY' if self.towards in [ ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE ] else 'SELL' self.offset = 'OPEN' if self.towards in [ ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN ] else 'CLOSE'
def __init__(self, code='000001', user_id='quantaxis', account_cookie='quantaxis', portfolio_cookie='quantaxis', user_cookie='quantaxis', 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, market_type=MARKET_TYPE.STOCK_CN, exchange_id=EXCHANGE_ID.SZSE, name=None, ): self.code = code self.user_id = user_id self.market_preset = MARKET_PRESET().get_code(self.code) """{'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.name = name self.market_type = market_type 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 = position_price_long self.position_price_short = position_price_short self.open_cost_long = open_cost_long self.open_cost_short = open_cost_short self.position_cost_long = position_cost_long self.position_cost_short = position_cost_short self.last_price = 0