rqalpha.mod.rqalpha_mod_sys_accounts.position_model 源代码

# -*- coding: utf-8 -*-
# 版权所有 2019 深圳米筐科技有限公司(下称“米筐科技”)
#
# 除非遵守当前许可,否则不得使用本软件。
#
#     * 非商业用途(非商业用途指个人出于非商业目的使用本软件,或者高校、研究所等非营利机构出于教育、科研等目的使用本软件):
#         遵守 Apache License 2.0(下称“Apache 2.0 许可”),
#         您可以在以下位置获得 Apache 2.0 许可的副本:http://www.apache.org/licenses/LICENSE-2.0。
#         除非法律有要求或以书面形式达成协议,否则本软件分发时需保持当前许可“原样”不变,且不得附加任何条件。
#
#     * 商业用途(商业用途指个人出于任何商业目的使用本软件,或者法人或其他组织出于任何目的使用本软件):
#         未经米筐科技授权,任何个人不得出于任何商业目的使用本软件(包括但不限于向第三方提供、销售、出租、出借、转让本软件、
#         本软件的衍生产品、引用或借鉴了本软件功能或源代码的产品或服务),任何法人或其他组织不得出于任何目的使用本软件,
#         否则米筐科技有权追究相应的知识产权侵权责任。
#         在此前提下,对本软件的使用同样需要遵守 Apache 2.0 许可,Apache 2.0 许可与本许可冲突之处,以本许可为准。
#         详细的授权流程,请联系 public@ricequant.com 获取。

from datetime import date

from decimal import Decimal

from rqalpha.model.trade import Trade
from rqalpha.const import POSITION_DIRECTION, SIDE, POSITION_EFFECT, DEFAULT_ACCOUNT_TYPE, INSTRUMENT_TYPE
from rqalpha.environment import Environment
from rqalpha.portfolio.position import Position, PositionProxy
from rqalpha.data.data_proxy import DataProxy
from rqalpha.utils import INST_TYPE_IN_STOCK_ACCOUNT, is_valid_price
from rqalpha.utils.logger import user_system_log
from rqalpha.utils.class_helper import deprecated_property, cached_property
from rqalpha.utils.i18n import gettext as _


def _int_to_date(d):
    r, d = divmod(d, 100)
    y, m = divmod(r, 100)
    return date(year=y, month=m, day=d)


[文档]class StockPosition(Position): __repr_properties__ = ( "order_book_id", "direction", "quantity", "market_value", "trading_pnl", "position_pnl", "last_price" ) __instrument_types__ = INST_TYPE_IN_STOCK_ACCOUNT dividend_reinvestment = False cash_return_by_stock_delisted = True t_plus_enabled = True def __init__(self, order_book_id, direction, init_quantity=0, init_price=None): super(StockPosition, self).__init__(order_book_id, direction, init_quantity, init_price) self._dividend_receivable = None self._pending_transform = None self._non_closable = 0 @property def dividend_receivable(self): # type: () -> float """ 应收分红 """ if self._dividend_receivable: return self._dividend_receivable[1] return 0 @property def equity(self): # type: () -> float """""" return super(StockPosition, self).equity + self.dividend_receivable @property def closable(self): # type: () -> int order_quantity = sum(o.unfilled_quantity for o in self._open_orders if o.position_effect in ( POSITION_EFFECT.CLOSE, POSITION_EFFECT.CLOSE_TODAY, POSITION_EFFECT.EXERCISE )) if self.t_plus_enabled: return self._quantity - order_quantity - self._non_closable return self._quantity - order_quantity def set_state(self, state): super(StockPosition, self).set_state(state) self._dividend_receivable = state.get("dividend_receivable") self._pending_transform = state.get("pending_transform") self._non_closable = state.get("non_closable", 0) def get_state(self): state = super(StockPosition, self).get_state() state.update({ "dividend_receivable": self._dividend_receivable, "pending_transform": self._pending_transform, "non_closable": self._non_closable }) return state def before_trading(self, trading_date): # type: (date) -> float delta_cash = super(StockPosition, self).before_trading(trading_date) if self._quantity == 0 and not self._dividend_receivable: return delta_cash if self.direction != POSITION_DIRECTION.LONG: raise RuntimeError("direction of stock position {} is not supposed to be short".format(self._order_book_id)) data_proxy = self._env.data_proxy self._handle_dividend_book_closure(trading_date, data_proxy) delta_cash += self._handle_dividend_payable(trading_date) self._handle_split(trading_date, data_proxy) return delta_cash def apply_trade(self, trade): # type: (Trade) -> float # 返回总资金的变化量 delta_cash = super(StockPosition, self).apply_trade(trade) if trade.position_effect == POSITION_EFFECT.OPEN and self._market_tplus >= 1: self._non_closable += trade.last_quantity return delta_cash def settlement(self, trading_date): # type: (date) -> float super(StockPosition, self).settlement(trading_date) if self._quantity == 0: return 0 if self.direction != POSITION_DIRECTION.LONG: raise RuntimeError("direction of stock position {} is not supposed to be short".format(self._order_book_id)) next_date = self._env.data_proxy.get_next_trading_date(trading_date) instrument = self._env.data_proxy.instrument(self._order_book_id) delta_cash = 0 if instrument.de_listed_at(next_date): try: transform_data = self._env.data_proxy.get_share_transformation(self._order_book_id) except NotImplementedError: pass else: if transform_data is not None: successor, conversion_ratio = transform_data self._env.portfolio.get_account(successor).apply_trade(Trade.__from_create__( order_id=None, price=self.avg_price / conversion_ratio, amount=self._quantity * conversion_ratio, side=SIDE.BUY, position_effect=POSITION_EFFECT.OPEN, order_book_id=successor )) for direction in POSITION_DIRECTION: successor_position = self._env.portfolio.get_position(successor, direction) successor_position.update_last_price(self._last_price / conversion_ratio) # 把购买 successor 消耗的 cash 补充回来 delta_cash = self.market_value if self.cash_return_by_stock_delisted: delta_cash = self.market_value self._quantity = self._old_quantity = 0 return delta_cash @cached_property def _market_tplus(self): return self._instrument.market_tplus def _handle_dividend_book_closure(self, trading_date, data_proxy): # type: (date, DataProxy) -> None last_date = data_proxy.get_previous_trading_date(trading_date) dividend = data_proxy.get_dividend_by_book_date(self._order_book_id, last_date) if dividend is None: return dividend_per_share = sum(dividend['dividend_cash_before_tax'] / dividend['round_lot']) if dividend_per_share != dividend_per_share: raise RuntimeError("Dividend per share of {} is not supposed to be nan.".format(self._order_book_id)) self._avg_price -= dividend_per_share # 前一天结算发生了除息, 此时 last_price 还是前一个交易日的收盘价,需要改为 除息后收盘价, 否则影响在before_trading中查看盈亏 self._last_price -= dividend_per_share try: payable_date = _int_to_date(dividend["payable_date"][0]) except ValueError: payable_date = _int_to_date(dividend["ex_dividend_date"][0]) self._dividend_receivable = (payable_date, self._quantity * dividend_per_share) def _handle_dividend_payable(self, trading_date): # type: (date) -> float # 返回总资金的变化量 if not self._dividend_receivable: return 0 payable_date, dividend_value = self._dividend_receivable if payable_date != trading_date: return 0 self._dividend_receivable = None if self.dividend_reinvestment: last_price = self.last_price amount = int(Decimal(dividend_value) / Decimal(last_price)) round_lot = self._instrument.round_lot amount = int(Decimal(amount) / Decimal(round_lot)) * round_lot if amount > 0: self.apply_trade(Trade.__from_create__( None, last_price, amount, SIDE.BUY, POSITION_EFFECT.OPEN, self._order_book_id )) return dividend_value - amount * last_price else: return dividend_value def _handle_split(self, trading_date, data_proxy): ratio = data_proxy.get_split_by_ex_date(self._order_book_id, trading_date) if ratio is None: return self._avg_price /= ratio self._last_price /= ratio ratio = Decimal(ratio) # int(6000 * 1.15) -> 6899 self._old_quantity = self._quantity = int(Decimal(self._quantity) * ratio) self._logical_old_quantity = int(Decimal(self._logical_old_quantity) * ratio)
[文档]class FuturePosition(Position): __repr_properties__ = ( "order_book_id", "direction", "old_quantity", "quantity", "margin", "market_value", "trading_pnl", "position_pnl", "last_price" ) __instrument_types__ = [INSTRUMENT_TYPE.FUTURE] old_quantity = property(lambda self: self._old_quantity) today_quantity = property(lambda self: self._quantity - self._old_quantity) @cached_property def contract_multiplier(self): return self._instrument.contract_multiplier @cached_property def margin_rate(self): return self._instrument.margin_rate * self._env.config.base.margin_multiplier @property def equity(self): # type: () -> float """""" return self._quantity * (self.last_price - self._avg_price) * self.contract_multiplier * self._direction_factor @property def margin(self) -> float: """ 保证金 = 持仓量 * 最新价 * 合约乘数 * 保证金率 """ return self.margin_rate * self.market_value @property def market_value(self): # type: () -> float return self.contract_multiplier * super(FuturePosition, self).market_value @property def trading_pnl(self): # type: () -> float return self.contract_multiplier * super(FuturePosition, self).trading_pnl @property def position_pnl(self): # type: () -> float return self.contract_multiplier * super(FuturePosition, self).position_pnl @property def pnl(self): # type: () -> float return super(FuturePosition, self).pnl * self.contract_multiplier def calc_close_today_amount(self, trade_amount, position_effect): if position_effect == POSITION_EFFECT.CLOSE_TODAY: return trade_amount if trade_amount <= self.today_quantity else self.today_quantity else: return max(trade_amount - self._old_quantity, 0) def apply_trade(self, trade): if trade.position_effect == POSITION_EFFECT.CLOSE_TODAY: self._transaction_cost += trade.transaction_cost self._quantity -= trade.last_quantity self._trade_cost -= trade.last_price * trade.last_quantity else: super(FuturePosition, self).apply_trade(trade) if trade.position_effect == POSITION_EFFECT.OPEN: return -1 * trade.transaction_cost else: return -1 * trade.transaction_cost + ( trade.last_price - self._avg_price ) * trade.last_quantity * self.contract_multiplier * self._direction_factor @property def prev_close(self): if not is_valid_price(self._prev_close): if self._env.config.mod.sys_accounts.futures_settlement_price_type == "settlement": self._prev_close = self._env.data_proxy.get_prev_settlement(self._order_book_id, self._env.trading_dt) else: self._prev_close = super().prev_close return self._prev_close def settlement(self, trading_date): # type: (date) -> float delta_cash = super(FuturePosition, self).settlement(trading_date) if self._quantity == 0: return delta_cash data_proxy = self._env.data_proxy instrument = data_proxy.instrument(self._order_book_id) next_date = data_proxy.get_next_trading_date(trading_date) if self._env.config.mod.sys_accounts.futures_settlement_price_type == "settlement": # 逐日盯市按照结算价结算 self._last_price = self._env.data_proxy.get_settle_price(self._order_book_id, self._env.trading_dt) delta_cash += self.equity self._avg_price = self.last_price if instrument.de_listed_at(next_date): user_system_log.warn(_(u"{order_book_id} is expired, close all positions by system").format( order_book_id=self._order_book_id )) self._quantity = self._old_quantity = 0 return delta_cash
class StockPositionProxy(PositionProxy): __repr_properties__ = ( "order_book_id", "quantity", "avg_price", "market_value" ) __instrument_types__ = INST_TYPE_IN_STOCK_ACCOUNT @property def type(self): return "STOCK" @property def quantity(self): return self._long.quantity @property def sellable(self): """ [int] 该仓位可卖出股数。T+1 的市场中sellable = 所有持仓 - 今日买入的仓位 - 已冻结 """ return self._long.closable @property def avg_price(self): """ [float] 平均开仓价格 """ return self._long.avg_price @property def value_percent(self): """ [float] 获得该持仓的实时市场价值在股票投资组合价值中所占比例,取值范围[0, 1] """ accounts = Environment.get_instance().portfolio.accounts if DEFAULT_ACCOUNT_TYPE.STOCK not in accounts: return 0 total_value = accounts[DEFAULT_ACCOUNT_TYPE.STOCK].total_value return 0 if total_value == 0 else self.market_value / total_value class FuturePositionProxy(PositionProxy): __repr_properties__ = ( "order_book_id", "buy_quantity", "sell_quantity", "buy_market_value", "sell_market_value", "buy_margin", "sell_margin" ) __instrument_types__ = [INSTRUMENT_TYPE.FUTURE] @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")