class FutureAccount(AssetAccount): forced_liquidation = True __abandon_properties__ = [ "holding_pnl", "realized_pnl", ] def fast_forward(self, orders, trades=None): # 计算 Positions if trades: close_trades = [] # 先处理开仓 for trade in trades: if trade.exec_id in self._backward_trade_set: continue if trade.position_effect == POSITION_EFFECT.OPEN: self._apply_trade(trade) else: close_trades.append(trade) # 后处理平仓 for trade in close_trades: self._apply_trade(trade) # 计算 Frozen Cash self._frozen_cash = sum( self._frozen_cash_of_order(order) for order in orders if order.is_active()) def order(self, order_book_id, quantity, style, target=False): position = self.positions[order_book_id] if target: # For order_to quantity = quantity - position.buy_quantity + position.sell_quantity orders = [] if quantity > 0: sell_old_quantity, sell_today_quantity = position.sell_old_quantity, position.sell_today_quantity # 平昨仓 if sell_old_quantity > 0: orders.append( order(order_book_id, min(quantity, sell_old_quantity), SIDE.BUY, POSITION_EFFECT.CLOSE, style)) quantity -= sell_old_quantity if quantity <= 0: return orders # 平今仓 if sell_today_quantity > 0: orders.append( order(order_book_id, min(quantity, sell_today_quantity), SIDE.BUY, POSITION_EFFECT.CLOSE_TODAY, style)) quantity -= sell_today_quantity if quantity <= 0: return orders # 开多仓 orders.append( order(order_book_id, quantity, SIDE.BUY, POSITION_EFFECT.OPEN, style)) return orders else: # 平昨仓 quantity *= -1 buy_old_quantity, buy_today_quantity = position.buy_old_quantity, position.buy_today_quantity if buy_old_quantity > 0: orders.append( order(order_book_id, min(quantity, buy_old_quantity), SIDE.SELL, POSITION_EFFECT.CLOSE, style)) quantity -= min(quantity, buy_old_quantity) if quantity <= 0: return orders # 平今仓 if buy_today_quantity > 0: orders.append( order(order_book_id, min(quantity, buy_today_quantity), SIDE.SELL, POSITION_EFFECT.CLOSE_TODAY, style)) quantity -= buy_today_quantity if quantity <= 0: return orders # 开空仓 orders.append( order(order_book_id, quantity, SIDE.SELL, POSITION_EFFECT.OPEN, style)) return orders def _on_order_pending_new(self, event): if self != event.account: return self._frozen_cash += self._frozen_cash_of_order(event.order) def _on_order_unsolicited_update(self, event): if self != event.account: return order = event.order if order.filled_quantity != 0: self._frozen_cash -= order.unfilled_quantity / order.quantity * self._frozen_cash_of_order( order) else: self._frozen_cash -= self._frozen_cash_of_order(event.order) def _on_trade(self, event): if self != event.account: return self._apply_trade(event.trade, event.order) def _on_settlement(self, event): self._static_total_value = self.total_value for position in list(self._positions.values()): order_book_id = position.order_book_id if position.is_de_listed( ) and position.buy_quantity + position.sell_quantity != 0: user_system_log.warn( _(u"{order_book_id} is expired, close all positions by system" ).format(order_book_id=order_book_id)) del self._positions[order_book_id] elif position.buy_quantity == 0 and position.sell_quantity == 0: del self._positions[order_book_id] else: position.apply_settlement() # 如果 total_value <= 0 则认为已爆仓,清空仓位,资金归0 if self._static_total_value <= 0 and self.forced_liquidation: if self._positions: user_system_log.warn( _("Trigger Forced Liquidation, current total_value is 0")) self._positions.clear() self._static_total_value = 0 self._backward_trade_set.clear() def _on_before_trading(self, event): pass @property def type(self): return DEFAULT_ACCOUNT_TYPE.FUTURE.name @staticmethod def _frozen_cash_of_order(order): order_cost = margin_of( order.order_book_id, order.quantity, order.frozen_price ) if order.position_effect == POSITION_EFFECT.OPEN else 0 return order_cost + Environment.get_instance( ).get_order_transaction_cost(DEFAULT_ACCOUNT_TYPE.FUTURE, order) def _apply_trade(self, trade, order=None): if trade.exec_id in self._backward_trade_set: return order_book_id = trade.order_book_id position = self._positions.get_or_create(order_book_id) position.apply_trade(trade) position.update_last_price() self._backward_trade_set.add(trade.exec_id) if order: if trade.last_quantity != order.quantity: self._frozen_cash -= trade.last_quantity / order.quantity * self._frozen_cash_of_order( order) else: self._frozen_cash -= self._frozen_cash_of_order(order) @property def buy_margin(self): """ [float] 多方向保证金 """ return sum(position.buy_margin for position in six.itervalues(self._positions)) @property def sell_margin(self): """ [float] 空方向保证金 """ return sum(position.sell_margin for position in six.itervalues(self._positions)) # deprecated propertie holding_pnl = deprecated_property("holding_pnl", "position_pnl") realized_pnl = deprecated_property("realized_pnl", "trading_pnl")
class FuturePositionProxy(PositionProxy): __abandon_properties__ = PositionProxy.__abandon_properties__ + [ "holding_pnl", "buy_holding_pnl", "sell_holding_pnl", "realized_pnl", "buy_realized_pnl", "sell_realized_pnl", "buy_avg_holding_price", "sell_avg_holding_price" ] @property def type(self): return "FUTURE" @property def margin_rate(self): return self._long.margin_rate @property def contract_multiplier(self): return self._long.contract_multiplier @property def buy_market_value(self): """ [float] 多方向市值 """ return self._long.market_value @property def sell_market_value(self): """ [float] 空方向市值 """ return self._short.market_value @property def buy_position_pnl(self): """ [float] 多方向昨仓盈亏 """ return self._long.position_pnl @property def sell_position_pnl(self): """ [float] 空方向昨仓盈亏 """ return self._short.position_pnl @property def buy_trading_pnl(self): """ [float] 多方向交易盈亏 """ return self._long.trading_pnl @property def sell_trading_pnl(self): """ [float] 空方向交易盈亏 """ return self._short.trading_pnl @property def buy_daily_pnl(self): """ [float] 多方向每日盈亏 """ return self.buy_position_pnl + self.buy_trading_pnl @property def sell_daily_pnl(self): """ [float] 空方向每日盈亏 """ return self.sell_position_pnl + self.sell_trading_pnl @property def buy_pnl(self): """ [float] 买方向累计盈亏 """ return self._long.pnl @property def sell_pnl(self): """ [float] 空方向累计盈亏 """ return self._short.pnl @property def buy_old_quantity(self): """ [int] 多方向昨仓 """ return self._long.old_quantity @property def sell_old_quantity(self): """ [int] 空方向昨仓 """ return self._short.old_quantity @property def buy_today_quantity(self): """ [int] 多方向今仓 """ return self._long.today_quantity @property def sell_today_quantity(self): """ [int] 空方向今仓 """ return self._short.today_quantity @property def buy_quantity(self): """ [int] 多方向持仓 """ return self.buy_old_quantity + self.buy_today_quantity @property def sell_quantity(self): """ [int] 空方向持仓 """ return self.sell_old_quantity + self.sell_today_quantity @property def margin(self): """ [float] 保证金 保证金 = 持仓量 * 最新价 * 合约乘数 * 保证金率 股票保证金 = 市值 = 持仓量 * 最新价 """ return self._long.margin + self._short.margin @property def buy_margin(self): """ [float] 多方向持仓保证金 """ return self._long.margin @property def sell_margin(self): """ [float] 空方向持仓保证金 """ return self._short.margin @property def buy_avg_open_price(self): """ [float] 多方向平均开仓价格 """ return self._long.avg_price @property def sell_avg_open_price(self): """ [float] 空方向平均开仓价格 """ return self._short.avg_price @property def buy_transaction_cost(self): """ [float] 多方向交易费率 """ return self._long.transaction_cost @property def sell_transaction_cost(self): """ [float] 空方向交易费率 """ return self._short.transaction_cost @property def closable_today_sell_quantity(self): return self._long.today_closable @property def closable_today_buy_quantity(self): return self._long.today_closable @property def closable_buy_quantity(self): """ [float] 可平多方向持仓 """ return self._long.closable @property def closable_sell_quantity(self): """ [float] 可平空方向持仓 """ return self._short.closable holding_pnl = deprecated_property("holding_pnl", "position_pnl") buy_holding_pnl = deprecated_property("buy_holding_pnl", "buy_position_pnl") sell_holding_pnl = deprecated_property("sell_holding_pnl", "sell_position_pnl") realized_pnl = deprecated_property("realized_pnl", "trading_pnl") buy_realized_pnl = deprecated_property("buy_realized_pnl", "buy_trading_pnl") sell_realized_pnl = deprecated_property("sell_realized_pnl", "sell_trading_pnl") buy_avg_holding_price = deprecated_property("buy_avg_holding_price", "buy_avg_open_price") sell_avg_holding_price = deprecated_property("sell_avg_holding_price", "sell_avg_open_price")
class Account(AbstractAccount): """ 账户,多种持仓和现金的集合。 不同品种的合约持仓可能归属于不同的账户,如股票、转债、场内基金、ETF 期权归属于股票账户,期货、期货期权归属于期货账户 """ __abandon_properties__ = [ "holding_pnl", "realized_pnl", "dividend_receivable", ] _position_types = {} # type: Dict[INSTRUMENT_TYPE, PositionType] def __init__(self, type, total_cash, init_positions): # type: (str, float, Dict[str, int]) -> None self._type = type self._total_cash = total_cash # 包含保证金的总资金 self._positions = {} self._backward_trade_set = set() self._frozen_cash = 0 self.register_event() for order_book_id, init_quantity in six.iteritems(init_positions): position_direction = POSITION_DIRECTION.LONG if init_quantity > 0 else POSITION_DIRECTION.SHORT self._get_or_create_pos(order_book_id, position_direction, init_quantity) def __repr__(self): positions_repr = {} for order_book_id, positions in six.iteritems(self._positions): for direction, position in six.iteritems(positions): if position.quantity != 0: positions_repr.setdefault( order_book_id, {})[direction.value] = position.quantity return "Account(cash={}, total_value={}, positions={})".format( self.cash, self.total_value, positions_repr) @classmethod def register_position_type(cls, instrument_type, position_type): # type: (INSTRUMENT_TYPE, PositionType) -> None # TODO: only can called before instantiated cls._position_types[instrument_type] = position_type def register_event(self): event_bus = Environment.get_instance().event_bus event_bus.add_listener( EVENT.TRADE, lambda e: self.apply_trade(e.trade, e.order) if e.account == self else None) event_bus.add_listener(EVENT.ORDER_PENDING_NEW, self._on_order_pending_new) event_bus.add_listener(EVENT.ORDER_CREATION_REJECT, self._on_order_unsolicited_update) event_bus.add_listener(EVENT.ORDER_UNSOLICITED_UPDATE, self._on_order_unsolicited_update) event_bus.add_listener(EVENT.ORDER_CANCELLATION_PASS, self._on_order_unsolicited_update) event_bus.add_listener(EVENT.PRE_BEFORE_TRADING, self._on_before_trading) event_bus.add_listener(EVENT.SETTLEMENT, self._on_settlement) event_bus.prepend_listener(EVENT.BAR, self._update_last_price) event_bus.prepend_listener(EVENT.TICK, self._update_last_price) def get_state(self): return { 'positions': { order_book_id: { "long": positions[POSITION_DIRECTION.LONG].get_state(), "short": positions[POSITION_DIRECTION.SHORT].get_state() } for order_book_id, positions in six.iteritems(self._positions) }, 'frozen_cash': self._frozen_cash, "total_cash": self._total_cash, 'backward_trade_set': list(self._backward_trade_set), } def set_state(self, state): self._frozen_cash = state['frozen_cash'] self._backward_trade_set = set(state['backward_trade_set']) self._positions.clear() for order_book_id, positions_state in six.iteritems( state['positions']): for direction in POSITION_DIRECTION: state = positions_state[direction] position = self._get_or_create_pos(order_book_id, direction) position.set_state(state) if "total_cash" in state: self._total_cash = state["total_cash"] else: # forward compatible total_cash = state["static_total_valueu"] for p in self._iter_pos(): if p._instrument.type == INSTRUMENT_TYPE.FUTURE: continue # FIXME: not exactly right try: total_cash -= p.equity except RuntimeError: total_cash -= p.prev_close * p.quantity # forward compatible if "dividend_receivable" in state: for order_book_id, dividend in six.iteritems( state["dividend_receivable"]): self._get_or_create_pos( order_book_id, POSITION_DIRECTION.LONG)._dividend_receivable = ( dividend["payable_date"], dividend["quantity"] * dividend["dividend_per_share"]) if "pending_transform" in state: for order_book_id, transform_info in six.iteritems( state["pending_transform"]): self._get_or_create_pos(order_book_id, POSITION_DIRECTION.LONG )._pending_transform = transform_info def fast_forward(self, orders=None, trades=None): if trades: close_trades = [] # 先处理开仓 for trade in trades: if trade.exec_id in self._backward_trade_set: continue if trade.position_effect == POSITION_EFFECT.OPEN: self.apply_trade(trade) else: close_trades.append(trade) # 后处理平仓 for trade in close_trades: self.apply_trade(trade) # 计算 Frozen Cash if orders: self._frozen_cash = sum( self._frozen_cash_of_order(order) for order in orders if order.is_active()) def get_positions(self): # type: () -> Iterable[BasePosition] """ 获取所有持仓对象列表, """ return self._iter_pos() def get_position(self, order_book_id, direction): # type: (str, POSITION_DIRECTION) -> BasePosition """ 获取某个标的的持仓对象 :param order_book_id: 标的编号 :param direction: 持仓方向 """ try: return self._positions[order_book_id][direction] except KeyError: instrument_type = Environment.get_instance( ).data_proxy.instruments(order_book_id).type position_type = self._position_types.get(instrument_type, BasePosition) return position_type(order_book_id, direction) def calc_close_today_amount(self, order_book_id, trade_amount, position_direction): return self._get_or_create_pos( order_book_id, position_direction).calc_close_today_amount(trade_amount) @property def type(self): return self._type @property @lru_cache(None) def positions(self): return PositionProxyDict(self._positions, self._position_types) @property def frozen_cash(self): # type: () -> float """ 冻结资金 """ return self._frozen_cash @property def cash(self): # type: () -> float """ 可用资金 """ return self._total_cash - self.margin - self._frozen_cash @property def market_value(self): # type: () -> float """ [float] 市值 """ return sum(p.market_value * (1 if p.direction == POSITION_DIRECTION.LONG else -1) for p in self._iter_pos()) @property def transaction_cost(self): # type: () -> float """ 总费用 """ return sum(p.transaction_cost for p in self._iter_pos()) @property def margin(self): # type: () -> float """ 总保证金 """ return sum(p.margin for p in self._iter_pos()) @property def buy_margin(self): # type: () -> float """ 多方向保证金 """ return sum(p.margin for p in self._iter_pos(POSITION_DIRECTION.LONG)) @property def sell_margin(self): # type: () -> float """ 空方向保证金 """ return sum(p.margin for p in self._iter_pos(POSITION_DIRECTION.SHORT)) @property def daily_pnl(self): # type: () -> float """ 当日盈亏 """ return self.trading_pnl + self.position_pnl - self.transaction_cost @property def equity(self): # type: () -> float """ 总权益 """ return sum(p.equity for p in self._iter_pos()) @property def total_value(self): # type: () -> float """ 账户总权益 """ return self._total_cash + self.equity @property def total_cash(self): # type: () -> float """ 账户总资金 """ return self._total_cash - self.margin @property def position_pnl(self): # type: () -> float """ 昨仓盈亏 """ return sum(p.position_pnl for p in self._iter_pos()) @property def trading_pnl(self): # type: () -> float """ 交易盈亏 """ return sum(p.trading_pnl for p in self._iter_pos()) def position_validator_enabled(self, order_book_id): # type: (str) -> bool return self._get_or_create_pos( order_book_id, POSITION_DIRECTION.LONG).position_validator_enabled def _on_before_trading(self, _): trading_date = Environment.get_instance().trading_dt.date() for position in self._iter_pos(): self._total_cash += position.before_trading(trading_date) def _on_settlement(self, _): trading_date = Environment.get_instance().trading_dt.date() for order_book_id, positions in list(six.iteritems(self._positions)): for position in six.itervalues(positions): delta_cash = position.settlement(trading_date) self._total_cash += delta_cash for order_book_id, positions in list(six.iteritems(self._positions)): if all(p.quantity == 0 for p in six.itervalues(positions)): del self._positions[order_book_id] self._backward_trade_set.clear() # 如果 total_value <= 0 则认为已爆仓,清空仓位,资金归0 forced_liquidation = Environment.get_instance( ).config.base.forced_liquidation if self.total_value <= 0 and forced_liquidation: if self._positions: user_system_log.warn( _("Trigger Forced Liquidation, current total_value is 0")) self._positions.clear() self._total_cash = 0 def _on_order_pending_new(self, event): if event.account != self: return order = event.order self._frozen_cash += self._frozen_cash_of_order(order) def _on_order_unsolicited_update(self, event): if event.account != self: return order = event.order if order.filled_quantity != 0: self._frozen_cash -= order.unfilled_quantity / order.quantity * self._frozen_cash_of_order( order) else: self._frozen_cash -= self._frozen_cash_of_order(event.order) def apply_trade(self, trade, order=None): # type: (Trade, Optional[Order]) -> None if trade.exec_id in self._backward_trade_set: return order_book_id = trade.order_book_id if trade.position_effect == POSITION_EFFECT.MATCH: self._total_cash += self._get_or_create_pos( order_book_id, POSITION_DIRECTION.LONG).apply_trade(trade) self._total_cash += self._get_or_create_pos( order_book_id, POSITION_DIRECTION.SHORT).apply_trade(trade) else: self._total_cash += self._get_or_create_pos( order_book_id, trade.position_direction).apply_trade(trade) self._backward_trade_set.add(trade.exec_id) if order and trade.position_effect != POSITION_EFFECT.MATCH: if trade.last_quantity != order.quantity: self._frozen_cash -= trade.last_quantity / order.quantity * self._frozen_cash_of_order( order) else: self._frozen_cash -= self._frozen_cash_of_order(order) def _iter_pos(self, direction=None): # type: (Optional[POSITION_DIRECTION]) -> Iterable[BasePosition] if direction: return (p[direction] for p in six.itervalues(self._positions)) else: return chain( *[six.itervalues(p) for p in six.itervalues(self._positions)]) def _get_or_create_pos(self, order_book_id, direction, init_quantity=0): # type: (str, Union[str, POSITION_DIRECTION], Optional[int]) -> BasePosition if order_book_id not in self._positions: instrument_type = Environment.get_instance( ).data_proxy.instruments(order_book_id).type position_type = self._position_types.get(instrument_type, BasePosition) if direction == POSITION_DIRECTION.LONG: long_init_position, short_init_position = init_quantity, 0 else: long_init_position, short_init_position = 0, init_quantity positions = self._positions.setdefault( order_book_id, { POSITION_DIRECTION.LONG: position_type(order_book_id, POSITION_DIRECTION.LONG, long_init_position), POSITION_DIRECTION.SHORT: position_type(order_book_id, POSITION_DIRECTION.SHORT, short_init_position) }) else: positions = self._positions[order_book_id] return positions[direction] def _update_last_price(self, _): env = Environment.get_instance() for order_book_id, positions in six.iteritems(self._positions): price = env.get_last_price(order_book_id) if price == price: for position in six.itervalues(positions): position.update_last_price(price) def _frozen_cash_of_order(self, order): env = Environment.get_instance() if order.position_effect == POSITION_EFFECT.OPEN: instrument = env.data_proxy.instruments(order.order_book_id) order_cost = instrument.calc_cash_occupation( order.frozen_price, order.quantity, order.position_direction) else: order_cost = 0 return order_cost + env.get_order_transaction_cost(order) holding_pnl = deprecated_property("holding_pnl", "position_pnl") realized_pnl = deprecated_property("realized_pnl", "trading_pnl")
class Account: """ 账户,多种持仓和现金的集合。 不同品种的合约持仓可能归属于不同的账户,如股票、转债、场内基金、ETF 期权归属于股票账户,期货、期货期权归属于期货账户 """ __abandon_properties__ = [ "holding_pnl", "realized_pnl", "dividend_receivable", ] def __init__(self, type, total_cash, init_positions): # type: (str, float, Dict[str, int]) -> None self._type = type self._total_cash = total_cash # 包含保证金的总资金 self._positions = {} self._backward_trade_set = set() self._frozen_cash = 0 self.register_event() self._management_fee_calculator_func = lambda account, rate: account.total_value * rate self._management_fee_rate = 0.0 self._management_fees = 0.0 for order_book_id, init_quantity in init_positions.items(): position_direction = POSITION_DIRECTION.LONG if init_quantity > 0 else POSITION_DIRECTION.SHORT self._get_or_create_pos(order_book_id, position_direction, init_quantity) def __repr__(self): positions_repr = {} for order_book_id, positions in self._positions.items(): for direction, position in positions.items(): if position.quantity != 0: positions_repr.setdefault( order_book_id, {})[direction.value] = position.quantity return "Account(cash={}, total_value={}, positions={})".format( self.cash, self.total_value, positions_repr) def register_event(self): event_bus = Environment.get_instance().event_bus event_bus.add_listener( EVENT.TRADE, lambda e: self.apply_trade(e.trade, e.order) if e.account == self else None) event_bus.add_listener(EVENT.ORDER_PENDING_NEW, self._on_order_pending_new) event_bus.add_listener(EVENT.ORDER_CREATION_REJECT, self._on_order_unsolicited_update) event_bus.add_listener(EVENT.ORDER_UNSOLICITED_UPDATE, self._on_order_unsolicited_update) event_bus.add_listener(EVENT.ORDER_CANCELLATION_PASS, self._on_order_unsolicited_update) event_bus.add_listener(EVENT.PRE_BEFORE_TRADING, self._on_before_trading) event_bus.add_listener(EVENT.SETTLEMENT, self._on_settlement) event_bus.prepend_listener(EVENT.BAR, self._update_last_price) event_bus.prepend_listener(EVENT.TICK, self._update_last_price) def get_state(self): return { 'positions': { order_book_id: { POSITION_DIRECTION.LONG: positions[POSITION_DIRECTION.LONG].get_state(), POSITION_DIRECTION.SHORT: positions[POSITION_DIRECTION.SHORT].get_state() } for order_book_id, positions in self._positions.items() }, 'frozen_cash': self._frozen_cash, "total_cash": self._total_cash, 'backward_trade_set': list(self._backward_trade_set), } def set_state(self, state): self._frozen_cash = state['frozen_cash'] self._backward_trade_set = set(state['backward_trade_set']) self._total_cash = state["total_cash"] self._positions.clear() for order_book_id, positions_state in state['positions'].items(): for direction in POSITION_DIRECTION: position = self._get_or_create_pos(order_book_id, direction) if direction in positions_state.keys(): position.set_state(positions_state[direction]) else: position.set_state(positions_state[direction.lower()]) def fast_forward(self, orders=None, trades=None): if trades: close_trades = [] # 先处理开仓 for trade in trades: if trade.exec_id in self._backward_trade_set: continue if trade.position_effect == POSITION_EFFECT.OPEN: self.apply_trade(trade) else: close_trades.append(trade) # 后处理平仓 for trade in close_trades: self.apply_trade(trade) # 计算 Frozen Cash if orders: self._frozen_cash = sum( self._frozen_cash_of_order(order) for order in orders if order.is_active()) def get_positions(self): # type: () -> Iterable[Position] """ 获取所有持仓对象列表, """ return self._iter_pos() def get_position(self, order_book_id, direction): # type: (str, POSITION_DIRECTION) -> Position """ 获取某个标的的持仓对象 :param order_book_id: 标的编号 :param direction: 持仓方向 """ try: return self._positions[order_book_id][direction] except KeyError: return Position(order_book_id, direction) def calc_close_today_amount(self, order_book_id, trade_amount, position_direction): return self._get_or_create_pos( order_book_id, position_direction).calc_close_today_amount(trade_amount) @property def type(self): return self._type @property @lru_cache(None) def positions(self): return PositionProxyDict(self._positions) @property def frozen_cash(self): # type: () -> float """ 冻结资金 """ return self._frozen_cash @property def cash(self): # type: () -> float """ 可用资金 """ return self._total_cash - self.margin - self._frozen_cash @property def market_value(self): # type: () -> float """ [float] 市值 """ return sum(p.market_value * (1 if p.direction == POSITION_DIRECTION.LONG else -1) for p in self._iter_pos()) @property def transaction_cost(self): # type: () -> float """ 总费用 """ return sum(p.transaction_cost for p in self._iter_pos()) @property def margin(self): # type: () -> float """ 总保证金 """ return sum(p.margin for p in self._iter_pos()) @property def buy_margin(self): # type: () -> float """ 多方向保证金 """ return sum(p.margin for p in self._iter_pos(POSITION_DIRECTION.LONG)) @property def sell_margin(self): # type: () -> float """ 空方向保证金 """ return sum(p.margin for p in self._iter_pos(POSITION_DIRECTION.SHORT)) @property def daily_pnl(self): # type: () -> float """ 当日盈亏 """ return self.trading_pnl + self.position_pnl - self.transaction_cost @property def equity(self): # type: () -> float """ 持仓总权益 """ return sum(p.equity for p in self._iter_pos()) @property def total_value(self): # type: () -> float """ 账户总权益 """ return self._total_cash + self.equity @property def total_cash(self): # type: () -> float """ 账户总资金 """ return self._total_cash - self.margin @property def position_pnl(self): # type: () -> float """ 昨仓盈亏 """ return sum(p.position_pnl for p in self._iter_pos()) @property def trading_pnl(self): # type: () -> float """ 交易盈亏 """ return sum(p.trading_pnl for p in self._iter_pos()) def _on_before_trading(self, _): trading_date = Environment.get_instance().trading_dt.date() for position in self._iter_pos(): self._total_cash += position.before_trading(trading_date) def _on_settlement(self, event): trading_date = Environment.get_instance().trading_dt.date() for order_book_id, positions in list(self._positions.items()): for position in six.itervalues(positions): delta_cash = position.settlement(trading_date) self._total_cash += delta_cash for order_book_id, positions in list(self._positions.items()): if all(p.quantity == 0 and p.equity == 0 for p in six.itervalues(positions)): del self._positions[order_book_id] self._backward_trade_set.clear() fee = self._management_fee() self._management_fees += fee self._total_cash -= fee # 如果 total_value <= 0 则认为已爆仓,清空仓位,资金归0 forced_liquidation = Environment.get_instance( ).config.base.forced_liquidation if self.total_value <= 0 and forced_liquidation: if self._positions: user_system_log.warn( _("Trigger Forced Liquidation, current total_value is 0")) self._positions.clear() self._total_cash = 0 def _on_order_pending_new(self, event): if event.account != self: return order = event.order self._frozen_cash += self._frozen_cash_of_order(order) def _on_order_unsolicited_update(self, event): if event.account != self: return order = event.order if order.filled_quantity != 0: self._frozen_cash -= order.unfilled_quantity / order.quantity * self._frozen_cash_of_order( order) else: self._frozen_cash -= self._frozen_cash_of_order(event.order) def apply_trade(self, trade, order=None): # type: (Trade, Optional[Order]) -> None if trade.exec_id in self._backward_trade_set: return order_book_id = trade.order_book_id if order and trade.position_effect != POSITION_EFFECT.MATCH: if trade.last_quantity != order.quantity: self._frozen_cash -= trade.last_quantity / order.quantity * self._frozen_cash_of_order( order) else: self._frozen_cash -= self._frozen_cash_of_order(order) if trade.position_effect == POSITION_EFFECT.MATCH: delta_cash = self._get_or_create_pos( order_book_id, POSITION_DIRECTION.LONG ).apply_trade(trade) + self._get_or_create_pos( order_book_id, POSITION_DIRECTION.SHORT).apply_trade(trade) self._total_cash += delta_cash else: delta_cash = self._get_or_create_pos( order_book_id, trade.position_direction).apply_trade(trade) self._total_cash += delta_cash self._backward_trade_set.add(trade.exec_id) def _iter_pos(self, direction=None): # type: (Optional[POSITION_DIRECTION]) -> Iterable[Position] if direction: return (p[direction] for p in six.itervalues(self._positions)) else: return chain( *[six.itervalues(p) for p in six.itervalues(self._positions)]) def _get_or_create_pos(self, order_book_id, direction, init_quantity=0): # type: (str, Union[str, POSITION_DIRECTION], Optional[int]) -> Position if order_book_id not in self._positions: if direction == POSITION_DIRECTION.LONG: long_init_position, short_init_position = init_quantity, 0 else: long_init_position, short_init_position = 0, init_quantity positions = self._positions.setdefault( order_book_id, { POSITION_DIRECTION.LONG: Position(order_book_id, POSITION_DIRECTION.LONG, long_init_position), POSITION_DIRECTION.SHORT: Position(order_book_id, POSITION_DIRECTION.SHORT, short_init_position) }) else: positions = self._positions[order_book_id] return positions[direction] def _update_last_price(self, _): env = Environment.get_instance() for order_book_id, positions in self._positions.items(): price = env.get_last_price(order_book_id) if price == price: for position in six.itervalues(positions): position.update_last_price(price) def _frozen_cash_of_order(self, order): env = Environment.get_instance() if order.position_effect == POSITION_EFFECT.OPEN: instrument = env.data_proxy.instruments(order.order_book_id) order_cost = instrument.calc_cash_occupation( order.frozen_price, order.quantity, order.position_direction) else: order_cost = 0 return order_cost + env.get_order_transaction_cost(order) def _management_fee(self): # type: () -> float """计算账户管理费用""" if self._management_fee_rate == 0: return 0 fee = self._management_fee_calculator_func(self, self._management_fee_rate) return fee def register_management_fee_calculator(self, calculator): # type: (Callable[[Account, float], float]) -> None """ 设置管理费用计算逻辑 该方法需要传入一个函数 .. code-block:: python def management_fee_calculator(account, rate): return len(account.positions) * rate def init(context): context.portfolio.accounts["STOCK"].set_management_fee_calculator(management_fee_calculator) """ self._management_fee_calculator_func = calculator def set_management_fee_rate(self, rate): # type: (float) -> None """管理费用计算费率""" self._management_fee_rate = rate @property def management_fees(self): # type: () -> float """该账户的管理费用总计""" return self._management_fees def deposit_withdraw(self, amount): # type: (float) -> None """出入金""" if (amount < 0) and (self.cash < amount * -1): raise ValueError( _('insufficient cash, current {}, target withdrawal {}'). format(self._total_cash, amount)) self._total_cash += amount holding_pnl = deprecated_property("holding_pnl", "position_pnl") realized_pnl = deprecated_property("realized_pnl", "trading_pnl")
class FuturePositionProxy(AssetPositionProxy): __abandon_properties__ = AssetPositionProxy.__abandon_properties__ +[ "holding_pnl", "buy_holding_pnl", "sell_holding_pnl", "realized_pnl", "buy_realized_pnl", "sell_realized_pnl", "buy_avg_holding_price", "sell_avg_holding_price" ] @property def type(self): return DEFAULT_ACCOUNT_TYPE.FUTURE.name def set_state(self, state): assert self.order_book_id == state['order_book_id'] if "long" in state and "short" in state: super(FuturePositionProxy, self).set_state(state) else: # for compatible buy_old_quantity = buy_logical_old_quantity = sum(q for _, q in state.get("buy_old_holding_list", [])) self._long.set_state({ "old_quantity": buy_old_quantity, "logical_old_quantity": buy_logical_old_quantity, "today_quantity": sum(q for _, q in state.get("buy_today_holding_list", [])), "avg_price": state.get("buy_avg_open_price"), "trade_cost": 0, "transaction_cost": state.get("buy_transaction_cost") }) sell_old_quantity = sell_logical_old_quantity = sum(q for _, q in state.get("sell_old_holding_list", [])) self._long.set_state({ "old_quantity": sell_old_quantity, "logical_old_quantity": sell_logical_old_quantity, "today_quantity": sum(q for _, q in state.get("sell_today_holding_list", [])), "avg_price": state.get("sell_avg_open_price"), "trade_cost": 0, "transaction_cost": state.get("sell_transaction_cost") }) @property def margin_rate(self): return self._long.margin_rate @property def contract_multiplier(self): return self._long.contract_multiplier @property def buy_market_value(self): """ [float] 多方向市值 """ return self._long.market_value @property def sell_market_value(self): """ [float] 空方向市值 """ return self._short.market_value @property def buy_position_pnl(self): """ [float] 多方向昨仓盈亏 """ return self._long.position_pnl @property def sell_position_pnl(self): """ [float] 空方向昨仓盈亏 """ return self._short.position_pnl @property def buy_trading_pnl(self): """ [float] 多方向交易盈亏 """ return self._long.trading_pnl @property def sell_trading_pnl(self): """ [float] 空方向交易盈亏 """ return self._short.trading_pnl @property def buy_daily_pnl(self): """ [float] 多方向每日盈亏 """ return self.buy_position_pnl + self.buy_trading_pnl @property def sell_daily_pnl(self): """ [float] 空方向每日盈亏 """ return self.sell_position_pnl + self.sell_trading_pnl @property def buy_pnl(self): """ [float] 买方向累计盈亏 """ return self._long.pnl @property def sell_pnl(self): """ [float] 空方向累计盈亏 """ return self._short.pnl @property def buy_old_quantity(self): """ [int] 多方向昨仓 """ return self._long.old_quantity @property def sell_old_quantity(self): """ [int] 空方向昨仓 """ return self._short.old_quantity @property def buy_today_quantity(self): """ [int] 多方向今仓 """ return self._long.today_quantity @property def sell_today_quantity(self): """ [int] 空方向今仓 """ return self._short.today_quantity @property def buy_quantity(self): """ [int] 多方向持仓 """ return self.buy_old_quantity + self.buy_today_quantity @property def sell_quantity(self): """ [int] 空方向持仓 """ return self.sell_old_quantity + self.sell_today_quantity @property def buy_margin(self): """ [float] 多方向持仓保证金 """ return self._long.margin @property def sell_margin(self): """ [float] 空方向持仓保证金 """ return self._short.margin @property def buy_avg_open_price(self): """ [float] 多方向平均开仓价格 """ return self._long.avg_price @property def sell_avg_open_price(self): """ [float] 空方向平均开仓价格 """ return self._short.avg_price @property def buy_transaction_cost(self): """ [float] 多方向交易费率 """ return self._long.transaction_cost @property def sell_transaction_cost(self): """ [float] 空方向交易费率 """ return self._short.transaction_cost @property def closable_today_sell_quantity(self): buy_close_today_order_quantity = sum(o.unfilled_quantity for o in self.open_orders if o.side == SIDE.BUY and o.position_effect == POSITION_EFFECT.CLOSE_TODAY) return self.sell_today_quantity - buy_close_today_order_quantity @property def closable_today_buy_quantity(self): sell_close_today_order_quantity = sum(o.unfilled_quantity for o in self.open_orders if o.side == SIDE.SELL and o.position_effect == POSITION_EFFECT.CLOSE_TODAY) return self.buy_today_quantity - sell_close_today_order_quantity @property def closable_buy_quantity(self): """ [float] 可平多方向持仓 """ sell_close_order_quantity = sum(o.unfilled_quantity for o in self.open_orders if o.side == SIDE.SELL and o.position_effect in (POSITION_EFFECT.CLOSE, POSITION_EFFECT.CLOSE_TODAY)) return self.buy_quantity - sell_close_order_quantity @property def closable_sell_quantity(self): """ [float] 可平空方向持仓 """ buy_close_order_quantity = sum(o.unfilled_quantity for o in self.open_orders if o.side == SIDE.BUY and o.position_effect in (POSITION_EFFECT.CLOSE, POSITION_EFFECT.CLOSE_TODAY)) return self.sell_quantity - buy_close_order_quantity def is_de_listed(self): """ 判断合约是否过期 """ instrument = Environment.get_instance().get_instrument(self.order_book_id) current_date = Environment.get_instance().trading_dt if instrument.de_listed_date is not None and current_date >= instrument.de_listed_date: return True return False def cal_close_today_amount(self, trade_amount, trade_side): if trade_side == SIDE.SELL: close_today_amount = trade_amount - self.buy_old_quantity else: close_today_amount = trade_amount - self.sell_old_quantity return max(close_today_amount, 0) holding_pnl = deprecated_property("holding_pnl", "position_pnl") buy_holding_pnl = deprecated_property("buy_holding_pnl", "buy_position_pnl") sell_holding_pnl = deprecated_property("sell_holding_pnl", "sell_position_pnl") realized_pnl = deprecated_property("realized_pnl", "trading_pnl") buy_realized_pnl = deprecated_property("buy_realized_pnl", "buy_trading_pnl") sell_realized_pnl = deprecated_property("sell_realized_pnl", "sell_trading_pnl") buy_avg_holding_price = deprecated_property("buy_avg_holding_price", "buy_avg_open_price") sell_avg_holding_price = deprecated_property("sell_avg_holding_price", "sell_avg_open_price")