class QIFI_Account(): def __init__(self, username, password, model="SIM", broker_name="QAPaperTrading", trade_host=mongo_ip, init_cash=1000000, taskid=str(uuid.uuid4())): """Initial QIFI Account是一个基于 DIFF/ QIFI/ QAAccount后的一个实盘适用的Account基类 1. 兼容多持仓组合 2. 动态计算权益 使用 model = SIM/ REAL来切换 qifiaccount 不去区分你的持仓是股票还是期货, 因此你可以实现跨市场的交易持仓管理 """ self.user_id = username self.username = username self.password = password self.source_id = "QIFI_Account" # 识别号 self.market_preset = MARKET_PRESET() # 指的是 Account所属的账户编组(实时的时候的账户观察组) self.portfolio = "QAPaperTrade" self.model = model self.broker_name = broker_name # 所属期货公司/ 模拟的组 self.investor_name = "" # 账户所属人(实盘的开户人姓名) self.bank_password = "" self.capital_password = "" self.wsuri = "" self.bank_id = "QASIM" self.bankname = "QASIMBank" self.trade_host = trade_host if model == 'BACKTEST': self.db = pymongo.MongoClient(trade_host).quantaxis else: self.db = pymongo.MongoClient(trade_host).QAREALTIME self.pub_host = "" self.trade_host = "" self.last_updatetime = "" self.status = 200 self._trading_day = "" self.init_cash = init_cash self.pre_balance = 0 self.datetime = "" self.static_balance = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.close_profit = 0 self.premium = 0 # 本交易日内交纳的期权权利金 self.event_id = 0 self.taskid = taskid self.money = 0 # QIFI 协议 self.transfers = {} self.schedule = {} self.banks = {} self.frozen = {} self.event = {} self.positions = {} self.trades = {} self.orders = {} def initial(self): self.reload() if self.pre_balance == 0 and self.balance == 0 and self.model != "REAL": self.log('Create new Account') self.create_simaccount() self.sync() @property def trading_day(self): if self.model == "BACKTEST": return str(self.datetime)[0:10] else: return self._trading_day def reload(self): if self.model.upper() in ['REAL', 'SIM']: message = self.db.account.find_one({ 'account_cookie': self.user_id, 'password': self.password }) time = datetime.datetime.now() # resume/settle if time.hour <= 15: self._trading_day = time.date() else: if time.weekday() in [0, 1, 2, 3]: self._trading_day = time.date() + datetime.timedelta( days=1) elif time.weekday() in [4, 5, 6]: self._trading_day = time.date() + datetime.timedelta( days=(7 - time.weekday())) if message is not None: accpart = message.get('accounts') self.money = message.get('money') self.source_id = message.get('sourceid') self.pre_balance = accpart.get('pre_balance') self.deposit = accpart.get('deposit') self.withdraw = accpart.get('withdraw') self.withdrawQuota = accpart.get('WithdrawQuota') self.close_profit = accpart.get('close_profit') self.static_balance = accpart.get('static_balance') self.event = message.get('event') self.trades = message.get('trades') self.transfers = message.get('transfers') self.orders = message.get('orders') self.taskid = message.get('taskid', str(uuid.uuid4())) positions = message.get('positions') for position in positions.values(): self.positions[position.get( 'instrument_id')] = QA_Position().loadfrommessage( position) for order in self.open_orders: self.log('try to deal {}'.format(order)) self.make_deal(order) self.banks = message.get('banks') self.status = message.get('status') self.wsuri = message.get('wsuri') self.on_reload() if message.get('trading_day', '') == str(self._trading_day): # reload pass else: # settle self.settle() def create_fromQIFI(self, message): pass def sync(self): self.on_sync() if self.model == "BACKTEST": ## 数据库: quantaxis.history self.db.history.update( { 'account_cookie': self.user_id, 'trading_day': self.trading_day }, {'$set': self.message}, upsert=True) else: ## 数据库: QAREALTIME.account self.db.account.update( { 'account_cookie': self.user_id, 'password': self.password }, {'$set': self.message}, upsert=True) self.db.hisaccount.insert_one({ 'updatetime': self.dtstr, 'account_cookie': self.user_id, 'accounts': self.account_msg }) def settle(self): self.log('settle') if self.model == "BACKTEST": ## 数据库: quantaxis.history self.db.history.update( { 'account_cookie': self.user_id, 'trading_day': self.trading_day }, {'$set': self.message}, upsert=True) else: self.db.hisaccount.update_one( { 'account_cookie': self.user_id, 'trading_day': self.trading_day }, {'$set': self.message}, upsert=True) self.pre_balance += (self.deposit - self.withdraw + self.close_profit) self.static_balance = self.pre_balance self.close_profit = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.premium = 0 self.money += self.frozen_margin self.orders = {} self.frozen = {} self.trades = {} self.transfers = {} self.event = {} self.event_id = 0 for item in self.positions.values(): item.settle() # sell first >> second buy ==> for make sure have enough cash buy_order_sche = [] for order in self.schedule.values(): if order['towards'] > 0: # buy order buy_order_sche.append(order) else: self.send_order(order['code'], order['amount'], order['price'], order['towards'], order['order_id']) for order in buy_order_sche: self.send_order(order['code'], order['amount'], order['price'], order['towards'], order['order_id']) self.schedule = {} def on_sync(self): pass def on_reload(self): pass @property def dtstr(self): return str(datetime.datetime.now()).replace('.', '_') def ask_deposit(self, money): self.deposit += money self.money += money self.transfers[str(self.event_id)] = { "datetime": 433241234123, # // 转账时间, epoch nano "currency": "CNY", # 币种 "amount": money, # 涉及金额 "error_id": 0, # 转账结果代码 "error_msg": "成功", # 转账结果代码 } self.event[self.dtstr] = "转账成功 {}".format(money) def ask_withdraw(self, money): if self.withdrawQuota > money: self.withdrawQuota -= money self.withdraw += money self.transfers[str(self.event_id)] = { "datetime": 433241234123, # // 转账时间, epoch nano "currency": "CNY", # 币种 "amount": -money, # 涉及金额 "error_id": 0, # 转账结果代码 "error_msg": "成功", # 转账结果代码 } self.event[self.dtstr] = "转账成功 {}".format(-money) else: self.event[self.dtstr] = "转账失败: 余额不足 left {} ask {}".format( self.withdrawQuota, money) def create_simaccount(self): self._trading_day = str(datetime.date.today()) self.wsuri = "ws://www.yutiansut.com:7988" self.pre_balance = 0 self.static_balance = 0 self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.user_id = self.user_id self.password = self.password self.money = 0 self.close_profit = 0 self.event_id = 0 self.transfers = {} self.banks = {} self.event = {} self.positions = {} self.trades = {} self.orders = {} self.banks[str(self.bank_id)] = { "id": self.bank_id, "name": self.bankname, "bank_account": "", "fetch_amount": 0.0, "qry_count": 0 } self.ask_deposit(self.init_cash) def create_backtestaccount(self): """ 生成一个回测的账户 回测账户的核心事件轴是数据的datetime, 基于数据的datetime来进行账户的更新 """ self._trading_day = "" self.pre_balance = self.init_cash self.static_balance = self.init_cash self.deposit = 0 # 入金 self.withdraw = 0 # 出金 self.withdrawQuota = 0 # 可取金额 self.user_id = self.user_id self.password = self.password self.money = self.init_cash self.close_profit = 0 self.event_id = 0 self.transfers = {} self.banks = {} self.event = {} self.positions = {} self.trades = {} self.orders = {} self.banks[str(self.bank_id)] = { "id": self.bank_id, "name": self.bankname, "bank_account": "", "fetch_amount": 0.0, "qry_count": 0 } # self.ask_deposit(self.init_cash) def add_position(self, position): if position.instrument_id not in self.positions.keys(): self.positions[position.instrument_id] = position return 0 else: return 1 def drop_position(self, position): pass def log(self, message): print(message) #self.event[self.dtstr] = message @property def open_orders(self): return [ item for item in self.orders.values() if item['volume_left'] > 0 ] @property def message(self): return { # // 账户号(兼容QUANTAXIS QAAccount)// 实盘的时候是 账户id "account_cookie": self.user_id, "password": self.password, "databaseip": self.trade_host, "model": self.model, "ping_gap": 5, "portfolio": self.portfolio, "broker_name": self.broker_name, # // 接入商名称 "capital_password": self.capital_password, # // 资金密码 (实盘用) "bank_password": self.bank_password, # // 银行密码(实盘用) "bankid": self.bank_id, # // 银行id "investor_name": self.investor_name, # // 开户人名称 "money": self.money, # // 当前可用现金 "pub_host": self.pub_host, "trade_host": self.trade_host, "taskid": self.taskid, "sourceid": self.source_id, "updatetime": str(self.last_updatetime), "wsuri": self.wsuri, "bankname": self.bankname, "trading_day": str(self.trading_day), "status": self.status, "accounts": self.account_msg, "trades": self.trades, "positions": self.position_msg, "orders": self.orders, "event": self.event, "transfers": self.transfers, "banks": self.banks, "frozen": self.frozen, "settlement": {}, } @property def account_msg(self): return { "user_id": self.user_id, "currency": "CNY", "pre_balance": self.pre_balance, "deposit": self.deposit, "withdraw": self.withdraw, "WithdrawQuota": self.withdrawQuota, "close_profit": self.close_profit, "commission": self.commission, "premium": self.premium, "static_balance": self.static_balance, "position_profit": self.position_profit, "float_profit": self.float_profit, "balance": self.balance, "margin": self.margin, "frozen_margin": self.frozen_margin, "frozen_commission": 0.0, "frozen_premium": 0.0, "available": self.available, "risk_ratio": 1 - self.available / self.balance } @property def position_msg(self): return dict( zip(self.positions.keys(), [item.message for item in self.positions.values()])) @property def position_profit(self): return sum( [position.position_profit for position in self.positions.values()]) @property def float_profit(self): return sum( [position.float_profit for position in self.positions.values()]) @property def frozen_margin(self): return sum([item.get('money') for item in self.frozen.values()]) def transform_dt(self, times): if isinstance(times, str): tradedt = datetime.datetime.strptime( times, '%Y-%m-%d %H:%M:%S') if len( times) == 19 else datetime.datetime.strptime( times.replace('_', '.'), '%Y-%m-%d %H:%M:%S.%f') return tradedt.timestamp() * 1000000000 elif isinstance(times, datetime.datetime): return tradedt.timestamp() * 1000000000 # 惰性计算 @property def available(self): return self.money @property def margin(self): """保证金 """ return sum([position.margin for position in self.positions.values()]) @property def commission(self): """本交易日内交纳的手续费 """ return sum( [position.commission for position in self.positions.values()]) @property def balance(self): """动态权益 Arguments: self {[type]} -- [description] """ return self.static_balance + self.deposit - self.withdraw + self.float_profit + self.close_profit def order_check(self, code: str, amount: int, price: float, towards: int, order_id: str) -> bool: """ order_check是账户自身的逻辑, 你可以重写这个代码 Attention: 需要注意的是 如果你修改了此部分代码 请注意如果你做了对于账户的资金的预操作请在结束的时候恢复 :::如: 下单失败-> 请恢复账户的资金和仓位 --> return Bool """ res = False qapos = self.get_position(code) self.log(qapos.curpos) self.log(qapos.close_available) if towards == ORDER_DIRECTION.BUY_CLOSE: # self.log("buyclose") # self.log(self.volume_short - self.volume_short_frozen) # self.log(amount) if (qapos.volume_short - qapos.volume_short_frozen) >= amount: # check qapos.volume_short_frozen_today += amount #qapos.volume_short_today -= amount res = True else: self.log("BUYCLOSE 仓位不足") elif towards == ORDER_DIRECTION.BUY_CLOSETODAY: if (qapos.volume_short_today - qapos.volume_short_frozen_today) >= amount: qapos.volume_short_frozen_today += amount #qapos.volume_short_today -= amount res = True else: self.log("BUYCLOSETODAY 今日仓位不足") elif towards in [ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL]: # self.log("sellclose") # self.log(self.volume_long - self.volume_long_frozen) # self.log(amount) if (qapos.volume_long - qapos.volume_long_frozen) >= amount: qapos.volume_long_frozen_today += amount #qapos.volume_long_today -= amount res = True else: self.log("SELL CLOSE 仓位不足") elif towards == ORDER_DIRECTION.SELL_CLOSETODAY: if (qapos.volume_long_today - qapos.volume_long_frozen_today) >= amount: # self.log("sellclosetoday") # self.log(self.volume_long_today - self.volume_long_frozen) # self.log(amount) qapos.volume_long_frozen_today += amount #qapos.volume_long_today -= amount return True else: self.log("SELLCLOSETODAY 今日仓位不足") elif towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN, ORDER_DIRECTION.BUY ]: """ 冻结的保证金 """ coeff = float(price) * float( self.market_preset.get_code(code).get( "unit_table", 1)) * float( self.market_preset.get_code(code).get( "buy_frozen_coeff", 1)) moneyneed = coeff * amount if self.available > moneyneed: self.money -= moneyneed self.frozen[order_id] = { 'amount': amount, 'coeff': coeff, 'money': moneyneed } res = True else: self.log("开仓保证金不足 TOWARDS{} Need{} HAVE{}".format( towards, moneyneed, self.available)) return res def send_order(self, code: str, amount: float, price: float, towards: int, order_id: str = '', datetime: str = ''): order_id = str(uuid.uuid4()) if order_id == '' else order_id if self.order_check(code, amount, price, towards, order_id): self.log("order check success") direction, offset = parse_orderdirection(towards) self.event_id += 1 order = { "account_cookie": self.user_id, "user_id": self.user_id, "instrument_id": code, "towards": int(towards), "exchange_id": self.market_preset.get_exchange(code), "order_time": self.dtstr, "volume": int(amount), "price": float(price), "order_id": order_id, "seqno": self.event_id, "direction": direction, "offset": offset, "volume_orign": int(amount), "price_type": "LIMIT", "limit_price": float(price), "time_condition": "GFD", "volume_condition": "ANY", "insert_date_time": self.transform_dt(self.dtstr), 'order_time': self.dtstr, "exchange_order_id": str(uuid.uuid4()), "status": "ALIVE", "volume_left": int(amount), "last_msg": "已报" } self.orders[order_id] = order self.log('下单成功 {}'.format(order_id)) self.sync() self.on_ordersend(order) return order else: self.log(RuntimeError("ORDER CHECK FALSE: {}".format(code))) return False def on_ordersend(self, order): pass def cancel_order(self, order_id): """Initial 撤单/ 释放冻结/ """ od = self.orders[order_id] od['last_msg'] = '已撤单' od['status'] = "CANCEL" od['volume_left'] = 0 if od['offset'] in ['CLOSE', 'CLOSETODAY']: pos = self.positions[od['instrument_id']] if od['direction'] == 'BUY': pos.volume_short_frozen_today += od['volume_left'] else: pos.volume_long_frozen_today += od['volume_left'] else: frozen = self.frozen[order_id] self.money += frozen['money'] frozen['amount'] = 0 frozen['money'] = 0 self.frozen[order_id] = frozen self.orders[order_id] = od self.log('撤单成功 {}'.format(order_id)) def make_deal(self, order: dict): if isinstance(order, dict): self.receive_deal(order["instrument_id"], trade_price=order["limit_price"], trade_time=self.dtstr, trade_amount=order["volume_left"], trade_towards=order["towards"], order_id=order['order_id'], trade_id=str(uuid.uuid4())) def receive_deal(self, code, trade_price, trade_amount, trade_towards, trade_time, message=None, order_id=None, trade_id=None, realorder_id=None): if order_id in self.orders.keys(): # update order od = self.orders[order_id] frozen = self.frozen.get(order_id, { 'order_id': order_id, 'money': 0, 'price': 0 }) vl = od.get('volume_left', 0) if trade_amount == vl: self.money += frozen['money'] frozen['amount'] = 0 frozen['money'] = 0 od['last_msg'] = '全部成交' od["status"] = "FINISHED" self.log('全部成交 {}'.format(order_id)) elif trade_amount < vl: frozen['amount'] = vl - trade_amount release_money = trade_amount * frozen.get('coeff', 1) self.money += release_money frozen['money'] -= release_money od['last_msg'] = '部分成交' od["status"] = "ALIVE" self.log('部分成交 {}'.format(order_id)) od['volume_left'] -= trade_amount self.orders[order_id] = od self.frozen[order_id] = frozen # update trade self.event_id += 1 trade_id = str(uuid.uuid4()) if trade_id is None else trade_id self.trades[trade_id] = { "seqno": self.event_id, "user_id": self.user_id, "trade_id": trade_id, "exchange_id": od['exchange_id'], "instrument_id": od['instrument_id'], "order_id": order_id, "exchange_trade_id": trade_id, "direction": od['direction'], "offset": od['offset'], "volume": trade_amount, "price": trade_price, "trade_time": trade_time, "commission": float(0), "trade_date_time": self.transform_dt(trade_time) } # update accounts print('update trade') margin, close_profit = self.get_position(code).update_pos( trade_price, trade_amount, trade_towards) self.money -= (margin - close_profit) self.close_profit += close_profit self.sync() def get_position(self, code: str = None) -> QA_Position: if code is None: return list(self.positions.values())[0] else: if code not in self.positions.keys(): self.positions[code] = QA_Position(code=code) return self.positions[code] def query_trade(self): pass def on_tick(self, tick): pass def on_bar(self, bar): pass def on_price_change(self, code, price, datetime=None): if code in self.positions.keys(): try: pos = self.get_position(code) if pos.last_price == price: pass else: pos.last_price = price self.sync() except Exception as e: self.log(e) if datetime: self.datetime = datetime def order_schedule(self, code: str, amount: float, price: float, towards: int, order_id: str = ''): """ 预调仓接口 """ if order_id == '': order_id = str(uuid.uuid4()) orderx = { 'code': code, 'amount': amount, 'price': price, 'towards': towards, 'order_id': order_id } self.schedule[order_id] = orderx
class QA_Account(QA_Worker): """QA_Account User-->Portfolio-->Account/Strategy ::::::::::::::::::::::::::::::::::::::::::::::::: :: :: Portfolio 1 -- Account/Strategy 1 :: :: USER :: -- Account/Strategy 2 :: :: :: Portfolio 2 -- Account/Strategy 3 :: ::::::::::::::::::::::::::::::::::::::::::::::::: 2018/1/5 再次修改 改版本去掉了多余的计算 精简账户更新 ====================== - 不再计算总资产/不再计算当前持仓/不再计算交易对照明细表 - 不再动态计算账户股票/期货市值 - 只维护 cash/history两个字段 剩下的全部惰性计算 QA_Account 是QUANTAXIS的最小不可分割单元之一 QA_Account是账户类 需要兼容股票/期货/指数 QA_Account继承自QA_Worker 可以被事件驱动 QA_Account可以直接被QA_Strategy继承 有三类输入: 信息类: 账户绑定的策略名/账户的用户名/账户类别/账户识别码/账户的broker 资产类: 现金/可用现金/交易历史/交易对照表 规则类: 是否允许卖空/是否允许t0结算 方法: 惰性计算:最新持仓/最新总资产/最新现金/持仓面板 生成订单/接受交易结果数据 接收新的数据/on_bar/on_tick方法/缓存新数据的market_data @royburns 1.添加注释 2018/05/18 T0交易的sell_available和正常的sell_available不一样: T0交易中, 当买入一笔/卖出一笔, 当天操作额度都会下降 T0的订单-账户对应系统 @2018/06/11 QA_Account不会基于行情计算市值,因此都只会对应记录证券数量和现金资产 @2018/12/23 当我们继承/复用 QA_Account 的时候, 我们需要实现什么 - init_cash - init_hold - broker @2018/12/24 账户需要追踪 @2018/12/31 账户需要截面数据 需要在任意截面的基础上往下做 基础的信息截面 ==> init_cash/init_hold | history的初始值 任意时间点的信息截面 """ def __init__(self, strategy_name=None, user_cookie=None, portfolio_cookie=None, account_cookie=None, market_type=MARKET_TYPE.STOCK_CN, frequence=FREQUENCE.DAY, broker=BROKER_TYPE.BACKETEST, init_hold={}, init_cash=1000000, commission_coeff=0.00025, tax_coeff=0.001, margin_level={}, allow_t0=False, allow_sellopen=False, allow_margin=False, running_environment=RUNNING_ENVIRONMENT.BACKETEST): """ :param [str] strategy_name: 策略名称 :param [str] user_cookie: 用户cookie :param [str] portfolio_cookie: 组合cookie :param [str] account_cookie: 账户cookie :param [dict] init_hold 初始化时的股票资产 :param [float] init_cash: 初始化资金 :param [float] commission_coeff: 交易佣金 :默认 万2.5 float 类型 :param [float] tax_coeff: 印花税 :默认 千1.5 float 类型 :param [Bool] margin_level: 保证金比例 默认{} :param [Bool] allow_t0: 是否允许t+0交易 默认False :param [Bool] allow_sellopen: 是否允许卖空开仓 默认False :param [Bool] allow_margin: 是否允许保证金交易 默认False ### 注意 >>>>>>>>>>>>> 在期货账户中: allow_t0/ allow_sellopen 是必须打开的 allow_margin 是作为保证金账户的开关 默认关闭 可以打开 则按照market_preset中的保证金比例来计算 具体可以参见: https://github.com/QUANTAXIS/QUANTAXIS/blob/master/EXAMPLE/test_backtest/FUTURE/TEST_%E4%BF%9D%E8%AF%81%E9%87%91%E8%B4%A6%E6%88%B7.ipynb >>>>>>>>>>>>> :param [QA.PARAM] market_type: 市场类别 默认QA.MARKET_TYPE.STOCK_CN A股股票 :param [QA.PARAM] frequence: 账户级别 默认日线QA.FREQUENCE.DAY :param [QA.PARAM] broker: BROEKR类 默认回测 QA.BROKER_TYPE.BACKTEST :param [QA.PARAM] running_environment 当前运行环境 默认Backtest # 2018/06/11 init_assets 从float变为dict,并且不作为输入,作为只读属性 # :param [float] init_assets: 初始资产 默认 1000000 元 (100万) init_assets:{ cash: xxx, stock: {'000001':2000}, init_date: '2018-02-05', init_datetime: '2018-02-05 15:00:00' } # 2018/06/11 取消在初始化的时候的cash和history输入 # :param [list] cash: 可用现金 默认 是 初始资产 list 类型 # :param [list] history: 交易历史 # 2018/11/9 修改保证金交易 # 我们把冻结的保证金 看做是未来的已实现交易: # 如==> 当前的一手空单 认为是未来的卖出成交(已知价格 不知时间) # 因此我们如此对于保证金交易进行评估: # 账户买入: 多单开仓: cash 下降x 保证金增加x 增加一手未来的卖出合约(持仓) ==> 平仓: cash上升 保证金恢复 cash + frozen(平仓释放) + 未平仓位 cash, available_cash frozen{ RB1901: { towards 2: {avg_money : xxx, amount: xxx, queue: collection.deque()}, towards -2: {avg_money, amount, queue: collection.deque()} }, IF1901: { towards 2: {avg_money, amount,queue: collection.deque()}, towards -2: {avg_money, amount,queue: collection.deque()} } } } hold: { RB1901: { 1, amount, # 多单待平仓 -1, amount # 空单待平仓 } } """ super().__init__() # warnings.warn('QUANTAXIS 1.0.46 has changed the init_assets ==> init_cash, please pay attention to this change if you using init_cash to initial an account class,\ # ', DeprecationWarning, stacklevel=2) self._history_headers = [ 'datetime', 'code', 'price', 'amount', 'cash', 'order_id', 'realorder_id', 'trade_id', 'account_cookie', 'commission', 'tax', 'message', 'frozen' ] ######################################################################## # 信息类: self.strategy_name = strategy_name self.user_cookie = user_cookie self.portfolio_cookie = portfolio_cookie self.account_cookie = QA_util_random_with_topic( 'Acc') if account_cookie is None else account_cookie self.market_type = market_type self.broker = broker self.frequence = frequence self.running_environment = running_environment ######################################################################## self._market_data = None self._currenttime = None self.commission_coeff = commission_coeff self.tax_coeff = tax_coeff self.datetime = None self.running_time = datetime.datetime.now() self.quantaxis_version = __version__ ######################################################################## # 资产类 self.orders = QA_OrderQueue() # 历史委托单 self.init_cash = init_cash self.init_hold = pd.Series(init_hold, name='amount') if isinstance( init_hold, dict) else init_hold self.init_hold.index.name = 'code' self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 可用资金 self.sell_available = copy.deepcopy(self.init_hold) self.buy_available = copy.deepcopy(self.init_hold) self.history = [] self.time_index = [] # 在回测中, 每日结算后更新 # 真实交易中, 为每日初始化/每次重新登录后的同步信息 self.static_balance = { 'static_assets': [], 'cash': [], 'frozen': [], 'hold': [], 'date': [] } # 日结算 self.today_trade = {'last': [], 'current': []} self.today_orders = {'last': [], 'current': []} ######################################################################## # 规则类 # 1.是否允许t+0 及买入及结算 # 2.是否允许卖空开仓 # 3.是否允许保证金交易/ 如果不是false 就需要制定保证金比例(dict形式) # 期货: allow_t0 True allow_sellopen True # self.allow_t0 = allow_t0 self.allow_sellopen = allow_sellopen self.allow_margin = allow_margin self.margin_level = margin_level # 保证金比例 if self.market_type is MARKET_TYPE.FUTURE_CN: self.allow_t0 = True self.allow_sellopen = True if self.allow_t0 and self.allow_sellopen or self.market_type is MARKET_TYPE.FUTURE_CN: self.load_marketpreset() """期货的多开/空开 ==> 资金冻结进保证金 frozen 对应平仓的时候, 释放保证金 1. frozen 是一个dict : {[code]:queue} key是标的 value是对应的交易queue """ self.frozen = {} # 冻结资金(保证金) def __repr__(self): return '< QA_Account {}>'.format(self.account_cookie) @property def message(self): 'the standard message which can be transfer' return { 'source': 'account', 'account_cookie': self.account_cookie, 'portfolio_cookie': self.portfolio_cookie, 'user_cookie': self.user_cookie, 'broker': self.broker, 'market_type': self.market_type, 'strategy_name': self.strategy_name, 'current_time': str(self._currenttime), 'allow_sellopen': self.allow_sellopen, 'allow_t0': self.allow_t0, 'margin_level': self.margin_level, 'init_assets': self.init_assets, 'init_cash': self.init_cash, 'init_hold': self.init_hold.to_dict(), 'commission_coeff': self.commission_coeff, 'tax_coeff': self.tax_coeff, 'cash': self.cash, 'history': self.history, 'trade_index': self.time_index, 'running_time': str(datetime.datetime.now()) if self.running_time is None else str(self.running_time), 'quantaxis_version': self.quantaxis_version, 'running_environment': self.running_environment, 'start_date': self.start_date, 'end_date': self.end_date } def load_marketpreset(self): """加载市场表 """ self.market_preset = MARKET_PRESET() @property def init_hold_with_account(self): """带account_id的初始化持仓 Returns: [type] -- [description] """ return self.init_hold.reset_index().assign( account_cookie=self.account_cookie).set_index( ['code', 'account_cookie']) @property def init_assets(self): """初始化账户资产 Returns: dict -- 2keys-cash,hold """ return {'cash': self.init_cash, 'hold': self.init_hold.to_dict()} @property def code(self): """ 该账户曾交易代码 用set 去重 """ return list(set([item[1] for item in self.history])) @property def date(self): """账户运行的日期 Arguments: self {[type]} -- [description] Returns: [type] -- [description] """ if self.datetime is not None: return str(self.datetime)[0:10] else: return None @property def poisitions(self): raise NotImplementedError @property def start_date(self): """账户的起始交易日期(只在回测中使用) Raises: RuntimeWarning -- [description] Returns: [type] -- [description] """ if len(self.time_index) > 0: return str(min(self.time_index))[0:10] else: print( RuntimeWarning( 'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE')) @property def end_date(self): """账户的交易结束日期(只在回测中使用) Raises: RuntimeWarning -- [description] Returns: [type] -- [description] """ if len(self.time_index) > 0: return str(max(self.time_index))[0:10] else: print( RuntimeWarning( 'QAACCOUNT: THIS ACCOUNT DOESNOT HAVE ANY TRADE')) @property def market_data(self): return self._market_data @property def trade_range(self): return QA_util_get_trade_range(self.start_date, self.end_date) @property def trade_day(self): return list( pd.Series(self.time_index).apply(lambda x: str(x)[0:10]).unique()) @property def history_table(self): '交易历史的table' if len(self.history) > 0: lens = len(self.history[0]) else: lens = len(self._history_headers) return pd.DataFrame(data=self.history, columns=self._history_headers[:lens]).sort_index() @property def today_trade_table(self): return pd.DataFrame(data=self.today_trade['current'], columns=self._history_headers).sort_index() @property def cash_table(self): '现金的table' _cash = pd.DataFrame(data=[self.cash[1::], self.time_index], index=['cash', 'datetime']).T _cash = _cash.assign(date=_cash.datetime.apply( lambda x: pd.to_datetime(str(x)[0:10]))).assign( account_cookie=self.account_cookie) # .sort_values('datetime') return _cash.set_index(['datetime', 'account_cookie'], drop=False) """ 实验性质 @2018-06-09 # 对于账户持仓的分解 1. 真实持仓hold: 正常模式/TZero模式: hold = 历史持仓(init_hold)+ 初始化账户后发生的所有交易导致的持仓(hold_available) 动态持仓(初始化账户后的持仓)hold_available: self.history 计算而得 2. 账户的可卖额度(sell_available) 正常模式: sell_available 结算前: init_hold+ 买卖交易(卖-) 结算后: init_hold+ 买卖交易(买+ 卖-) TZero模式: sell_available 结算前: init_hold - 买卖交易占用的额度(abs(买+ 卖-)) 结算过程 是为了补平(等于让hold={}) 结算后: init_hold """ @property def hold(self): """真实持仓 """ return pd.concat([self.init_hold, self.hold_available]).groupby('code').sum().replace( 0, np.nan).dropna().sort_index() @property def hold_available_temp(self): """可用持仓 """ return self._table.groupby('code').amount.sum().replace( 0, np.nan).dropna().sort_index() @property def hold_available(self): """可用持仓 """ return self.history_table.groupby('code').amount.sum().replace( 0, np.nan).dropna().sort_index() # @property # def order_table(self): # """return order trade list""" # return self.orders.trade_list @property def trade(self): """每次交易的pivot表 Returns: pd.DataFrame 此处的pivot_table一定要用np.sum """ return self.history_table.pivot_table( index=['datetime', 'account_cookie'], columns='code', values='amount', aggfunc=np.sum).fillna(0).sort_index() @property def daily_cash(self): '每日交易结算时的现金表' res = self.cash_table.drop_duplicates(subset='date', keep='last') return pd.concat([res.set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\ .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie'], drop=False).sort_index() @property def daily_hold(self): '每日交易结算时的持仓表' data = self.trade.cumsum() if len(data) < 1: return None else: # print(data.index.levels[0]) data = data.assign(account_cookie=self.account_cookie).assign( date=pd.to_datetime(data.index.levels[0]).date) data.date = pd.to_datetime(data.date) data = data.set_index(['date', 'account_cookie']) res = data[~data.index.duplicated(keep='last')].sort_index() # 这里会导致股票停牌时的持仓也被计算 但是计算market_value的时候就没了 return pd.concat([res.reset_index().set_index('date'), pd.Series(data=None, index=pd.to_datetime(self.trade_range).set_names('date'), name='predrop')], axis=1)\ .ffill().drop(['predrop'], axis=1).reset_index().set_index(['date', 'account_cookie']).sort_index() # 计算assets的时候 需要一个market_data=QA.QA_fetch_stock_day_adv(list(data.columns),data.index[0],data.index[-1]) # (market_data.to_qfq().pivot('close')*data).sum(axis=1)+user_cookie.get_account(a_1).daily_cash.set_index('date').cash @property def latest_cash(self): 'return the lastest cash 可用资金' return self.cash[-1] @property def current_time(self): 'return current time (in backtest/real environment)' return self._currenttime def hold_table(self, datetime=None): "到某一个时刻的持仓 如果给的是日期,则返回当日开盘前的持仓" if datetime is None: hold_available = self.history_table.set_index( 'datetime').sort_index().groupby( 'code').amount.sum().sort_index() else: hold_available = self.history_table.set_index( 'datetime').sort_index().loc[:datetime].groupby( 'code').amount.sum().sort_index() return pd.concat([self.init_hold, hold_available ]).groupby('code').sum().sort_index().apply( lambda x: x if x > 0 else None).dropna() def hold_price(self, datetime=None): """计算持仓成本 如果给的是日期,则返回当日开盘前的持仓 Keyword Arguments: datetime {[type]} -- [description] (default: {None}) Returns: [type] -- [description] """ def weights(x): if sum(x['amount']) != 0: return np.average(x['price'], weights=x['amount'], returned=True) else: return np.nan if datetime is None: return self.history_table.set_index( 'datetime', drop=False).sort_index().groupby('code').apply( weights).dropna() else: return self.history_table.set_index( 'datetime', drop=False).sort_index().loc[:datetime].groupby( 'code').apply(weights).dropna() # @property def hold_time(self, datetime=None): """持仓时间 Keyword Arguments: datetime {[type]} -- [description] (default: {None}) """ def weights(x): if sum(x['amount']) != 0: return pd.Timestamp(self.datetime) - pd.to_datetime( x.datetime.max()) else: return np.nan if datetime is None: return self.history_table.set_index( 'datetime', drop=False).sort_index().groupby('code').apply( weights).dropna() else: return self.history_table.set_index( 'datetime', drop=False).sort_index().loc[:datetime].groupby( 'code').apply(weights).dropna() def reset_assets(self, init_cash=None): 'reset_history/cash/' self.sell_available = copy.deepcopy(self.init_hold) self.history = [] self.init_cash = init_cash self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 在途资金 def receive_simpledeal(self, code, trade_price, trade_amount, trade_towards, trade_time, message=None, order_id=None, trade_id=None, realorder_id=None): """快速撮合成交接口 此接口是一个直接可以成交的接口, 所以务必确保给出的信息是可以成交的 此接口涉及的是 1. 股票/期货的成交 2. 历史记录的增加 3. 现金/持仓/冻结资金的处理 Arguments: code {[type]} -- [description] trade_price {[type]} -- [description] trade_amount {[type]} -- [description] trade_towards {[type]} -- [description] trade_time {[type]} -- [description] Keyword Arguments: message {[type]} -- [description] (default: {None}) 2018/11/7 @yutiansut 修复一个bug: 在直接使用该快速撮合接口的时候, 期货卖出会扣减保证金, 买回来的时候应该反算利润 如 3800卖空 3700买回平仓 应为100利润 @2018-12-31 保证金账户ok @2019/1/3 一些重要的意思 frozen = self.market_preset.get_frozen(code) # 保证金率 unit = self.market_preset.get_unit(code) # 合约乘数 raw_trade_money = trade_price*trade_amount*market_towards # 总市值 value = raw_trade_money * unit # 合约总价值 trade_money = value * frozen # 交易保证金 """ self.datetime = trade_time market_towards = 1 if trade_towards > 0 else -1 # value 合约价值 unit 合约乘数 if self.allow_margin: frozen = self.market_preset.get_frozen(code) # 保证金率 unit = self.market_preset.get_unit(code) # 合约乘数 raw_trade_money = trade_price * trade_amount * market_towards # 总市值 value = raw_trade_money * unit # 合约总价值 trade_money = value * frozen # 交易保证金 else: trade_money = trade_price * trade_amount * market_towards raw_trade_money = trade_money value = trade_money unit = 1 frozen = 1 # 计算费用 # trade_price if self.market_type == MARKET_TYPE.FUTURE_CN: # 期货不收税 # 双边手续费 也没有最小手续费限制 commission_fee_preset = self.market_preset.get_code(code) if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN ]: commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_peramount'] * \ abs(value) elif trade_towards in [ ORDER_DIRECTION.BUY_CLOSETODAY, ORDER_DIRECTION.SELL_CLOSETODAY ]: commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_today_peramount'] * \ abs(value) tax_fee = 0 # 买入不收印花税 elif self.market_type == MARKET_TYPE.STOCK_CN: commission_fee = self.commission_coeff * \ abs(trade_money) commission_fee = 5 if commission_fee < 5 else commission_fee if int(trade_towards) > 0: tax_fee = 0 # 买入不收印花税 else: tax_fee = self.tax_coeff * abs(trade_money) # 结算交易 if self.cash[-1] > trade_money + commission_fee + tax_fee: self.time_index.append(trade_time) # TODO: 目前还不支持期货的锁仓 if self.allow_sellopen: if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN ]: # 开仓单占用现金 计算avg # 初始化 if code in self.frozen.keys(): if trade_towards in self.frozen[code].keys(): pass else: self.frozen[code][trade_towards] = { 'money': 0, 'amount': 0, 'avg_price': 0 } else: self.frozen[code] = { ORDER_DIRECTION.BUY_OPEN: { 'money': 0, 'amount': 0, 'avg_price': 0 }, ORDER_DIRECTION.SELL_OPEN: { 'money': 0, 'amount': 0, 'avg_price': 0 } } """[summary] # frozen的计算 # money 冻结的资金 # amount 冻结的数量 2018-12-31 """ self.frozen[code][trade_towards]['money'] = ( (self.frozen[code][trade_towards]['money'] * self.frozen[code][trade_towards]['amount']) + abs(trade_money)) / ( self.frozen[code][trade_towards]['amount'] + trade_amount) self.frozen[code][trade_towards]['avg_price'] = ( (self.frozen[code][trade_towards]['avg_price'] * self.frozen[code][trade_towards]['amount']) + abs(raw_trade_money)) / ( self.frozen[code][trade_towards]['amount'] + trade_amount) self.frozen[code][trade_towards]['amount'] += trade_amount self.cash.append(self.cash[-1] - abs(trade_money) - commission_fee - tax_fee) elif trade_towards in [ ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE ]: # 平仓单释放现金 # if trade_towards == ORDER_DIRECTION.BUY_CLOSE: # 卖空开仓 平仓买入 # self.cash if trade_towards == ORDER_DIRECTION.BUY_CLOSE: # 买入平仓 之前是空开 # self.frozen[code][ORDER_DIRECTION.SELL_OPEN]['money'] -= trade_money self.frozen[code][ORDER_DIRECTION. SELL_OPEN]['amount'] -= trade_amount frozen_part = self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['money'] * trade_amount # 账户的现金+ 冻结的的释放 + 买卖价差* 杠杆 self.cash.append(self.cash[-1] + frozen_part + (frozen_part - trade_money) / frozen - commission_fee - tax_fee) if self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['money'] = 0 self.frozen[code][ ORDER_DIRECTION.SELL_OPEN]['avg_price'] = 0 elif trade_towards == ORDER_DIRECTION.SELL_CLOSE: # 卖出平仓 之前是多开 # self.frozen[code][ORDER_DIRECTION.BUY_OPEN]['money'] -= trade_money self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['amount'] -= trade_amount frozen_part = self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['money'] * trade_amount self.cash.append(self.cash[-1] + frozen_part + (abs(trade_money) - frozen_part) / frozen - commission_fee - tax_fee) if self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['amount'] == 0: self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['money'] = 0 self.frozen[code][ ORDER_DIRECTION.BUY_OPEN]['avg_price'] = 0 else: # 不允许卖空开仓的==> 股票 self.cash.append(self.cash[-1] - trade_money - tax_fee - commission_fee) if self.allow_t0 or trade_towards == ORDER_DIRECTION.SELL: self.sell_available[code] = self.sell_available.get( code, 0) + trade_amount * market_towards self.buy_available = self.sell_available self.cash_available = self.cash[-1] frozen_money = abs(trade_money) if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN ] else 0 self.history.append([ trade_time, code, trade_price, market_towards * trade_amount, self.cash[-1], order_id, realorder_id, trade_id, self.account_cookie, commission_fee, tax_fee, message, frozen_money ]) else: # print(self.cash[-1]) self.cash_available = self.cash[-1] #print('NOT ENOUGH MONEY FOR {}'.format(order_id)) def receive_deal(self, code: str, trade_id: str, order_id: str, realorder_id: str, trade_price: float, trade_amount: int, trade_towards: int, trade_time: str, message=None): """更新deal Arguments: code {str} -- [description] trade_id {str} -- [description] order_id {str} -- [description] realorder_id {str} -- [description] trade_price {float} -- [description] trade_amount {int} -- [description] trade_towards {int} -- [description] trade_time {str} -- [description] Returns: [type] -- [description] """ print('receive deal') trade_time = str(trade_time) code = str(code) trade_price = float(trade_price) trade_towards = int(trade_towards) realorder_id = str(realorder_id) trade_id = str(trade_id) trade_amount = int(trade_amount) order_id = str(order_id) market_towards = 1 if trade_towards > 0 else -1 """2019/01/03 直接使用快速撮合接口了 2333 这两个接口现在也没啥区别了.... 太绝望了 """ self.receive_simpledeal(code, trade_price, trade_amount, trade_towards, trade_time, message=message, order_id=order_id, trade_id=trade_id, realorder_id=realorder_id) def send_order(self, code=None, amount=None, time=None, towards=None, price=None, money=None, order_model=None, amount_model=None, *args, **kwargs): """ ATTENTION CHANGELOG 1.0.28 修改了Account的send_order方法, 区分按数量下单和按金额下单两种方式 - AMOUNT_MODEL.BY_PRICE ==> AMOUNT_MODEL.BY_MONEY # 按金额下单 - AMOUNT_MODEL.BY_AMOUNT # 按数量下单 在按金额下单的时候,应给予 money参数 在按数量下单的时候,应给予 amount参数 python code: Account=QA.QA_Account() Order_bymoney=Account.send_order(code='000001', price=11, money=0.3*Account.cash_available, time='2018-05-09', towards=QA.ORDER_DIRECTION.BUY, order_model=QA.ORDER_MODEL.MARKET, amount_model=QA.AMOUNT_MODEL.BY_MONEY ) Order_byamount=Account.send_order(code='000001', price=11, amount=100, time='2018-05-09', towards=QA.ORDER_DIRECTION.BUY, order_model=QA.ORDER_MODEL.MARKET, amount_model=QA.AMOUNT_MODEL.BY_AMOUNT ) :param code: 证券代码 :param amount: 买卖 数量多数股 :param time: Timestamp 对象 下单时间 :param towards: int , towards>0 买入 towards<0 卖出 :param price: 买入,卖出 标的证券的价格 :param money: 买卖 价格 :param order_model: 类型 QA.ORDER_MODE :param amount_model:类型 QA.AMOUNT_MODEL :return: QA_Order | False @2018/12/23 send_order 是QA的标准返回, 如需对接其他接口, 只需要对于QA_Order做适配即可 @2018/12/27 在判断账户为期货账户(及 允许双向交易) @2018/12/30 保证金账户的修改 1. 保证金账户冻结的金额 2. 保证金账户的结算 3. 保证金账户的判断 """ wrong_reason = None assert code is not None and time is not None and towards is not None and order_model is not None and amount_model is not None # 🛠todo 移到Utils类中, 时间转换 # date 字符串 2011-10-11 长度10 date = str(time)[0:10] if len(str(time)) == 19 else str(time) # time 字符串 20011-10-11 09:02:00 长度 19 time = str(time) if len(str(time)) == 19 else '{} 09:31:00'.format( str(time)[0:10]) # 🛠todo 移到Utils类中, amount_to_money 成交量转金额 # BY_MONEY :: amount --钱 如10000元 因此 by_money里面 需要指定价格,来计算实际的股票数 # by_amount :: amount --股数 如10000股 if self.allow_margin: amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (self.market_preset.get_unit(code) * self.market_preset.get_frozen(code) * price * (1 + self.commission_coeff)) / 100) * 100 else: amount = amount if amount_model is AMOUNT_MODEL.BY_AMOUNT else int( money / (price * (1 + self.commission_coeff)) / 100) * 100 # 🛠todo 移到Utils类中, money_to_amount 金额转成交量 if self.allow_margin: money = amount * price * self.market_preset.get_unit(code)*self.market_preset.get_frozen(code) * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money else: money = amount * price * \ (1+self.commission_coeff) if amount_model is AMOUNT_MODEL.BY_AMOUNT else money # flag 判断买卖 数量和价格以及买卖方向是否正确 flag = False assert (int(towards) != 0) if int(towards) in [1, 2, 3]: # 是买入的情况(包括买入.买开.买平) if self.cash_available >= money: if self.market_type is MARKET_TYPE.STOCK_CN: # 如果是股票 买入的时候有100股的最小限制 amount = int(amount / 100) * 100 self.cash_available -= money flag = True if self.running_environment == RUNNING_ENVIRONMENT.TZERO: if self.buy_available.get(code, 0) >= amount: flag = True self.cash_available -= money self.buy_available[code] -= amount else: flag = False wrong_reason = 'T0交易买入超出限额' if self.market_type == MARKET_TYPE.FUTURE_CN: # 如果有负持仓-- 允许卖空的时候 if towards == 3: # 多平 _hold = self.sell_available.get(code, 0) # 假设有负持仓: # amount为下单数量 如 账户原先-3手 现在平1手 #left_amount = amount+_hold if _hold < 0 else amount _money = abs( float(amount * price * (1 + self.commission_coeff))) print(_hold) if self.cash_available >= _money: if _hold < 0: self.cash_available -= _money flag = True else: wrong_reason = '空单仓位不足' else: wrong_reason = '平多剩余资金不够' if towards == 2: self.cash_available -= money flag = True else: wrong_reason = 'QAACCOUNT: 可用资金不足 cash_available {} code {} time {} amount {} towards {}'.format( self.cash_available, code, time, amount, towards) elif int(towards) in [-1, -2, -3]: # 是卖出的情况(包括卖出,卖出开仓allow_sellopen如果允许. 卖出平仓) # print(self.sell_available[code]) _hold = self.sell_available.get(code, 0) # _hold 是你的持仓 # 如果你的hold> amount>0 # 持仓数量>卖出数量 if _hold >= amount: self.sell_available[code] -= amount # towards = ORDER_DIRECTION.SELL flag = True # 如果持仓数量<卖出数量 else: # 如果是允许卖空开仓 实际计算时 先减去持仓(正持仓) 再计算 负持仓 就按原先的占用金额计算 if self.allow_sellopen and towards == -2: if self.cash_available >= money: # 卖空的市值小于现金(有担保的卖空), 不允许裸卖空 # self.cash_available -= money flag = True else: print('sellavailable', _hold) print('amount', amount) print('aqureMoney', money) print('cash', self.cash_available) wrong_reason = "卖空资金不足/不允许裸卖空" else: wrong_reason = "卖出仓位不足" if flag and (amount > 0): _order = QA_Order(user_cookie=self.user_cookie, strategy=self.strategy_name, frequence=self.frequence, account_cookie=self.account_cookie, code=code, market_type=self.market_type, date=date, datetime=time, sending_time=time, callback=self.receive_deal, amount=amount, price=price, order_model=order_model, towards=towards, money=money, amount_model=amount_model, commission_coeff=self.commission_coeff, tax_coeff=self.tax_coeff, *args, **kwargs) # init # 历史委托order状态存储, 保存到 QA_Order 对象中的队列中 self.datetime = time self.orders.insert_order(_order) return _order else: print('ERROR : CODE {} TIME {} AMOUNT {} TOWARDS {}'.format( code, time, amount, towards)) print(wrong_reason) return False def cancel_order(self, order): if order.towards in [ ORDER_DIRECTION.BUY, ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE ]: if order.amount_model is AMOUNT_MODEL.BY_MONEY: self.cash_available += order.money elif order.amount_model is AMOUNT_MODEL.BY_AMOUNT: self.cash_available += order.price * order.amount elif order.towards in [ ORDER_DIRECTION.SELL, ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN ]: self.sell_available[order.code] += order.amount # self.sell_available[] @property def close_positions_order(self): """平仓单 Raises: RuntimeError -- if ACCOUNT.RUNNING_ENVIRONMENT is NOT TZERO Returns: list -- list with order """ order_list = [] time = '{} 15:00:00'.format(self.date) if self.running_environment == RUNNING_ENVIRONMENT.TZERO: for code, amount in self.hold_available.iteritems(): order = False if amount < 0: # 先卖出的单子 买平 order = self.send_order( code=code, price=0, amount=abs(amount), time=time, towards=ORDER_DIRECTION.BUY_CLOSE, order_model=ORDER_MODEL.CLOSE, amount_model=AMOUNT_MODEL.BY_AMOUNT) elif amount > 0: # 先买入的单子, 卖平 order = self.send_order( code=code, price=0, amount=abs(amount), time=time, towards=ORDER_DIRECTION.SELL_CLOSE, order_model=ORDER_MODEL.CLOSE, amount_model=AMOUNT_MODEL.BY_AMOUNT) if order: order_list.append(order) return order_list else: raise RuntimeError( 'QAACCOUNT with {} environments cannot use this methods'. format(self.running_environment)) def settle(self): """ 股票/期货的日结算 股票的结算: 结转股票可卖额度 T0的结算: 结转T0的额度 期货的结算: 结转静态资金 """ if self.running_environment == RUNNING_ENVIRONMENT.TZERO and self.hold_available.sum( ) != 0: raise RuntimeError('QAACCOUNT: 该T0账户未当日仓位,请平仓 {}'.format( self.hold_available.to_dict())) if self.market_type == MARKET_TYPE.FUTURE_CN: self.static_balance['frozen'].append( sum([ rx['money'] * rx['amount'] for var in self.frozen.values() for rx in var.values() ])) self.static_balance['cash'].append(self.cash[-1]) self.static_balance['hold'].append(self.hold.to_dict()) self.static_balance['date'].append(self.date) """静态权益的结算 只关心开仓价/ 不做盯市制度 动态权益的结算需要关心 """ self.static_balance['static_assets'].append( self.static_balance['cash'][-1] + self.static_balance['frozen'][-1]) self.sell_available = self.hold self.buy_available = self.hold self.datetime = '{} 09:30:00'.format(QA_util_get_next_day( self.date)) if self.date is not None else None def on_bar(self, event): ''' 策略事件 :param event: :return: ''' 'while updating the market data' print("on_bar account {} ".format(self.account_cookie), event.market_data) def on_tick(self, event): ''' 策略事件 :param event: :return: ''' 'on tick event' print("on_tick ", event.market_data) pass def from_message(self, message): """resume the account from standard message 这个是从数据库恢复账户时需要的""" self.account_cookie = message.get('account_cookie', None) self.portfolio_cookie = message.get('portfolio_cookie', None) self.user_cookie = message.get('user_cookie', None) self.broker = message.get('broker', None) self.market_type = message.get('market_type', None) self.strategy_name = message.get('strategy_name', None) self._currenttime = message.get('current_time', None) self.allow_sellopen = message.get('allow_sellopen', False) self.allow_t0 = message.get('allow_t0', False) self.margin_level = message.get('margin_level', False) self.init_cash = message.get('init_cash', message.get('init_assets', 1000000)) # 兼容修改 self.init_hold = pd.Series(message.get('init_hold', {}), name='amount') self.init_hold.index.name = 'code' self.commission_coeff = message.get('commission_coeff', 0.00015) self.tax_coeff = message.get('tax_coeff', 0.0015) self.history = message['history'] self.cash = message['cash'] self.time_index = message['trade_index'] self.running_time = message.get('running_time', None) self.quantaxis_version = message.get('quantaxis_version', None) self.running_environment = message.get('running_environment', RUNNING_ENVIRONMENT.BACKETEST) self.settle() return self @property def table(self): """ 打印出account的内容 """ return pd.DataFrame([ self.message, ]).set_index('account_cookie', drop=False).T def run(self, event): ''' 这个方法是被 QA_ThreadEngine 处理队列时候调用的, QA_Task 中 do 方法调用 run (在其它线程中) 'QA_WORKER method 重载' :param event: 事件类型 QA_Event :return: ''' 'QA_WORKER method' if event.event_type is ACCOUNT_EVENT.SETTLE: self.settle() # elif event.event_type is ACCOUNT_EVENT.UPDATE: # self.receive_deal(event.message) elif event.event_type is ACCOUNT_EVENT.MAKE_ORDER: """generate order if callback callback the order if not return back the order """ data = self.send_order(code=event.code, amount=event.amount, time=event.time, amount_model=event.amount_model, towards=event.towards, price=event.price, order_model=event.order_model) if event.callback: event.callback(data) else: return data elif event.event_type is ENGINE_EVENT.UPCOMING_DATA: """update the market_data 1. update the inside market_data struct 2. tell the on_bar methods # 这样有点慢 """ self._currenttime = event.market_data.datetime[0] if self._market_data is None: self._market_data = event.market_data else: self._market_data = self._market_data + event.market_data self.on_bar(event) if event.callback: event.callback(event) def save(self): """ 存储账户信息 """ save_account(self.message) def sync_account(self, sync_message): """同步账户 Arguments: sync_message {[type]} -- [description] """ self.init_hold = sync_message['hold_available'] self.init_cash = sync_message['cash_available'] self.sell_available = copy.deepcopy(self.init_hold) self.history = [] self.cash = [self.init_cash] self.cash_available = self.cash[-1] # 在途资金 def change_cash(self, money): """ 外部操作|高危| """ res = self.cash[-1] + money if res >= 0: # 高危操作 self.cash[-1] = res def get_orders(self, if_today=True): ''' 返回当日委托/历史委托 :param if_today: true 只返回今天的订单 :return: QA_OrderQueue ''' # 🛠todo 筛选其它不是今天的订单返回 return self.orders def get_history(self, start, end): """返回历史成交 Arguments: start {str} -- [description] end {str]} -- [description] """ return self.history_table.set_index('datetime', drop=False).loc[slice( pd.Timestamp(start), pd.Timestamp(end))]
class QA_Position(): """一个持仓模型:/兼容股票期货/兼容保证金持仓 兼容快期DIFF协议 基础字段 code [str] 品种名称 volume_long_today [float] 今仓 多单持仓 volume_long_his [float] 昨仓 多单持仓 volume_short_today [float] 今仓 空单持仓 volume_short_his [float] 昨仓 空单持仓 volume_long_frozen_his [float] 多单昨日冻结 volume_long_frozen_today [float] 多单今日冻结 volume_short_frozen_his [float] 空单昨日冻结 volume_short_frozen_today [float] 空单今日冻结 margin_long [float] 多单保证金 margin_short [float] 空单保证金 open_price_long [float] 多单开仓价 open_price_short [float] 空单开仓价 position_price_long [float] 逐日盯市的前一交易日的结算价 position_price_short [float] 逐日盯市的前一交易日的结算价 open_cost_long [float] 多单开仓成本(指的是保证金冻结) open_cost_short [float] 空单开仓成本 position_cost_long [float] 多单持仓成本(指的是基于逐日盯市制度下的成本价) position_cost_short [float] 空单持仓成本 market_type=MARKET_TYPE.STOCK_CN [enum] 市场类别 exchange_id=EXCHANGE_ID.SZSE [enum] 交易所id(支持股票/期货) name=None [str] 名称 功能: 1/ 支持当价格变化后的 持仓的自行计算更新 2/ 支持调仓模型(未加入) 3/ 支持仓位风控(未加入) 4/ 支持资金分配和PMS内部资金结算(moneypreset) PMS 内部可以预分配一个资金限额, 方便pms实时计算属于PMS的收益 兼容QA_Account的创建/拆入Positions库 QAPosition 不对订单信息做正确性保证, 需要自行在外部构建 OMS系统 {QACEPEngine/QAAccountPro} """ def __init__( self, code='000001', account_cookie='quantaxis', portfolio_cookie='portfolio', username='******', moneypreset=100000, # 初始分配资金 frozen=None, moneypresetLeft=None, volume_long_today=0, volume_long_his=0, volume_short_today=0, volume_short_his=0, volume_long_frozen_his=0, volume_long_frozen_today=0, volume_short_frozen_his=0, volume_short_frozen_today=0, margin_long=0, margin_short=0, open_price_long=0, open_price_short=0, position_price_long=0, # 逐日盯市的前一交易日的结算价 position_price_short=0, # 逐日盯市的前一交易日的结算价 open_cost_long=0, open_cost_short=0, position_cost_long=0, position_cost_short=0, position_id=None, market_type=None, exchange_id=None, trades=None, orders=None, name=None, commission=0, auto_reload=False, allow_exceed=False, spms_id=None, oms_id=None, *args, **kwargs): self.code = code self.account_cookie = account_cookie self.portfolio_cookie = portfolio_cookie self.username = username self.time = '' self.market_preset = MARKET_PRESET().get_code(self.code) self.position_id = str( uuid.uuid4()) if position_id is None else position_id self.moneypreset = moneypreset self.moneypresetLeft = self.moneypreset if moneypresetLeft is None else moneypresetLeft """{'name': '原油', 'unit_table': 1000, 'price_tick': 0.1, 'buy_frozen_coeff': 0.1, 'sell_frozen_coeff': 0.1, 'exchange': 'INE', 'commission_coeff_peramount': 0, 'commission_coeff_pervol': 20.0, 'commission_coeff_today_peramount': 0, 'commission_coeff_today_pervol': 0.0} """ self.rule = 'FIFO' self.name = name if market_type is None: self.market_type = MARKET_TYPE.FUTURE_CN if re.search( r'[a-zA-z]+', self.code) else MARKET_TYPE.STOCK_CN self.exchange_id = exchange_id self.volume_long_his = volume_long_his self.volume_long_today = volume_long_today self.volume_short_his = volume_short_his self.volume_short_today = volume_short_today self.volume_long_frozen_his = volume_long_frozen_his self.volume_long_frozen_today = volume_long_frozen_today self.volume_short_frozen_his = volume_short_frozen_his self.volume_short_frozen_today = volume_short_frozen_today self.margin_long = margin_long self.margin_short = margin_short self.open_price_long = open_price_long self.open_price_short = open_price_short self.position_price_long = open_price_long if position_price_long == 0 else position_price_long self.position_price_short = open_price_short if position_price_short == 0 else position_price_short self.open_cost_long = open_cost_long if open_cost_long != 0 else open_price_long * \ self.volume_long*self.market_preset.get('unit_table', 1) self.open_cost_short = open_cost_short if open_cost_short != 0 else open_price_short * \ self.volume_short*self.market_preset.get('unit_table', 1) self.position_cost_long = position_cost_long if position_cost_long != 0 else self.position_price_long * \ self.volume_long*self.market_preset.get('unit_table', 1) self.position_cost_short = position_cost_short if position_cost_short != 0 else self.position_price_short * \ self.volume_short*self.market_preset.get('unit_table', 1) self.last_price = 0 self.commission = commission self.trades = [] if trades is None else trades self.orders = {} if orders is None else orders self.frozen = {} if frozen is None else frozen self.spms_id = spms_id self.oms_id = oms_id if auto_reload: self.reload() self.allow_exceed = allow_exceed def __repr__(self): return '< QAPOSITION {} amount {}/{} >'.format(self.code, self.volume_long, self.volume_short) def read_diff(self, diff_slice): """[summary] Arguments: diff_slice {dict} -- [description] {'user_id': '100002', 'exchange_id': 'SHFE', 'instrument_id': 'rb1905', 'volume_long_today': 0, 'volume_long_his': 0, 'volume_long': 0, 'volume_long_frozen_today': 0, 'volume_long_frozen_his': 0, 'volume_long_frozen': 0, 'volume_short_today': 0, 'volume_short_his': 0, 'volume_short': 0, 'volume_short_frozen_today': 0, 'volume_short_frozen_his': 0, 'volume_short_frozen': 0, 'open_price_long': 4193.0, 'open_price_short': 4192.0, 'open_cost_long': 0.0, 'open_cost_short': 0.0, 'position_price_long': 4193.0, 'position_price_short': 4192.0, 'position_cost_long': 0.0, 'position_cost_short': 0.0, 'last_price': 4137.0, 'float_profit_long': 0.0, 'float_profit_short': 0.0, 'float_profit': 0.0, 'position_profit_long': 0.0, 'position_profit_short': 0.0, 'position_profit': 0.0, 'margin_long': 0.0, 'margin_short': 0.0, 'margin': 0.0} Returns: QA_Position -- [description] """ self.account_cookie = diff_slice['user_id'] self.code = diff_slice['instrument_id'] self.volume_long_today = diff_slice['volume_long_today'] self.volume_long_his = diff_slice['volume_long_his'] self.volume_long_frozen_today = diff_slice['volume_long_frozen_today'] self.volume_long_frozen_his = diff_slice['volume_long_frozen_his'] self.volume_short_today = diff_slice['volume_short_today'] self.volume_short_his = diff_slice['volume_short_his'] self.volume_short_frozen_today = diff_slice[ 'volume_short_frozen_today'] self.volume_short_frozen_his = diff_slice['volume_short_frozen_his'] self.open_price_long = diff_slice['open_price_long'] self.open_price_short = diff_slice['open_price_short'] self.open_cost_long = diff_slice['open_cost_long'] self.open_cost_short = diff_slice['open_cost_short'] self.position_price_long = diff_slice['position_price_long'] self.position_price_short = diff_slice['position_price_short'] self.position_cost_long = diff_slice['position_cost_long'] self.position_cost_short = diff_slice['position_cost_short'] self.margin_long = diff_slice['margin_long'] self.margin_short = diff_slice['margin_short'] self.exchange_id = diff_slice['exchange_id'] self.market_type = MARKET_TYPE.FUTURE_CN return self @property def volume_long(self): return self.volume_long_today + self.volume_long_his @property def volume_short(self): return self.volume_short_his + self.volume_short_today @property def volume_long_frozen(self): return self.volume_long_frozen_his + self.volume_long_frozen_today @property def volume_short_frozen(self): return self.volume_short_frozen_his + self.volume_short_frozen_today @property def margin(self): return self.margin_long + self.margin_short @property def float_profit_long(self): if self.market_preset is not None: return self.last_price * self.volume_long * self.market_preset.get( 'unit_table', 1) - self.open_cost_long @property def float_profit_short(self): if self.market_preset is not None: return self.open_cost_short - self.last_price * self.volume_short * self.market_preset.get( 'unit_table', 1) @property def float_profit(self): return self.float_profit_long + self.float_profit_short @property def position_profit_long(self): if self.market_preset is not None: return self.last_price * self.volume_long * self.market_preset.get( 'unit_table', 1) - self.position_cost_long @property def position_profit_short(self): if self.market_preset is not None: return self.position_cost_short - self.last_price * self.volume_short * self.market_preset.get( 'unit_table', 1) @property def position_profit(self): return self.position_profit_long + self.position_profit_short @property def static_message(self): return { # 基础字段 'code': self.code, # 品种名称 'instrument_id': self.code, 'user_id': self.account_cookie, 'portfolio_cookie': self.portfolio_cookie, 'username': self.username, 'position_id': self.position_id, 'account_cookie': self.account_cookie, 'frozen': self.frozen, 'name': self.name, 'spms_id': self.spms_id, 'oms_id': self.oms_id, 'market_type': self.market_type, 'exchange_id': self.exchange_id, # 交易所ID 'moneypreset': self.moneypreset, 'moneypresetLeft': self.moneypresetLeft, 'lastupdatetime': str(self.time), # 持仓量 'volume_long_today': self.volume_long_today, 'volume_long_his': self.volume_long_his, 'volume_long': self.volume_long, 'volume_short_today': self.volume_short_today, 'volume_short_his': self.volume_short_his, 'volume_short': self.volume_short, # 平仓委托冻结(未成交) 'volume_long_frozen_today': self.volume_long_frozen_today, 'volume_long_frozen_his': self.volume_long_frozen_his, 'volume_long_frozen': self.volume_long_frozen, 'volume_short_frozen_today': self.volume_short_frozen_today, 'volume_short_frozen_his': self.volume_short_frozen_his, 'volume_short_frozen': self.volume_short_frozen, # 保证金 'margin_long': self.margin_long, # 多头保证金 'margin_short': self.margin_short, 'margin': self.margin, # 持仓字段 'position_price_long': self.position_price_long, # 多头成本价 'position_cost_long': self.position_cost_long, # 多头总成本( 总市值) 'position_price_short': self.position_price_short, 'position_cost_short': self.position_cost_short, # 平仓字段 'open_price_long': self.open_price_long, # 多头开仓价 'open_cost_long': self.open_cost_long, # 多头开仓成本 'open_price_short': self.open_price_short, # 空头开仓价 'open_cost_short': self.open_cost_short, # 空头成本 # 历史字段 'trades': self.trades, 'orders': self.orders } @property def hold_detail(self): return { # 持仓量 'volume_long_today': self.volume_long_today, 'volume_long_his': self.volume_long_his, 'volume_long': self.volume_long, 'volume_short_today': self.volume_short_today, 'volume_short_his': self.volume_short_his, 'volume_short': self.volume_short } @property def realtime_message(self): return { # 扩展字段 "last_price": self.last_price, # //多头浮动盈亏 ps.last_price * ps.volume_long * ps.ins->volume_multiple - ps.open_cost_long; "float_profit_long": self.float_profit_long, # //空头浮动盈亏 ps.open_cost_short - ps.last_price * ps.volume_short * ps.ins->volume_multiple; "float_profit_short": self.float_profit_short, # //浮动盈亏 = float_profit_long + float_profit_short "float_profit": self.float_profit, "position_profit_long": self.position_profit_long, # //多头持仓盈亏 "position_profit_short": self.position_profit_short, # //空头持仓盈亏 # //持仓盈亏 = position_profit_long + position_profit_short "position_profit": self.position_profit } @property def message(self): msg = self.static_message msg.update(self.realtime_message) return msg def order_check(self, amount: float, price: float, towards: int, order_id: str) -> bool: res = False if towards == ORDER_DIRECTION.BUY_CLOSE: # print('buyclose') #print(self.volume_short - self.volume_short_frozen) # print(amount) if (self.volume_short - self.volume_short_frozen) >= amount: # check self.volume_short_frozen_today += amount res = True else: print('BUYCLOSE 仓位不足') elif towards == ORDER_DIRECTION.BUY_CLOSETODAY: if (self.volume_short_today - self.volume_short_frozen_today) >= amount: self.volume_short_frozen_today += amount res = True else: print('BUYCLOSETODAY 今日仓位不足') elif towards == ORDER_DIRECTION.SELL_CLOSE: # print('sellclose') #print(self.volume_long - self.volume_long_frozen) # print(amount) if (self.volume_long - self.volume_long_frozen) >= amount: self.volume_long_frozen_today += amount res = True else: print('SELL CLOSE 仓位不足') elif towards == ORDER_DIRECTION.SELL_CLOSETODAY: if (self.volume_long_today - self.volume_short_frozen_today) >= amount: # print('sellclosetoday') #print(self.volume_long_today - self.volume_long_frozen) # print(amount) self.volume_long_frozen_today += amount return True else: print('SELLCLOSETODAY 今日仓位不足') elif towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.SELL_OPEN, ORDER_DIRECTION.BUY ]: """ 冻结的保证金 """ moneyneed = float(amount) * float(price) * float( self.market_preset.get('unit_table', 1)) * float( self.market_preset.get('buy_frozen_coeff', 1)) if (self.moneypresetLeft > moneyneed) or self.allow_exceed: self.moneypresetLeft -= moneyneed self.frozen[order_id] = moneyneed res = True else: print('开仓保证金不足 TOWARDS{} Need{} HAVE{}'.format( towards, moneyneed, self.moneypresetLeft)) return res def send_order(self, amount: float, price: float, towards: int): order_id = str(uuid.uuid4()) if self.order_check(amount, price, towards, order_id): #print('order check success') order = { 'position_id': str(self.position_id), 'account_cookie': self.account_cookie, 'instrument_id': self.code, 'towards': int(towards), 'exchange_id': str(self.exchange_id), 'order_time': str(self.time), 'volume': float(amount), 'price': float(price), 'order_id': order_id, 'status': ORDER_STATUS.NEW } self.orders[order_id] = order return order else: print(RuntimeError('ORDER CHECK FALSE: {}'.format(self.code))) return False def update_pos(self, price, amount, towards): """支持股票/期货的更新仓位 Arguments: price {[type]} -- [description] amount {[type]} -- [description] towards {[type]} -- [description] margin: 30080 margin_long: 0 margin_short: 30080 open_cost_long: 0 open_cost_short: 419100 open_price_long: 4193 open_price_short: 4191 position_cost_long: 0 position_cost_short: 419100 position_price_long: 4193 position_price_short: 4191 position_profit: -200 position_profit_long: 0 position_profit_short: -200 """ self.on_pirce_change(price) temp_cost = float(amount)*float(price) * \ float(self.market_preset.get('unit_table', 1)) profit = 0 if towards == ORDER_DIRECTION.BUY: # 股票模式/ 期货买入开仓 marginValue = temp_cost self.margin_long += marginValue # 重算开仓均价 self.open_price_long = (self.open_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 重算持仓均价 self.position_price_long = ( self.position_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 增加今仓数量 ==> 会自动增加volume_long self.volume_long_today += amount # self.open_cost_long += temp_cost self.position_cost_long += temp_cost elif towards == ORDER_DIRECTION.SELL: # 股票卖出模式: # 今日买入仓位不能卖出 if self.volume_long_his > amount: self.position_cost_long = self.position_cost_long * \ (self.volume_long - amount)/self.volume_long self.open_cost_long = self.open_cost_long * \ (self.volume_long-amount)/self.volume_long self.volume_long_his -= amount self.volume_long_frozen_today -= amount marginValue = -1 * (self.position_price_long * amount) profit = (price - self.position_price_long) * amount self.moneypresetLeft += (-marginValue + profit) elif towards == ORDER_DIRECTION.BUY_OPEN: # 增加保证金 marginValue = temp_cost * \ self.market_preset['buy_frozen_coeff'] self.margin_long += marginValue # 重算开仓均价 self.open_price_long = (self.open_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 重算持仓均价 self.position_price_long = ( self.position_price_long * self.volume_long + amount * price) / (amount + self.volume_long) # 增加今仓数量 ==> 会自动增加volume_long self.volume_long_today += amount # self.open_cost_long += temp_cost self.position_cost_long += temp_cost self.moneypresetLeft -= marginValue elif towards == ORDER_DIRECTION.SELL_OPEN: # 增加保证金 """ 1. 增加卖空保证金 2. 重新计算 开仓成本 3. 重新计算 持仓成本 4. 增加开仓cost 5. 增加持仓cost 6. 增加空单仓位 """ marginValue = temp_cost * \ self.market_preset['sell_frozen_coeff'] self.margin_short += marginValue # 重新计算开仓/持仓成本 self.open_price_short = ( self.open_price_short * self.volume_short + amount * price) / (amount + self.volume_short) self.position_price_short = ( self.position_price_short * self.volume_short + amount * price) / (amount + self.volume_short) self.open_cost_short += temp_cost self.position_cost_short += temp_cost self.volume_short_today += amount self.moneypresetLeft -= marginValue elif towards == ORDER_DIRECTION.BUY_CLOSETODAY: if self.volume_short_today > amount: self.position_cost_short = self.position_cost_short * \ (self.volume_short-amount)/self.volume_short self.open_cost_short = self.open_cost_short * \ (self.volume_short-amount)/self.volume_short self.volume_short_today -= amount self.volume_short_frozen_today += amount # close_profit = (self.position_price_short - price) * volume * position->ins->volume_multiple; marginValue = -(self.position_price_short * amount*self.market_preset.get('unit_table') *\ self.market_preset['sell_frozen_coeff']) profit = (self.position_price_short - price ) * amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) # 释放保证金 # TODO # self.margin_short #self.open_cost_short = price* amount elif towards == ORDER_DIRECTION.SELL_CLOSETODAY: if self.volume_long_today > amount: self.position_cost_long = self.position_cost_long * \ (self.volume_long - amount)/self.volume_long self.open_cost_long = self.open_cost_long * \ (self.volume_long-amount)/self.volume_long self.volume_long_today -= amount self.volume_long_frozen_today += amount marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\ self.market_preset['buy_frozen_coeff']) profit = (price - self.position_price_long) * \ amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) elif towards == ORDER_DIRECTION.BUY_CLOSE: # 有昨仓先平昨仓 self.position_cost_short = self.position_cost_short * \ (self.volume_short-amount)/self.volume_short self.open_cost_short = self.open_cost_short * \ (self.volume_short-amount)/self.volume_short if self.volume_short_his >= amount: self.volume_short_his -= amount else: self.volume_short_today -= (amount - self.volume_short_his) self.volume_short_his = 0 self.volume_short_frozen_today -= amount marginValue = -1*(self.position_price_short * amount*self.market_preset.get('unit_table') *\ self.market_preset['sell_frozen_coeff']) profit = (self.position_price_short - price) * amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) elif towards == ORDER_DIRECTION.SELL_CLOSE: # 有昨仓先平昨仓 self.position_cost_long = self.position_cost_long * \ (self.volume_long - amount)/self.volume_long self.open_cost_long = self.open_cost_long * \ (self.volume_long-amount)/self.volume_long if self.volume_long_his >= amount: self.volume_long_his -= amount else: self.volume_long_today -= (amount - self.volume_long_his) self.volume_long_his = 0 self.volume_long_frozen_today -= amount marginValue = -1*(self.position_price_long * amount*self.market_preset.get('unit_table') *\ self.market_preset['buy_frozen_coeff']) profit = (price - self.position_price_long) * \ amount * self.market_preset.get('unit_table') self.moneypresetLeft += (-marginValue + profit) # 计算收益/成本 return marginValue, profit def settle(self): """收盘后的结算事件 """ self.volume_long_his += self.volume_long_today self.volume_long_today = 0 self.volume_long_frozen_today = 0 self.volume_short_his += self.volume_short_today self.volume_short_today = 0 self.volume_short_frozen_today = 0 @property def curpos(self): return { 'volume_long': self.volume_long, 'volume_short': self.volume_short, 'volume_long_frozen': self.volume_long_frozen, 'volume_short_frozen': self.volume_short_frozen } @property def close_available(self): """可平仓数量 Returns: [type] -- [description] """ return { 'volume_long': self.volume_long - self.volume_long_frozen, 'volume_short': self.volume_short - self.volume_short_frozen } def change_moneypreset(self, money): self.moneypreset = money def save(self): """save&update save data to mongodb | update """ print(self.static_message) save_position(self.static_message) def reload(self): res = DATABASE.positions.find_one({ 'account_cookie': self.account_cookie, 'portfolio_cookie': self.portfolio_cookie, 'username': self.username, 'position_id': self.position_id }) if res is None: self.save() else: self.loadfrommessage(res) def calc_commission( self, trade_price, trade_amount, trade_towards, ): if self.market_type == MARKET_TYPE.FUTURE_CN: # 期货不收税 # 双边手续费 也没有最小手续费限制 value = trade_price * trade_amount * \ self.market_preset.get_unit(code) commission_fee_preset = self.market_preset.get_code(code) if trade_towards in [ ORDER_DIRECTION.BUY_OPEN, ORDER_DIRECTION.BUY_CLOSE, ORDER_DIRECTION.SELL_CLOSE, ORDER_DIRECTION.SELL_OPEN ]: commission_fee = commission_fee_preset['commission_coeff_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_peramount'] * \ abs(value) elif trade_towards in [ ORDER_DIRECTION.BUY_CLOSETODAY, ORDER_DIRECTION.SELL_CLOSETODAY ]: commission_fee = commission_fee_preset['commission_coeff_today_pervol'] * trade_amount + \ commission_fee_preset['commission_coeff_today_peramount'] * \ abs(value) return commission_fee def loadfrommessage(self, message): self.__init__( code=message['code'], account_cookie=message['account_cookie'], frozen=message['frozen'], portfolio_cookie=message['portfolio_cookie'], username=message['username'], moneypreset=message['moneypreset'], # 初始分配资金 moneypresetLeft=message['moneypresetLeft'], volume_long_today=message['volume_long_today'], volume_long_his=message['volume_long_his'], volume_short_today=message['volume_short_today'], volume_short_his=message['volume_short_his'], volume_long_frozen_his=message['volume_long_frozen_his'], volume_long_frozen_today=message['volume_long_frozen_today'], volume_short_frozen_his=message['volume_short_frozen_his'], volume_short_frozen_today=message['volume_short_frozen_today'], margin_long=message['margin_long'], margin_short=message['margin_short'], open_price_long=message['open_price_long'], open_price_short=message['open_price_short'], # 逐日盯市的前一交易日的结算价 position_price_long=message['position_price_long'], # 逐日盯市的前一交易日的结算价 position_price_short=message['position_price_short'], open_cost_long=message['open_cost_long'], open_cost_short=message['open_cost_short'], position_cost_long=message['position_cost_long'], position_cost_short=message['position_cost_short'], position_id=message['position_id'], market_type=message['market_type'], exchange_id=message['exchange_id'], trades=message['trades'], orders=message['orders'], commission=message['commission'], name=message['name']) return self def on_order(self, order: QA_Order): """这里是一些外部操作导致的POS变化 - 交易过程的外部手动交易 - 风控状态下的监控外部交易 order_id 是外部的 trade_id 不一定存在 """ if order['order_id'] not in self.frozen.keys(): print('OUTSIDE ORDER') # self.frozen[order['order_id']] = order[] # 回放订单/注册进订单系统 order = self.send_order( order.get('amount', order.get('volume')), order['price'], eval('ORDER_DIRECTION.{}_{}'.format(order.get('direction'), order.get('offset')))) self.orders[order]['status'] = ORDER_STATUS.QUEUED def on_transaction(self, transaction: dict): towards = transaction.get( 'towards', eval('ORDER_DIRECTION.{}_{}'.format(transaction.get('direction'), transaction.get('offset')))) transaction['towards'] = towards # TODO: # 在这里可以加入更多关于PMS交易的代码 try: self.update_pos( transaction['price'], transaction.get('amount', transaction.get('volume')), towards) self.moneypresetLeft += self.frozen.get(transaction['order_id'], 0) # 当出现外部交易的时候, 直接在frozen中注册订单 self.frozen[transaction['order_id']] = 0 self.orders[transaction['order_id']] = ORDER_STATUS.SUCCESS_ALL self.trades.append(transaction) except Exception as e: raise e def on_pirce_change(self, price): self.last_price = price def on_bar(self, bar): """只订阅这个code的数据 Arguments: bar {[type]} -- [description] """ self.last_price = bar['close'] # print(self.realtime_message) pass def on_tick(self, tick): """只订阅当前code的tick Arguments: tick {[type]} -- [description] """ self.last_price = tick['LastPrice'] # print(self.realtime_message) pass def on_signal(self, signal): raise NotImplementedError('此接口为内部接口 为CEP专用') def callback_sub(self): raise NotImplementedError('此接口为内部接口 为CEP专用') def callback_pub(self): raise NotImplementedError('此接口为内部接口 为CEP专用')