def __init__(self, strategy_name=None, user_cookie=None, market_type=MARKET_TYPE.STOCK_CN, frequence=FREQUENCE.DAY, broker=BROKER_TYPE.BACKETEST, portfolio_cookie=None, account_cookie=None, sell_available={}, init_assets=None, cash=None, history=None, commission_coeff=0.00025, tax_coeff=0.0015, margin_level=False, allow_t0=False, allow_sellopen=False): """ :param strategy_name: 策略名称 :param user_cookie: 用户cookie :param market_type: 市场类别 默认QA.MARKET_TYPE.STOCK_CN A股股票 :param frequence: 账户级别 默认日线QA.FREQUENCE.DAY :param broker: BROEKR类 默认回测 QA.BROKER_TYPE.BACKTEST :param portfolio_cookie: 组合cookie :param account_cookie: 账户cookie :param sell_available: 可卖股票数 :param init_assets: 初始资产 默认 1000000 元 (100万) :param cash: 可用现金 默认 是 初始资产 list 类型 :param history: 交易历史 :param commission_coeff: 交易佣金 :默认 万2.5 float 类型 :param tax_coeff: 印花税 :默认 千1.5 float 类型 :param margin_level: 保证金比例 默认False :param allow_t0: 是否允许t+0交易 默认False :param allow_sellopen: 是否允许卖空开仓 默认False """ super().__init__() self._history_headers = ['datetime', 'code', 'price', 'amount', 'order_id', 'trade_id', 'account_cookie', 'commission', 'tax'] ######################################################################## # 信息类: self.strategy_name = strategy_name self.user_cookie = user_cookie self.market_type = market_type self.portfolio_cookie = portfolio_cookie self.account_cookie = QA_util_random_with_topic( 'Acc') if account_cookie is None else account_cookie self.broker = broker self.frequence = frequence self.market_data = None self._currenttime = None self.commission_coeff = commission_coeff self.tax_coeff = tax_coeff self.running_time = datetime.datetime.now() ######################################################################## # 资产类 self.orders = QA_OrderQueue() # 历史委托单 self.init_assets = 1000000 if init_assets is None else init_assets self.cash = [self.init_assets] if cash is None else cash self.cash_available = self.cash[-1] # 可用资金 self.sell_available = sell_available self.history = [] if history is None else history self.time_index = [] ######################################################################## # 规则类 # 两个规则 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level
class QA_OrderHandler(QA_Worker): """ORDER执行器 ORDEHANDLDER 归属于MARKET前置 仅负责一个无状态的执行层 ORDER执行器的作用是因为 在实盘中 当一个订单发送出去的时候,市场不会返回一个更新的订单类回来 大部分时间都依赖子线程主动查询 或者是一个市场信息来进行判断 ORDER_Handler的作用就是根据信息更新Order 用于接受订单 发送给相应的marker_broker 再根据返回的信息 进行更新 可用的market_broker: 1.回测盘 2.实时模拟盘 3.实盘 """ def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() def run(self, event): if event.event_type is BROKER_EVENT.RECEIVE_ORDER: # 此时的message应该是订单类 order = self.order_queue.insert_order(event.order) if event.callback: event.callback(order) elif event.event_type is BROKER_EVENT.TRADE: res=[] for item in self.order_queue.trade_list: result=event.broker.receive_order( QA_Event(event_type=BROKER_EVENT.TRADE, order=item)) self.order_queue.set_status( item.order_id, result['header']['status']) if item.callback: item.callback(result) res.append(result) event.res = res return event elif event.event_type is BROKER_EVENT.SETTLE: self.order_queue.settle() elif event.event_type is MARKET_EVENT.QUERY_ORDER: return self.query_order(event.order_id) def query_order(self, order_id): return self.order_queue.queue_df.query()
class QA_OrderHandler(QA_Worker): """ORDER执行器 ORDEHANDLDER 归属于MARKET前置 仅负责一个无状态的执行层 ORDER执行器的作用是因为 在实盘中 当一个订单发送出去的时候,市场不会返回一个更新的订单类回来 大部分时间都依赖子线程主动查询 或者是一个市场信息来进行判断 ORDER_Handler的作用就是根据信息更新Order 用于接受订单 发送给相应的marker_broker 再根据返回的信息 进行更新 可用的market_broker: 1.回测盘 2.实时模拟盘 3.实盘 """ def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() def run(self, event): if event.event_type is BROKER_EVENT.RECEIVE_ORDER: # 此时的message应该是订单类 order = self.order_queue.insert_order(event.order) if event.callback: event.callback(order) elif event.event_type is BROKER_EVENT.TRADE: res=[] for item in self.order_queue.trade_list: result=event.broker.receive_order( QA_Event(event_type=BROKER_EVENT.TRADE, order=item)) self.order_queue.set_status( item.order_id, result['header']['status']) if item.callback: item.callback(result) res.append(result) event.res = res return event elif event.event_type is BROKER_EVENT.SETTLE: self.order_queue.settle() elif event.event_type is MARKET_EVENT.QUERY_ORDER: return self.query_order(event.order_id) def query_order(self, order_id): return self.order_queue.queue.query()
def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() self.if_start_orderquery = False
def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() self.if_start_orderquery = False self.monitor = {} # 1.1新增 用于监控订单
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不会基于行情计算市值,因此都只会对应记录证券数量和现金资产 """ 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.0015, margin_level=False, allow_t0=False, allow_sellopen=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: 保证金比例 默认False :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :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: 交易历史 """ 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', 'trade_id', 'account_cookie', 'commission', 'tax' ] ######################################################################## # 信息类: 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 = [] ######################################################################## # 规则类 # 两个规则 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level 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, '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 } @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 start_date(self): """账户的起始交易日期 Raises: RuntimeWarning -- [description] Returns: [type] -- [description] """ if len(self.time_index) > 0: return min(self.time_index)[0:10] else: raise 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 max(self.time_index)[0:10] else: raise RuntimeWarning( 'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE') @property def trade_range(self): return QA_util_get_trade_range(self.start_date, self.end_date) @property def history_table(self): '交易历史的table' return pd.DataFrame(data=self.history, 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(self): """可用持仓 """ return pd.DataFrame( data=self.history, columns=self._history_headers).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): '每日交易结算时的现金表' return self.cash_table.drop_duplicates( subset='date', keep='last').set_index(['date', 'account_cookie'], drop=False).sort_index() @property def daily_hold(self): '每日交易结算时的持仓表' data = self.trade.cumsum() data = data.assign(account_cookie=self.account_cookie).assign( date=data.index.levels[0]) data.date = data.date.apply(lambda x: str(x)[0:10]) data = data.set_index(['date', 'account_cookie']) return data[~data.index.duplicated(keep='last')].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_deal(self, message): """ 用于更新账户 update history and cash :param message: :return: """ if message['header']['status'] is TRADE_STATUS.SUCCESS: trade_amount = float( float(message['body']['order']['price']) * float(message['body']['order']['amount']) * message['body']['order']['towards'] + float(message['body']['fee']['commission']) + float(message['body']['fee']['tax'])) if self.cash[-1] > trade_amount: self.time_index.append( str(message['body']['order']['datetime'])) self.history.append([ str(message['body']['order']['datetime']), str(message['body']['order']['code']), float(message['body']['order']['price']), int(message['body']['order']['towards']) * float(message['body']['order']['amount']), self.cash[-1] - trade_amount, str(message['header']['order_id']), str(message['header']['trade_id']), str(self.account_cookie), float(message['body']['fee']['commission']), float(message['body']['fee']['tax']) ]) self.cash.append(self.cash[-1] - trade_amount) self.cash_available = self.cash[-1] # 资金立刻结转 else: print(message) print(self.cash[-1]) self.cash_available = self.cash[-1] print('NOT ENOUGH MONEY FOR {}'.format( message['body']['order'])) self.datetime = message['body']['order']['datetime'] return self.message def send_order(self, code=None, amount=None, time=None, towards=None, price=None, money=None, order_model=None, amount_model=None): """ 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: """ 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股 amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (price * (1 + self.commission_coeff))) # 🛠todo 移到Utils类中, money_to_amount 金额转成交量 money = amount * price * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money # amount_model = AMOUNT_MODEL.BY_AMOUNT # flag 判断买卖 数量和价格以及买卖方向是否正确 flag = False assert (int(towards) != 0) if int(towards) > 0: # 是买入的情况(包括买入.买开.买平) if self.cash_available >= money: if self.market_type is MARKET_TYPE.STOCK_CN: # 如果是股票 买入的时候有100股的最小限制 amount = int(amount / 100) * 100 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 print('T0交易买入超出限额') else: self.cash_available -= money flag = True else: print('可用资金不足 {} {} {} {}'.format(code, time, amount, towards)) elif int(towards) < 0: # 是卖出的情况(包括卖出,卖出开仓allow_sellopen如果允许. 卖出平仓) #print(self.sell_available[code]) if self.sell_available.get(code, 0) >= amount: self.sell_available[code] -= amount flag = True elif self.allow_sellopen: if self.cash_available > money: # 卖空的市值小于现金(有担保的卖空), 不允许裸卖空 flag = True else: print("卖空资金不足/不允许裸卖空") else: print('资金股份不足/不允许卖空开仓') 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) # init # 历史委托order状态存储, 保存到 QA_Order 对象中的队列中 self.datetime = time self.orders.insert_order(_order) return _order else: print('ERROR : amount=0 {} {} {} {}'.format( code, time, amount, towards)) return False @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): '同步可用资金/可卖股票' if self.running_environment == RUNNING_ENVIRONMENT.TZERO and self.hold_available.sum( ) != 0: raise RuntimeError('QAACCOUNT: 该T0账户未当日仓位,请平仓 {}'.format( self.hold_available.to_dict())) 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 ", 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.commission_coeff = message.get('commission_coeff', 0.00015) self.tax_coeff = message.get('tax_coeff', 0.0015) self.history = message['history'] self.cash = message['cash'] self.time_index = message['trade_index'] self.running_time = message.get('running_time', None) self.quantaxis_version = message.get('quantaxis_version', None) self.running_environment = message.get('running_environment', RUNNING_ENVIRONMENT.BACKETEST) self.settle() return self @property def table(self): """ 打印出account的内容 """ return pd.DataFrame([ self.message, ]).set_index('account_cookie', drop=False).T def run(self, event): ''' 这个方法是被 QA_ThreadEngine 处理队列时候调用的, QA_Task 中 do 方法调用 run (在其它线程中) 'QA_WORKER method 重载' :param event: 事件类型 QA_Event :return: ''' 'QA_WORKER method' if event.event_type is ACCOUNT_EVENT.SETTLE: self.settle() elif event.event_type is ACCOUNT_EVENT.UPDATE: self.receive_deal(event.message) elif event.event_type is ACCOUNT_EVENT.MAKE_ORDER: """generate order if callback callback the order if not return back the order """ data = self.send_order(code=event.code, amount=event.amount, time=event.time, amount_model=event.amount_model, towards=event.towards, price=event.price, order_model=event.order_model) if event.callback: event.callback(data) else: return data elif event.event_type is ENGINE_EVENT.UPCOMING_DATA: """update the market_data 1. update the inside market_data struct 2. tell the on_bar methods """ self._currenttime = event.market_data.datetime[0] if self.market_data is None: self.market_data = event.market_data else: self.market_data = self.market_data + event.market_data self.on_bar(event) if event.callback: event.callback(event) def save(self): """ 存储账户信息 """ save_account(self.message) def sync_account(self, sync_message): """同步账户 Arguments: sync_message {[type]} -- [description] """ self.init_hold = sync_message['hold_available'] self.init_cash = sync_message['cash_available'] self.sell_available = copy.deepcopy(self.init_hold) self.history = [] self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 在途资金 def change_cash(self, money): """ 外部操作|高危| """ res = self.cash[-1] + money if res >= 0: # 高危操作 self.cash[-1] = res def get_orders(self, if_today=True): ''' 返回当日委托/历史委托 :param if_today: true 只返回今天的订单 :return: QA_OrderQueue ''' # 🛠todo 筛选其它不是今天的订单返回 return self.orders def get_history(self, start, end): """返回历史成交 Arguments: start {str} -- [description] end {str]} -- [description] """ return self.history_table.set_index('datetime', drop=False).loc[slice( pd.Timestamp(start), pd.Timestamp(end))]
class QA_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 """ def __init__(self, strategy_name=None, user_cookie=None, market_type=MARKET_TYPE.STOCK_CN, frequence=FREQUENCE.DAY, broker=BROKER_TYPE.BACKETEST, portfolio_cookie=None, account_cookie=None, sell_available={}, init_assets=None, cash=None, history=None, commission_coeff=0.00025, tax_coeff=0.0015, margin_level=False, allow_t0=False, allow_sellopen=False): """ :param strategy_name: 策略名称 :param user_cookie: 用户cookie :param market_type: 市场类别 默认QA.MARKET_TYPE.STOCK_CN A股股票 :param frequence: 账户级别 默认日线QA.FREQUENCE.DAY :param broker: BROEKR类 默认回测 QA.BROKER_TYPE.BACKTEST :param portfolio_cookie: 组合cookie :param account_cookie: 账户cookie :param sell_available: 可卖股票数 :param init_assets: 初始资产 默认 1000000 元 (100万) :param cash: 可用现金 默认 是 初始资产 list 类型 :param history: 交易历史 :param commission_coeff: 交易佣金 :默认 万2.5 float 类型 :param tax_coeff: 印花税 :默认 千1.5 float 类型 :param margin_level: 保证金比例 默认False :param allow_t0: 是否允许t+0交易 默认False :param allow_sellopen: 是否允许卖空开仓 默认False """ super().__init__() self._history_headers = ['datetime', 'code', 'price', 'amount', 'order_id', 'trade_id', 'account_cookie', 'commission', 'tax'] ######################################################################## # 信息类: self.strategy_name = strategy_name self.user_cookie = user_cookie self.market_type = market_type self.portfolio_cookie = portfolio_cookie self.account_cookie = QA_util_random_with_topic( 'Acc') if account_cookie is None else account_cookie self.broker = broker self.frequence = frequence self.market_data = None self._currenttime = None self.commission_coeff = commission_coeff self.tax_coeff = tax_coeff self.running_time = datetime.datetime.now() ######################################################################## # 资产类 self.orders = QA_OrderQueue() # 历史委托单 self.init_assets = 1000000 if init_assets is None else init_assets self.cash = [self.init_assets] if cash is None else cash self.cash_available = self.cash[-1] # 可用资金 self.sell_available = sell_available self.history = [] if history is None else history self.time_index = [] ######################################################################## # 规则类 # 两个规则 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level def __repr__(self): return '< QA_Account {}>'.format(self.account_cookie) @property def message(self): 'the standard message which can be transef' 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': self._currenttime, 'allow_sellopen': self.allow_sellopen, 'allow_t0': self.allow_t0, 'margin_level': self.margin_level, 'init_assets': self.init_assets, 'commission_coeff': self.commission_coeff, 'tax_coeff': self.tax_coeff, 'cash': self.cash, 'history': self.history, 'trade_index': self.time_index, 'running_time': datetime.datetime.now() } @property def code(self): """ 该账户曾交易代码 用set 去重 """ return list(set([item[1] for item in self.history])) @property def start_date(self): return min(self.time_index)[0:10] @property def end_date(self): return max(self.time_index)[0:10] @property def trade_range(self): return QA_util_get_trade_range(self.start_date, self.end_date) @property def history_table(self): '交易历史的table' return pd.DataFrame(data=self.history, 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) return _cash.set_index(['datetime', 'account_cookie'], drop=False).sort_index() @property def hold(self): '持仓' return pd.DataFrame(data=self.history, columns=self._history_headers).groupby('code').amount.sum().sort_index() @property def order_table(self): """return order trade list""" return self.orders.trade_list @property def trade(self): '每次交易的pivot表' return self.history_table.pivot_table(index=['datetime', 'account_cookie'], columns='code', values='amount').fillna(0).sort_index() @property def daily_cash(self): '每日交易结算时的现金表' return self.cash_table.drop_duplicates(subset='date', keep='last').sort_index() @property def daily_hold(self): '每日交易结算时的持仓表' data = self.trade.cumsum() data = data.assign(account_cookie=self.account_cookie).assign( date=data.index.levels[0]) data.date = data.date.apply(lambda x: str(x)[0:10]) data=data.set_index(['date', 'account_cookie']) return data[~data.index.duplicated(keep='last')].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: return self.history_table.set_index('datetime').sort_index().groupby('code').amount.sum().sort_index() else: return self.history_table.set_index('datetime').sort_index().loc[:datetime].groupby('code').amount.sum().sort_index() def hold_price(self, datetime=None): "计算持仓成本 如果给的是日期,则返回当日开盘前的持仓" def weights(x): if sum(x['amount']) != 0: return np.average(x['price'], weights=x['amount'], returned=True) else: return (0, 0) if datetime is None: return self.history_table.set_index('datetime').sort_index().groupby('code').apply(weights) else: return self.history_table.set_index('datetime').sort_index().loc[:datetime].groupby('code').apply(weights) def reset_assets(self, init_assets=None): 'reset_history/cash/' self.sell_available = {} self.history = [] self.init_assets = init_assets self.cash = [self.init_assets] self.cash_available = self.cash[-1] # 在途资金 def receive_deal(self, message): """ 用于更新账户 update history and cash :param message: :return: """ if message['header']['status'] is TRADE_STATUS.SUCCESS: trade_amount = float(float(message['body']['order']['price']) * float(message['body']['order']['amount']) * message['body']['order']['towards'] + float(message['body']['fee']['commission']) + float(message['body']['fee']['tax'])) if self.cash[-1] > trade_amount: self.time_index.append( str(message['body']['order']['datetime'])) self.history.append( [str(message['body']['order']['datetime']), str(message['body']['order']['code']), float(message['body']['order']['price']), int(message['body']['order']['towards']) * float(message['body']['order']['amount']), str( message['header']['order_id']), str(message['header']['trade_id']), str(self.account_cookie), float(message['body']['fee']['commission']), float(message['body']['fee']['tax'])]) self.cash.append(self.cash[-1]-trade_amount) self.cash_available = self.cash[-1] # 资金立刻结转 else: print(message) print(self.cash[-1]) self.cash_available = self.cash[-1] print('NOT ENOUGH MONEY FOR {}'.format(message['body']['order'])) return self.message def send_order(self, code=None, amount=None, time=None, towards=None, price=None, money=None, order_model=None, amount_model=None): """ 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: """ 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股 amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (price*(1+self.commission_coeff))) #🛠todo 移到Utils类中, money_to_amount 金额转成交量 money = amount * price * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money # amount_model = AMOUNT_MODEL.BY_AMOUNT # flag 判断买卖 数量和价格以及买卖方向是否正确 flag = False assert (int(towards) != 0) if int(towards) > 0: # 是买入的情况(包括买入.买开.买平) if self.cash_available >= money: self.cash_available -= money if self.market_type is MARKET_TYPE.STOCK_CN: # 如果是股票 买入的时候有100股的最小限制 amount = int(amount / 100) * 100 flag = True else: print('可用资金不足') elif int(towards) < 0: # 是卖出的情况(包括卖出,卖出开仓allow_sellopen如果允许. 卖出平仓) if self.sell_available.get(code, 0) >= amount: self.sell_available[code] -= amount flag = True elif self.allow_sellopen: if self.cash_available > money: # 卖空的市值小于现金(有担保的卖空), 不允许裸卖空 flag = True else: print("卖空资金不足/不允许裸卖空") else: print('资金股份不足/不允许卖空开仓') 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) # init self.orders.insert_order(_order) # 历史委托order状态存储, 保存到 QA_Order 对象中的队列中 return _order else: print('ERROR : amount=0') return False def settle(self): '同步可用资金/可卖股票' self.sell_available = self.hold def on_bar(self, event): ''' 策略事件 :param event: :return: ''' 'while updating the market data' print("on_bar ",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_assets = message['init_assets'] 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.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[-1] 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 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 __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.0015, margin_level=False, allow_t0=False, allow_sellopen=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: 保证金比例 默认False :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :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: 交易历史 """ 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', 'trade_id', 'account_cookie', 'commission', 'tax'] ######################################################################## # 信息类: 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 = [] ######################################################################## # 规则类 # 两个规则 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level
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不会基于行情计算市值,因此都只会对应记录证券数量和现金资产 """ 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.0015, margin_level=False, allow_t0=False, allow_sellopen=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: 保证金比例 默认False :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :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: 交易历史 """ 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', 'trade_id', 'account_cookie', 'commission', 'tax'] ######################################################################## # 信息类: 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 = [] ######################################################################## # 规则类 # 两个规则 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level 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, '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 } @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 start_date(self): """账户的起始交易日期 Raises: RuntimeWarning -- [description] Returns: [type] -- [description] """ if len(self.time_index) > 0: return min(self.time_index)[0:10] else: raise 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 max(self.time_index)[0:10] else: raise RuntimeWarning( 'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE') @property def trade_range(self): return QA_util_get_trade_range(self.start_date, self.end_date) @property def history_table(self): '交易历史的table' return pd.DataFrame(data=self.history, 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(self): """可用持仓 """ return pd.DataFrame(data=self.history, columns=self._history_headers).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): '每日交易结算时的现金表' return self.cash_table.drop_duplicates(subset='date', keep='last').set_index(['date', 'account_cookie'], drop=False).sort_index() @property def daily_hold(self): '每日交易结算时的持仓表' data = self.trade.cumsum() data = data.assign(account_cookie=self.account_cookie).assign( date=data.index.levels[0]) data.date = data.date.apply(lambda x: str(x)[0:10]) data = data.set_index(['date', 'account_cookie']) return data[~data.index.duplicated(keep='last')].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_deal(self, message): """ 用于更新账户 update history and cash :param message: :return: """ if message['header']['status'] is TRADE_STATUS.SUCCESS: trade_amount = float(float(message['body']['order']['price']) * float(message['body']['order']['amount']) * message['body']['order']['towards'] + float(message['body']['fee']['commission']) + float(message['body']['fee']['tax'])) if self.cash[-1] > trade_amount: self.time_index.append( str(message['body']['order']['datetime'])) self.history.append( [str(message['body']['order']['datetime']), str(message['body']['order']['code']), float(message['body']['order']['price']), int(message['body']['order']['towards']) * float(message['body']['order']['amount']), self.cash[-1]-trade_amount, str( message['header']['order_id']), str(message['header']['trade_id']), str(self.account_cookie), float(message['body']['fee']['commission']), float(message['body']['fee']['tax'])]) self.cash.append(self.cash[-1]-trade_amount) self.cash_available = self.cash[-1] # 资金立刻结转 else: print(message) print(self.cash[-1]) self.cash_available = self.cash[-1] print('NOT ENOUGH MONEY FOR {}'.format( message['body']['order'])) self.datetime = message['body']['order']['datetime'] return self.message def send_order(self, code=None, amount=None, time=None, towards=None, price=None, money=None, order_model=None, amount_model=None): """ 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: """ 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股 amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (price*(1+self.commission_coeff))) # 🛠todo 移到Utils类中, money_to_amount 金额转成交量 money = amount * price * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money # amount_model = AMOUNT_MODEL.BY_AMOUNT # flag 判断买卖 数量和价格以及买卖方向是否正确 flag = False assert (int(towards) != 0) if int(towards) > 0: # 是买入的情况(包括买入.买开.买平) if self.cash_available >= money: if self.market_type is MARKET_TYPE.STOCK_CN: # 如果是股票 买入的时候有100股的最小限制 amount = int(amount / 100) * 100 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 print('T0交易买入超出限额') else: self.cash_available -= money flag = True else: print('可用资金不足 {} {} {} {}'.format(code, time, amount, towards)) elif int(towards) < 0: # 是卖出的情况(包括卖出,卖出开仓allow_sellopen如果允许. 卖出平仓) #print(self.sell_available[code]) if self.sell_available.get(code, 0) >= amount: self.sell_available[code] -= amount flag = True elif self.allow_sellopen: if self.cash_available > money: # 卖空的市值小于现金(有担保的卖空), 不允许裸卖空 flag = True else: print("卖空资金不足/不允许裸卖空") else: print('资金股份不足/不允许卖空开仓') 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) # init # 历史委托order状态存储, 保存到 QA_Order 对象中的队列中 self.datetime = time self.orders.insert_order(_order) return _order else: print('ERROR : amount=0 {} {} {} {}'.format( code, time, amount, towards)) return False @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): '同步可用资金/可卖股票' if self.running_environment == RUNNING_ENVIRONMENT.TZERO and self.hold_available.sum() != 0: raise RuntimeError('QAACCOUNT: 该T0账户未当日仓位,请平仓 {}'.format( self.hold_available.to_dict())) 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 ", 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.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 change_cash(self, money): """ 外部操作|高危| """ res = self.cash[-1]+money if res >= 0: # 高危操作 self.cash[-1] = res def get_orders(self, if_today=True): ''' 返回当日委托/历史委托 :param if_today: true 只返回今天的订单 :return: QA_OrderQueue ''' # 🛠todo 筛选其它不是今天的订单返回 return self.orders def get_history(self, start, end): """返回历史成交 Arguments: start {str} -- [description] end {str]} -- [description] """ return self.history_table.set_index('datetime', drop=False).loc[slice(pd.Timestamp(start), pd.Timestamp(end))]
class QA_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不会基于行情计算市值,因此都只会对应记录证券数量和现金资产 """ 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=False, allow_t0=False, allow_sellopen=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: 保证金比例 默认False :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :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' ] ######################################################################## # 信息类: 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 = [] ######################################################################## # 规则类 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) # 期货: allow_t0 True allow_sellopen True # self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level # 保证金比例 """期货的多开/空开 ==> 资金冻结进保证金 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 } @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 start_date(self): """账户的起始交易日期 Raises: RuntimeWarning -- [description] Returns: [type] -- [description] """ if len(self.time_index) > 0: return str(min(self.time_index))[0:10] else: raise 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: raise 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 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(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() 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): """快速撮合成交接口 此接口是一个直接可以成交的接口, 所以务必确保给出的信息是可以成交的 此接口涉及的是 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利润 """ self.datetime = trade_time market_towards = 1 if trade_towards > 0 else -1 trade_money = float(trade_price * trade_amount * market_towards) # trade_price if self.market_type == MARKET_TYPE.FUTURE_CN: # 期货不收税 # 双边手续费 也没有最小手续费限制 commission_fee = self.commission_coeff * \ abs(trade_money) tax_fee = 0 elif self.market_type == MARKET_TYPE.STOCK_CN: commission_fee = self.commission_coeff * \ abs(trade_money) tax_fee = self.tax_coeff * \ abs(trade_money) trade_money += (commission_fee + tax_fee) if self.cash[-1] > trade_money: 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 } else: self.frozen[code] = { ORDER_DIRECTION.BUY_OPEN: { 'money': 0, 'amount': 0 }, ORDER_DIRECTION.SELL_OPEN: { 'money': 0, 'amount': 0 } } 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]['amount'] += trade_amount self.cash.append(self.cash[-1] - abs(trade_money)) 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 self.cash.append(self.cash[-1] - trade_money + self.frozen[code] [ORDER_DIRECTION.SELL_OPEN]['money'] * trade_amount * 2) if self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['money'] = 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 self.cash.append(self.cash[-1] - trade_money) if self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['money'] = 0 else: self.cash.append(self.cash[-1] - trade_money) if self.allow_t0: 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] self.history.append([ trade_time, code, trade_price, market_towards * trade_amount, self.cash[-1], None, None, None, self.account_cookie, commission_fee, tax_fee, message ]) 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 trade_money = trade_price * trade_amount * market_towards commission_fee = trade_money * self.commission_coeff if self.market_type == MARKET_TYPE.STOCK_CN: if trade_towards > 0: commission_fee = self.commission_coeff * \ abs(trade_money) commission_fee = 5 if commission_fee < 5 else commission_fee tax_fee = 0 # 买入不收印花税 if self.allow_t0: self.sell_available = self.hold self.buy_available = self.hold else: commission_fee = self.commission_coeff * \ abs(trade_money) commission_fee = 5 if commission_fee < 5 else commission_fee tax_fee = self.tax_coeff * \ abs(trade_money) # self.trade_money = self.deal_price * \ # self.deal_amount + self.commission_fee + self.tax elif self.market_type == MARKET_TYPE.FUTURE_CN: # 期货不收税 # 双边手续费 也没有最小手续费限制 commission_fee = self.commission_coeff * \ abs(trade_money) # commission_fee = 5 if commission_fee < 5 else commission_fee #self.commission_fee = 5 if commission_fee < 5 else commission_fee tax_fee = 0 # 买入不收印花税 _trade_money_frozen = abs(trade_money) + commission_fee + tax_fee trade_money += (commission_fee + tax_fee) if self.cash[-1] > trade_money: 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 } else: self.frozen[code] = { ORDER_DIRECTION.BUY_OPEN: { 'money': 0, 'amount': 0 }, ORDER_DIRECTION.SELL_OPEN: { 'money': 0, 'amount': 0 } } 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]['amount'] += trade_amount self.cash.append(self.cash[-1] - _trade_money_frozen) 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 self.cash.append(self.cash[-1] - trade_money + self.frozen[code] [ORDER_DIRECTION.SELL_OPEN]['money'] * trade_amount * 2) if self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['money'] = 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 self.cash.append(self.cash[-1] - trade_money) if self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['money'] = 0 else: self.cash.append(self.cash[-1] - trade_money) self.cash_available = self.cash[-1] 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 ]) if self.allow_t0: self.sell_available = self.hold self.buy_available = self.hold else: print(self.cash[-1]) self.cash_available = self.cash[-1] print('NOT ENOUGH MONEY FOR {}'.format(order_id)) self.datetime = trade_time # return self.message 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: """ 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股 # amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( # money / (price*(1+self.commission_coeff))) 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 金额转成交量 money = amount * price * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money # amount_model = AMOUNT_MODEL.BY_AMOUNT # 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 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 print('T0交易买入超出限额') else: self.cash_available -= money flag = True else: # 如果有负持仓-- 允许卖空的时候 if self.allow_sellopen and towards == 3: # 多平 _hold = self.sell_available.get(code, 0) left_amount = amount + _hold if _hold < 0 else amount _money = float(left_amount * price + amount * price * self.commission_coeff) if self.cash_available >= _money: self.cash_available -= _money flag = True else: print( '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: # left_amount = amount-_hold if _hold > 0 else amount # 如果仓位是反的 # _money = float(left_amount * price + amount * # price*self.commission_coeff) if 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) print("卖空资金不足/不允许裸卖空") # else: # print('资金股份不足/不允许卖空开仓') 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)) 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): '同步可用资金/可卖股票' if self.running_environment == RUNNING_ENVIRONMENT.TZERO and self.hold_available.sum( ) != 0: raise RuntimeError('QAACCOUNT: 该T0账户未当日仓位,请平仓 {}'.format( self.hold_available.to_dict())) self.sell_available = self.hold self.buy_available = self.hold self.datetime = '{} 09:30:00'.format(QA_util_get_next_day( self.date)) if self.date is not None else None def on_bar(self, event): ''' 策略事件 :param event: :return: ''' 'while updating the market data' print("on_bar account {} ".format(self.account_cookie), event.market_data) def on_tick(self, event): ''' 策略事件 :param event: :return: ''' 'on tick event' print("on_tick ", event.market_data) pass def from_message(self, message): """resume the account from standard message 这个是从数据库恢复账户时需要的""" self.account_cookie = message.get('account_cookie', None) self.portfolio_cookie = message.get('portfolio_cookie', None) self.user_cookie = message.get('user_cookie', None) self.broker = message.get('broker', None) self.market_type = message.get('market_type', None) self.strategy_name = message.get('strategy_name', None) self._currenttime = message.get('current_time', None) self.allow_sellopen = message.get('allow_sellopen', False) self.allow_t0 = message.get('allow_t0', False) self.margin_level = message.get('margin_level', False) self.init_cash = message.get('init_cash', message.get('init_assets', 1000000)) # 兼容修改 self.init_hold = pd.Series(message.get('init_hold', {}), name='amount') self.init_hold.index.name = 'code' self.commission_coeff = message.get('commission_coeff', 0.00015) self.tax_coeff = message.get('tax_coeff', 0.0015) self.history = message['history'] self.cash = message['cash'] self.time_index = message['trade_index'] self.running_time = message.get('running_time', None) self.quantaxis_version = message.get('quantaxis_version', None) self.running_environment = message.get('running_environment', RUNNING_ENVIRONMENT.BACKETEST) self.settle() return self @property def table(self): """ 打印出account的内容 """ return pd.DataFrame([ self.message, ]).set_index('account_cookie', drop=False).T def run(self, event): ''' 这个方法是被 QA_ThreadEngine 处理队列时候调用的, QA_Task 中 do 方法调用 run (在其它线程中) 'QA_WORKER method 重载' :param event: 事件类型 QA_Event :return: ''' 'QA_WORKER method' if event.event_type is ACCOUNT_EVENT.SETTLE: self.settle() # elif event.event_type is ACCOUNT_EVENT.UPDATE: # self.receive_deal(event.message) elif event.event_type is ACCOUNT_EVENT.MAKE_ORDER: """generate order if callback callback the order if not return back the order """ data = self.send_order(code=event.code, amount=event.amount, time=event.time, amount_model=event.amount_model, towards=event.towards, price=event.price, order_model=event.order_model) if event.callback: event.callback(data) else: return data elif event.event_type is ENGINE_EVENT.UPCOMING_DATA: """update the market_data 1. update the inside market_data struct 2. tell the on_bar methods # 这样有点慢 """ self._currenttime = event.market_data.datetime[0] if self._market_data is None: self._market_data = event.market_data else: self._market_data = self._market_data + event.market_data self.on_bar(event) if event.callback: event.callback(event) def save(self): """ 存储账户信息 """ save_account(self.message) def sync_account(self, sync_message): """同步账户 Arguments: sync_message {[type]} -- [description] """ self.init_hold = sync_message['hold_available'] self.init_cash = sync_message['cash_available'] self.sell_available = copy.deepcopy(self.init_hold) self.history = [] self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 在途资金 def change_cash(self, money): """ 外部操作|高危| """ res = self.cash[-1] + money if res >= 0: # 高危操作 self.cash[-1] = res def get_orders(self, if_today=True): ''' 返回当日委托/历史委托 :param if_today: true 只返回今天的订单 :return: QA_OrderQueue ''' # 🛠todo 筛选其它不是今天的订单返回 return self.orders def get_history(self, start, end): """返回历史成交 Arguments: start {str} -- [description] end {str]} -- [description] """ return self.history_table.set_index('datetime', drop=False).loc[slice( pd.Timestamp(start), pd.Timestamp(end))]
class QA_OrderHandler(QA_Worker): """ORDER执行器 ORDEHANDLDER 归属于MARKET前置 仅负责一个无状态的执行层 ORDER执行器的作用是因为 在实盘中 当一个订单发送出去的时候,市场不会返回一个更新的订单类回来 大部分时间都依赖子线程主动查询 或者是一个市场信息来进行判断 ORDER_Handler的作用就是根据信息更新Order 用于接受订单 发送给相应的marker_broker 再根据返回的信息 进行更新 可用的market_broker: 1.回测盘 2.实时模拟盘 3.实盘 ORDERHANDLER 持久化问题: 设定机制: 2秒查询1次 持久化: 2秒一次 2018-07-29 # 重新设置ORDERHADLER的运行模式: -- 常规检查 5秒一次 -- 如果出现订单 则2-3秒 对账户轮询(直到出现订单成交/撤单为止) """ def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() self.if_start_orderquery = False self.monitor = {} # 1.1新增 用于监控订单 def run(self, event): if event.event_type is BROKER_EVENT.RECEIVE_ORDER: # 此时的message应该是订单类 """ OrderHandler 收到订单 orderhandler 调控转发给broker broker返回发单结果(成功/失败) orderhandler.order_queue 插入一个订单 执行回调 """ order = event.order order = event.broker.receive_order( QA_Event( event_type=BROKER_EVENT.TRADE, order=event.order, market_data=event.market_data ) ) order = self.order_queue.insert_order(order) if event.callback: event.callback(order) elif event.event_type is BROKER_EVENT.TRADE: # 实盘和本地 同步执行 self._trade() # event.event_queue.task_done() elif event.event_type is BROKER_EVENT.SETTLE: """订单队列的结算: 当队列中的订单都被处理过后: 算可以结算了 """ print('SETTLE ORDERHANDLER') self.if_start_orderquery = False if len(self.order_queue.pending) > 0: pass else: raise RuntimeWarning('ORDERHANDLER STILL HAVE UNTRADE ORDERS') self.order_queue.settle() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() try: event.event_queue.task_done() except: pass elif event.event_type is BROKER_EVENT.NEXT_TRADEDAY: """下一个交易日 """ self.if_start_orderquery = True if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event) ) elif event.event_type is MARKET_EVENT.QUERY_ORDER: """query_order和query_deal 需要联动使用 query_order 得到所有的订单列表 query_deal 判断订单状态--> 运行callback函数 实盘涉及到外部订单问题: 及 订单的来源 不完全从QUANTAXIS中发出, 则QA无法记录来源 (标记为外部订单) """ if self.if_start_orderquery: try: # 做一些容错处理 res = [ self.monitor[account].query_orders( account.account_cookie, '' ) for account in list(self.monitor.keys()) ] res = pd.concat(res, axis=0) if len(res) > 0 else None except: time.sleep(1) self.order_status = res if res is not None else self.order_status if len(self.order_status) > 0: QA_SU_save_order(self.order_status) # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 event.event_type = MARKET_EVENT.QUERY_DEAL if event.event_queue.qsize() < 1: time.sleep(random.randint(1, 2)) # 非阻塞 if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event) ) elif event.event_type is MARKET_EVENT.QUERY_DEAL: """order_handler- query_deal 将order_handler订单队列中的订单---和deal中匹配起来 """ if self.if_start_orderquery: res = [ self.monitor[account].query_orders( account.account_cookie, 'filled' ) for account in list(self.monitor.keys()) ] try: res = pd.concat( res, axis=0 ) if len(res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status if len(self.deal_status) > 0: QA_SU_save_deal(self.deal_status) # 检查pending订单, 更新订单状态 try: for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[1 ]: # 此时有成交推送(但可能是多条) res = self.deal_status.loc[order.account_cookie, order.realorder_id] if isinstance(res, pd.Series): order.trade( str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time) ) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade( str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time) ) else: for _, deal in res.iterrows: order.trade( str(deal.trade_id), float(deal.trade_price), int(deal.trade_amount), str(deal.trade_time) ) except Exception as e: print(e) print(self.order_queue.order_list) print(self.deal_status.index) print(self.order_status) # event.event_queue.task_done() # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 if event.event_queue.qsize() < 1: time.sleep(random.randint(2, 5)) event.event_type = MARKET_EVENT.QUERY_ORDER if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event) ) elif event.event_type is MARKET_EVENT.QUERY_POSITION: pass def subscribe(self, account, broker): print('subscribe') self.monitor[account] = broker def unsubscribe(self, account, broker): try: self.monitor.pop(account) except: print('failled to unscribe {}'.format(account.account_cookie)) def _trade(self, order=None, account=None): # 回测通过query_order加快速度,实盘只有query_orders方法 if order is not None and hasattr(self.monitor[account], 'query_order'): res = self.monitor[account].query_order(order.order_id) order.trade(str(res[14]), float(res[6]), int(res[10]), str(res[2])) else: print('orderhandler: trade') res = [ self.monitor[account].query_orders( account.account_cookie, 'filled' ) for account in list(self.monitor.keys()) ] try: res = pd.concat(res, axis=0) if len(res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[1]: # 此时有成交推送(但可能是多条) # res = self.deal_status.loc[order.account_cookie, order.realorder_id] print(res) if isinstance(res, pd.Series): order.trade( str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time) ) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade( str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time) ) else: # print(res) # print(len(res)) for _, deal in res.iterrows: order.trade( str(deal.trade_id), float(deal.trade_price), int(deal.trade_amount), str(deal.trade_time) ) # print('order_handler: finish trade') return True
class QA_OrderHandlerAsync(QA_Worker): """ORDER执行器 异步handler """ def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() self.if_start_orderquery = False self.monitor = {} # 1.1新增 用于监控订单 def run(self, event): if event.event_type is BROKER_EVENT.RECEIVE_ORDER: # 此时的message应该是订单类 """ OrderHandler 收到订单 orderhandler 调控转发给broker broker返回发单结果(成功/失败) orderhandler.order_queue 插入一个订单 执行回调 """ order = event.order order = event.broker.receive_order( QA_Event(event_type=BROKER_EVENT.TRADE, order=event.order, market_data=event.market_data)) # print(threading.current_thread().ident) order = self.order_queue.insert_order(order) if event.callback: event.callback(order) elif event.event_type is BROKER_EVENT.TRADE: # 实盘和本地 同步执行 self._trade() # event.event_queue.task_done() elif event.event_type is BROKER_EVENT.SETTLE: """订单队列的结算: 当队列中的订单都被处理过后: 算可以结算了 """ print('SETTLE ORDERHANDLER') # if len(self.order_queue.untrade) > 0: # self.if_start_orderquery = False # event.event_type = BROKER_EVENT.TRADE # event.event_queue.put( # QA_Task( # worker=self, # engine='ORDER', # event=event # ) # ) if len(self.order_queue.untrade)==0: self._trade() else: self._trade() # print(self.order_queue.untrade) self.order_queue.settle() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() try: event.event_queue.task_done() except: pass elif event.event_type is MARKET_EVENT.QUERY_ORDER: """query_order和query_deal 需要联动使用 query_order 得到所有的订单列表 query_deal 判断订单状态--> 运行callback函数 实盘涉及到外部订单问题: 及 订单的来源 不完全从QUANTAXIS中发出, 则QA无法记录来源 (标记为外部订单) """ if self.if_start_orderquery: # if QA_util_if_tradetime(datetime.datetime.now()): # print(event.broker) # print(event.account_cookie) try: # 做一些容错处理 res = [self.monitor[account].query_orders( account.account_cookie, '') for account in list(self.monitor.keys())] res = pd.concat(res, axis=0) if len( res) > 0 else None except: time.sleep(1) self.order_status = res if res is not None else self.order_status if len(self.order_status) > 0: QA_SU_save_order(self.order_status) # else: # time.sleep(1) # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 # event=event event.event_type = MARKET_EVENT.QUERY_DEAL if event.event_queue.qsize() < 1: time.sleep(random.randint(1, 2)) # event.event_queue.task_done() # 非阻塞 if self.if_start_orderquery: event.event_queue.put( QA_Task( worker=self, engine='ORDER', event=event ) ) # time.sleep(random.randint(2,5)) # print(event.event_type) # print(event2.event_type) # self.run(event) # print(self.order_status) # print('UPDATE ORDERS') elif event.event_type is MARKET_EVENT.QUERY_DEAL: """order_handler- query_deal 将order_handler订单队列中的订单---和deal中匹配起来 """ # if len(self.order_queue.pending) > 0: # for order in self.order_queue.pending: # #self.query # waiting_realorder_id = [ # order.realorder_id for order in self.order_queue.trade_list] # result = event.broker.query_deal # time.sleep(1) if self.if_start_orderquery: res = [self.monitor[account].query_orders( account.account_cookie, 'filled') for account in list(self.monitor.keys())] try: #res=[pd.DataFrame() if not isinstance(item,pd.DataFrame) else item for item in res] res = pd.concat(res, axis=0) if len( res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status if len(self.deal_status) > 0: QA_SU_save_deal(self.deal_status) # print(self.order_status) # 检查pending订单, 更新订单状态 try: for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[1]: # 此时有成交推送(但可能是多条) # res = self.deal_status.loc[order.account_cookie, order.realorder_id] if isinstance(res, pd.Series): order.trade(str(res.trade_id), float(res.trade_price), int( res.trade_amount), str(res.trade_time)) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade(str(res.trade_id), float(res.trade_price), int( res.trade_amount), str(res.trade_time)) else: print(res) print(len(res)) for _, deal in res.iterrows: order.trade(str(deal.trade_id), float(deal.trade_price), int( deal.trade_amount), str(deal.trade_time)) except Exception as e: print(e) print(self.order_queue.order_list) print(self.deal_status.index) print(self.order_status) # event.event_queue.task_done() # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 if event.event_queue.qsize() < 1: time.sleep(random.randint(2, 5)) event.event_type = MARKET_EVENT.QUERY_ORDER if self.if_start_orderquery: event.event_queue.put( QA_Task( worker=self, engine='ORDER', event=event ) ) # self.run(event) # self.run(event) elif event.event_type is MARKET_EVENT.QUERY_POSITION: pass def subscribe(self, account, broker): print('subscribe') self.monitor[account] = broker def unsubscribe(self, account, broker): try: self.monitor.pop(account) except: print('failled to unscribe {}'.format(account.account_cookie)) async def _trade(self): res = [self.monitor[account].query_orders( account.account_cookie, 'filled') for account in list(self.monitor.keys())] try: #res=[pd.DataFrame() if not isinstance(item,pd.DataFrame) else item for item in res] res = pd.concat(res, axis=0) if len( res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[1]: # 此时有成交推送(但可能是多条) # res = self.deal_status.loc[order.account_cookie, order.realorder_id] if isinstance(res, pd.Series): await order.trade(str(res.trade_id), float(res.trade_price), int( res.trade_amount), str(res.trade_time)) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade(str(res.trade_id), float(res.trade_price), int( res.trade_amount), str(res.trade_time)) else: print(res) print(len(res)) for _, deal in res.iterrows: order.trade(str(deal.trade_id), float(deal.trade_price), int( deal.trade_amount), str(deal.trade_time)) return True
class QA_OrderHandlerAsync(QA_Worker): """ORDER执行器 异步handler """ def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() self.if_start_orderquery = False self.monitor = {} # 1.1新增 用于监控订单 def run(self, event): if event.event_type is BROKER_EVENT.RECEIVE_ORDER: # 此时的message应该是订单类 """ OrderHandler 收到订单 orderhandler 调控转发给broker broker返回发单结果(成功/失败) orderhandler.order_queue 插入一个订单 执行回调 """ order = event.order order = event.broker.receive_order( QA_Event(event_type=BROKER_EVENT.TRADE, order=event.order, market_data=event.market_data)) # print(threading.current_thread().ident) order = self.order_queue.insert_order(order) if event.callback: event.callback(order) elif event.event_type is BROKER_EVENT.TRADE: # 实盘和本地 同步执行 self._trade() # event.event_queue.task_done() elif event.event_type is BROKER_EVENT.SETTLE: """订单队列的结算: 当队列中的订单都被处理过后: 算可以结算了 """ print('SETTLE ORDERHANDLER') # if len(self.order_queue.untrade) > 0: # self.if_start_orderquery = False # event.event_type = BROKER_EVENT.TRADE # event.event_queue.put( # QA_Task( # worker=self, # engine='ORDER', # event=event # ) # ) if len(self.order_queue.untrade) == 0: self._trade() else: self._trade() # print(self.order_queue.untrade) self.order_queue.settle() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() try: event.event_queue.task_done() except: pass elif event.event_type is MARKET_EVENT.QUERY_ORDER: """query_order和query_deal 需要联动使用 query_order 得到所有的订单列表 query_deal 判断订单状态--> 运行callback函数 实盘涉及到外部订单问题: 及 订单的来源 不完全从QUANTAXIS中发出, 则QA无法记录来源 (标记为外部订单) """ if self.if_start_orderquery: # if QA_util_if_tradetime(datetime.datetime.now()): # print(event.broker) # print(event.account_cookie) try: # 做一些容错处理 res = [ self.monitor[account].query_orders( account.account_cookie, '') for account in list(self.monitor.keys()) ] res = pd.concat(res, axis=0) if len(res) > 0 else None except: time.sleep(1) self.order_status = res if res is not None else self.order_status if len(self.order_status) > 0: QA_SU_save_order(self.order_status) # else: # time.sleep(1) # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 # event=event event.event_type = MARKET_EVENT.QUERY_DEAL if event.event_queue.qsize() < 1: time.sleep(random.randint(1, 2)) # event.event_queue.task_done() # 非阻塞 if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event)) # time.sleep(random.randint(2,5)) # print(event.event_type) # print(event2.event_type) # self.run(event) # print(self.order_status) # print('UPDATE ORDERS') elif event.event_type is MARKET_EVENT.QUERY_DEAL: """order_handler- query_deal 将order_handler订单队列中的订单---和deal中匹配起来 """ # if len(self.order_queue.pending) > 0: # for order in self.order_queue.pending: # #self.query # waiting_realorder_id = [ # order.realorder_id for order in self.order_queue.trade_list] # result = event.broker.query_deal # time.sleep(1) if self.if_start_orderquery: res = [ self.monitor[account].query_orders(account.account_cookie, 'filled') for account in list(self.monitor.keys()) ] try: #res=[pd.DataFrame() if not isinstance(item,pd.DataFrame) else item for item in res] res = pd.concat( res, axis=0) if len(res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status if len(self.deal_status) > 0: QA_SU_save_deal(self.deal_status) # print(self.order_status) # 检查pending订单, 更新订单状态 try: for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[ 1]: # 此时有成交推送(但可能是多条) # res = self.deal_status.loc[order.account_cookie, order.realorder_id] if isinstance(res, pd.Series): order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) else: print(res) print(len(res)) for _, deal in res.iterrows: order.trade(str(deal.trade_id), float(deal.trade_price), int(deal.trade_amount), str(deal.trade_time)) except Exception as e: print(e) print(self.order_queue.order_list) print(self.deal_status.index) print(self.order_status) # event.event_queue.task_done() # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 if event.event_queue.qsize() < 1: time.sleep(random.randint(2, 5)) event.event_type = MARKET_EVENT.QUERY_ORDER if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event)) # self.run(event) # self.run(event) elif event.event_type is MARKET_EVENT.QUERY_POSITION: pass def subscribe(self, account, broker): print('subscribe') self.monitor[account] = broker def unsubscribe(self, account, broker): try: self.monitor.pop(account) except: print('failled to unscribe {}'.format(account.account_cookie)) async def _trade(self): res = [ self.monitor[account].query_orders(account.account_cookie, 'filled') for account in list(self.monitor.keys()) ] try: #res=[pd.DataFrame() if not isinstance(item,pd.DataFrame) else item for item in res] res = pd.concat(res, axis=0) if len(res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[1]: # 此时有成交推送(但可能是多条) # res = self.deal_status.loc[order.account_cookie, order.realorder_id] if isinstance(res, pd.Series): await order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) else: print(res) print(len(res)) for _, deal in res.iterrows: order.trade(str(deal.trade_id), float(deal.trade_price), int(deal.trade_amount), str(deal.trade_time)) return True
class QA_Account(QA_Worker): """[QA_Account] 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 """ def __init__(self, strategy_name=None, user_cookie=None, market_type=MARKET_TYPE.STOCK_CN, frequence=FREQUENCE.DAY, broker=BROKER_TYPE.BACKETEST, portfolio_cookie=None, account_cookie=None, sell_available={}, init_assets=None, cash=None, history=None, commission_coeff=0.00025, tax_coeff=0.0015, margin_level=False, allow_t0=False, allow_sellopen=False): """ :param strategy_name: :param user_cookie: :param market_type: :param frequence: :param broker: :param portfolio_cookie: :param account_cookie: :param sell_available: :param init_assets: 初始资产 默认 1000000 元 (100万) :param cash: 可用现金 默认 是 初始资产 list 类型 :param history: :param commission_coeff: 交易佣金 :默认 万2.5 float 类型 :param tax_coeff: 印花税 :默认 千1.5 float 类型 :param margin_level: :param allow_t0: :param allow_sellopen: """ super().__init__() self._history_headers = [ 'datetime', 'code', 'price', 'amount', 'order_id', 'trade_id', 'account_cookie', 'commission', 'tax' ] ######################################################################## # 信息类: self.strategy_name = strategy_name self.user_cookie = user_cookie self.market_type = market_type self.portfolio_cookie = portfolio_cookie self.account_cookie = QA_util_random_with_topic( 'Acc') if account_cookie is None else account_cookie self.broker = broker self.frequence = frequence self.market_data = None self._currenttime = None self.commission_coeff = commission_coeff self.tax_coeff = tax_coeff ######################################################################## # 资产类 self.orders = QA_OrderQueue() # 历史委托单 self.init_assets = 1000000 if init_assets is None else init_assets self.cash = [self.init_assets] if cash is None else cash self.cash_available = self.cash[-1] # 可用资金 self.sell_available = sell_available self.history = [] if history is None else history self.time_index = [] ######################################################################## # 规则类 # 两个规则 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level def __repr__(self): return '< QA_Account {}>'.format(self.account_cookie) @property def message(self): 'the standard message which can be transef' 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': self._currenttime, 'allow_sellopen': self.allow_sellopen, 'allow_t0': self.allow_t0, 'margin_level': self.margin_level, 'init_assets': self.init_assets, 'cash': self.cash, 'history': self.history, 'trade_index': self.time_index, 'running_time': datetime.datetime.now() } @property def code(self): """ 该账户曾交易代码 用set 去重 """ return list(set([item[1] for item in self.history])) @property def start_date(self): return str(self.time_index[0])[0:10] @property def end_date(self): return str(self.time_index[-1])[0:10] @property def history_table(self): '交易历史的table' return pd.DataFrame(data=self.history, 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: str(x)[0:10])).assign( account_cookie=self.account_cookie) return _cash.set_index(['datetime', 'account_cookie'], drop=False).sort_index() @property def hold(self): '持仓' return pd.DataFrame(data=self.history, columns=self._history_headers).groupby( 'code').amount.sum().sort_index() @property def order_table(self): """return order trade list""" return self.orders.trade_list @property def trade(self): '每次交易的pivot表' return self.history_table.pivot_table( index=['datetime', 'account_cookie'], columns='code', values='amount').fillna(0).sort_index() @property def daily_cash(self): '每日交易结算时的现金表' return self.cash_table.drop_duplicates(subset='date', keep='last').sort_index() @property def daily_hold(self): '每日交易结算时的持仓表' data = self.trade.cumsum() data = data.assign(account_cookie=self.account_cookie).assign( date=data.index.levels[0]) data.date = data.date.apply(lambda x: str(x)[0:10]) return data.set_index(['date', 'account_cookie'], drop=False).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 reset_assets(self, init_assets=None): 'reset_history/cash/' self.sell_available = {} self.history = [] self.init_assets = init_assets self.cash = [self.init_assets] self.cash_available = self.cash[-1] # 在途资金 def receive_deal(self, message): """ 用于更新账户 update history and cash :param message: :return: """ if message['header']['status'] is TRADE_STATUS.SUCCESS: trade_amount = float( float(message['body']['order']['price']) * float(message['body']['order']['amount']) * message['body']['order']['towards'] + float(message['body']['fee']['commission']) + float(message['body']['fee']['tax'])) if self.cash[-1] > trade_amount: self.time_index.append( str(message['body']['order']['datetime'])) self.history.append([ str(message['body']['order']['datetime']), str(message['body']['order']['code']), float(message['body']['order']['price']), int(message['body']['order']['towards']) * float(message['body']['order']['amount']), str(message['header']['order_id']), str(message['header']['trade_id']), str(self.account_cookie), float(message['body']['fee']['commission']), float(message['body']['fee']['tax']) ]) self.cash.append(self.cash[-1] - trade_amount) self.cash_available = self.cash[-1] # 资金立刻结转 else: print(message) print(self.cash[-1]) self.cash_available = self.cash[-1] return self.message def send_order(self, code=None, amount=None, time=None, towards=None, price=None, money=None, order_model=None, amount_model=None): """ 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: :param towards: :param price: :param money: :param order_model: :param amount_model: :return: """ 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 flag = False date = str(time)[0:10] if len(str(time)) == 19 else str(time) time = str(time) if len(str(time)) == 19 else '{} 09:31:00'.format( str(time)[0:10]) # BY_MONEY :: amount --钱 如10000元 因此 by_money里面 需要指定价格,来计算实际的股票数 # by_amount :: amount --股数 如10000股 amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (price * (1 + self.commission_coeff))) money = amount * price * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money # amount_model = AMOUNT_MODEL.BY_AMOUNT if int(towards) > 0: # 是买入的情况(包括买入.买开.买平) if self.cash_available >= money: self.cash_available -= money if self.market_type is MARKET_TYPE.STOCK_CN: # 如果是股票 买入的时候有100股的最小限制 amount = int(amount / 100) * 100 flag = True else: print('可用资金不足') elif int(towards) < 0: if self.sell_available.get(code, 0) >= amount: self.sell_available[code] -= amount flag = True elif self.allow_sellopen: if self.cash_available > money: # 卖空的市值小于现金 flag = True else: print('资金股份不足/不允许卖空开仓') 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) # init self.orders.insert_order(_order) # order状态存储 return _order else: print('ERROR : amount=0') return False def settle(self): '同步可用资金/可卖股票' self.sell_available = self.hold def on_bar(self, event): 'while updating the market data' print(event.market_data) def on_tick(self, event): 'on tick event' 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.history = message['history'] self.cash = message['cash'] self.time_index = message['trade_index'] self.init_assets = message['init_assets'] return self @property def table(self): """ 打印出account的内容 """ return pd.DataFrame([ self.message, ]).set_index('account_cookie', drop=False).T def run(self, event): '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[-1] 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 change_cash(self, money): """ 外部操作|高危| """ res = self.cash[-1] + money if res >= 0: # 高危操作 self.cash[-1] = res def get_orders(self, if_today=True): """ 返回当日委托/历史委托 """ # self.orders.get_orders. self.orders.queue pass
def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event()
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.0015, margin_level=False, allow_t0=False, allow_sellopen=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: 保证金比例 默认False :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :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: 交易历史 """ 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', 'trade_id', 'account_cookie', 'commission', 'tax' ] ######################################################################## # 信息类: 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 = [] ######################################################################## # 规则类 # 两个规则 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level
class QA_OrderHandler(QA_Worker): """ORDER执行器 ORDEHANDLDER 归属于MARKET前置 仅负责一个无状态的执行层 ORDER执行器的作用是因为 在实盘中 当一个订单发送出去的时候,市场不会返回一个更新的订单类回来 大部分时间都依赖子线程主动查询 或者是一个市场信息来进行判断 ORDER_Handler的作用就是根据信息更新Order 用于接受订单 发送给相应的marker_broker 再根据返回的信息 进行更新 可用的market_broker: 1.回测盘 2.实时模拟盘 3.实盘 ORDERHANDLER 持久化问题: 设定机制: 2秒查询1次 持久化: 2秒一次 2018-07-29 # 重新设置ORDERHADLER的运行模式: -- 常规检查 5秒一次 -- 如果出现订单 则2-3秒 对账户轮询(直到出现订单成交/撤单为止) """ def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() self.if_start_orderquery = False self.monitor = {} # 1.1新增 用于监控订单 def run(self, event): if event.event_type is BROKER_EVENT.RECEIVE_ORDER: # 此时的message应该是订单类 """ OrderHandler 收到订单 orderhandler 调控转发给broker broker返回发单结果(成功/失败) orderhandler.order_queue 插入一个订单 执行回调 """ order = event.order order = event.broker.receive_order( QA_Event(event_type=BROKER_EVENT.TRADE, order=event.order, market_data=event.market_data)) order = self.order_queue.insert_order(order) if event.callback: event.callback(order) elif event.event_type is BROKER_EVENT.TRADE: # 实盘和本地 同步执行 self._trade() # event.event_queue.task_done() elif event.event_type is BROKER_EVENT.SETTLE: """订单队列的结算: 当队列中的订单都被处理过后: 算可以结算了 """ print('SETTLE ORDERHANDLER') self.if_start_orderquery = False if len(self.order_queue.pending) > 0: pass else: raise RuntimeWarning('ORDERHANDLER STILL HAVE UNTRADE ORDERS') self.order_queue.settle() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() try: event.event_queue.task_done() except: pass elif event.event_type is BROKER_EVENT.NEXT_TRADEDAY: """下一个交易日 """ self.if_start_orderquery = True if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event)) elif event.event_type is MARKET_EVENT.QUERY_ORDER: """query_order和query_deal 需要联动使用 query_order 得到所有的订单列表 query_deal 判断订单状态--> 运行callback函数 实盘涉及到外部订单问题: 及 订单的来源 不完全从QUANTAXIS中发出, 则QA无法记录来源 (标记为外部订单) """ if self.if_start_orderquery: try: # 做一些容错处理 res = [ self.monitor[account].query_orders( account.account_cookie, '') for account in list(self.monitor.keys()) ] res = pd.concat(res, axis=0) if len(res) > 0 else None except: time.sleep(1) self.order_status = res if res is not None else self.order_status if len(self.order_status) > 0: QA_SU_save_order(self.order_status) # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 event.event_type = MARKET_EVENT.QUERY_DEAL if event.event_queue.qsize() < 1: time.sleep(random.randint(1, 2)) # 非阻塞 if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event)) elif event.event_type is MARKET_EVENT.QUERY_DEAL: """order_handler- query_deal 将order_handler订单队列中的订单---和deal中匹配起来 """ if self.if_start_orderquery: res = [ self.monitor[account].query_orders(account.account_cookie, 'filled') for account in list(self.monitor.keys()) ] try: res = pd.concat( res, axis=0) if len(res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status if len(self.deal_status) > 0: QA_SU_save_deal(self.deal_status) # 检查pending订单, 更新订单状态 try: for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[ 1]: # 此时有成交推送(但可能是多条) res = self.deal_status.loc[order.account_cookie, order.realorder_id] if isinstance(res, pd.Series): order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) else: for _, deal in res.iterrows: order.trade(str(deal.trade_id), float(deal.trade_price), int(deal.trade_amount), str(deal.trade_time)) except Exception as e: print(e) print(self.order_queue.order_list) print(self.deal_status.index) print(self.order_status) # event.event_queue.task_done() # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 if event.event_queue.qsize() < 1: time.sleep(random.randint(2, 5)) event.event_type = MARKET_EVENT.QUERY_ORDER if self.if_start_orderquery: event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event)) elif event.event_type is MARKET_EVENT.QUERY_POSITION: pass def subscribe(self, account, broker): print('subscribe') self.monitor[account] = broker def unsubscribe(self, account, broker): try: self.monitor.pop(account) except: print('failled to unscribe {}'.format(account.account_cookie)) def _trade(self, order=None, account=None): # 回测通过query_order加快速度,实盘只有query_orders方法 if order is not None and hasattr(self.monitor[account], 'query_order'): res = self.monitor[account].query_order(order.order_id) order.trade(str(res[14]), float(res[6]), int(res[10]), str(res[2])) else: print('orderhandler: trade') res = [ self.monitor[account].query_orders(account.account_cookie, 'filled') for account in list(self.monitor.keys()) ] try: res = pd.concat(res, axis=0) if len(res) > 0 else pd.DataFrame() except: res = None self.deal_status = res if res is not None else self.deal_status for order in self.order_queue.pending: if len(self.deal_status) > 0: if order.realorder_id in self.deal_status.index.levels[1]: # 此时有成交推送(但可能是多条) # res = self.deal_status.loc[order.account_cookie, order.realorder_id] print(res) if isinstance(res, pd.Series): order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) elif isinstance(res, pd.DataFrame): if len(res) == 0: pass elif len(res) == 1: res = res.iloc[0] order.trade(str(res.trade_id), float(res.trade_price), int(res.trade_amount), str(res.trade_time)) else: # print(res) # print(len(res)) for _, deal in res.iterrows: order.trade(str(deal.trade_id), float(deal.trade_price), int(deal.trade_amount), str(deal.trade_time)) # print('order_handler: finish trade') return True
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=False, allow_t0=False, allow_sellopen=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: 保证金比例 默认False :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :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' ] ######################################################################## # 信息类: 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 = [] ######################################################################## # 规则类 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) # 期货: allow_t0 True allow_sellopen True # self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.margin_level = margin_level # 保证金比例 """期货的多开/空开 ==> 资金冻结进保证金 frozen 对应平仓的时候, 释放保证金 1. frozen 是一个dict : {[code]:queue} key是标的 value是对应的交易queue """ self.frozen = {} # 冻结资金(保证金)
class QA_OrderHandler(QA_Worker): """ORDER执行器 ORDEHANDLDER 归属于MARKET前置 仅负责一个无状态的执行层 ORDER执行器的作用是因为 在实盘中 当一个订单发送出去的时候,市场不会返回一个更新的订单类回来 大部分时间都依赖子线程主动查询 或者是一个市场信息来进行判断 ORDER_Handler的作用就是根据信息更新Order 用于接受订单 发送给相应的marker_broker 再根据返回的信息 进行更新 可用的market_broker: 1.回测盘 2.实时模拟盘 3.实盘 ORDERHANDLER 持久化问题: 设定机制: 2秒查询1次 持久化: 2秒一次 2018-07-29 # 重新设置ORDERHADLER的运行模式: -- 常规检查 5秒一次 -- 如果出现订单 则2-3秒 对账户轮询(直到出现订单成交/撤单为止) """ def __init__(self, *args, **kwargs): super().__init__() self.order_queue = QA_OrderQueue() self.type = EVENT_TYPE.MARKET_EVENT self.event = QA_Event() self.order_status = pd.DataFrame() self.deal_status = pd.DataFrame() self.if_start_orderquery = False def run(self, event): if event.event_type is BROKER_EVENT.RECEIVE_ORDER: # 此时的message应该是订单类 """ OrderHandler 收到订单 orderhandler 调控转发给broker broker返回发单结果(成功/失败) orderhandler.order_queue 插入一个订单 执行回调 """ order = event.order # print(event.broker) order = event.broker.receive_order( QA_Event(event_type=BROKER_EVENT.TRADE, order=event.order)) # print(threading.current_thread().ident) order = self.order_queue.insert_order(order) if event.callback: event.callback(order) elif event.event_type is BROKER_EVENT.TRADE: # 实盘和本地 同步执行 res = [] for order in self.order_queue.pending: result = event.broker.query_orders(order.account_cookie, order.realorder_id) self.order_queue.set_status(order.order_id, result['header']['status']) if order.callback: order.callback(result) res.append(result) event.res = res return event elif event.event_type is BROKER_EVENT.SETTLE: self.order_queue.settle() elif event.event_type is MARKET_EVENT.QUERY_ORDER: """query_order和query_deal 需要联动使用 query_order 得到所有的订单列表 query_deal 判断订单状态--> 运行callback函数 实盘涉及到外部订单问题: 及 订单的来源 不完全从QUANTAXIS中发出, 则QA无法记录来源 (标记为外部订单) """ if self.if_start_orderquery: # if QA_util_if_tradetime(datetime.datetime.now()): # print(event.broker) # print(event.account_cookie) try: # 做一些容错处理 res = [ event.broker[i].query_orders(event.account_cookie[i], '') for i in range(len(event.account_cookie)) ] res = pd.concat(res, axis=0) if len(res) > 0 else None except: time.sleep(1) self.order_status = res if res is not None else self.order_status if len(self.order_status) > 0: QA_SU_save_order(self.order_status) # else: # time.sleep(1) # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 # event=event event.event_type = MARKET_EVENT.QUERY_DEAL if event.event_queue.qsize() < 1: time.sleep(random.randint(1, 2)) # 非阻塞 event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event)) # time.sleep(random.randint(2,5)) # print(event.event_type) # print(event2.event_type) # self.run(event) # print(self.order_status) # print('UPDATE ORDERS') elif event.event_type is MARKET_EVENT.QUERY_DEAL: """order_handler- query_deal 将order_handler订单队列中的订单---和deal中匹配起来 """ # if len(self.order_queue.pending) > 0: # for order in self.order_queue.pending: # #self.query # waiting_realorder_id = [ # order.realorder_id for order in self.order_queue.trade_list] # result = event.broker.query_deal # time.sleep(1) if self.if_start_orderquery: self.deal_status = [ event.broker[i].query_orders(event.account_cookie[i], 'filled') for i in range(len(event.account_cookie)) ] # print(self.order_status) self.deal_status = pd.concat( self.deal_status, axis=0) if len(self.deal_status) > 0 else pd.DataFrame() if len(self.deal_status) > 0: QA_SU_save_deal(self.deal_status) # print(self.order_status) # 这里加入随机的睡眠时间 以免被发现固定的刷新请求 if event.event_queue.qsize() < 1: time.sleep(random.randint(2, 5)) event.event_type = MARKET_EVENT.QUERY_ORDER event.event_queue.put( QA_Task(worker=self, engine='ORDER', event=event)) # self.run(event) # self.run(event) elif event.event_type is MARKET_EVENT.QUERY_POSITION: pass def query_order(self, order_id): pass