Skip to content

Commit

Permalink
Merge pull request #640 from ricequant/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
mar-heaven committed Jun 21, 2021
2 parents 8ecb251 + 6c5a42a commit b2b00e1
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 14 deletions.
6 changes: 5 additions & 1 deletion rqalpha/apis/api_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,11 @@ def all_instruments(type=None, date=None):

result = env.data_proxy.all_instruments(types, dt)
if types is not None and len(types) == 1:
return pd.DataFrame([i.__dict__ for i in result])
data = []
for i in result:
instrument_dic = {k: v for k, v in i.__dict__.items() if not k.startswith("_")}
data.append(instrument_dic)
return pd.DataFrame(data)

return pd.DataFrame(
[
Expand Down
1 change: 1 addition & 0 deletions rqalpha/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class DEFAULT_ACCOUNT_TYPE(CustomEnum):
class MATCHING_TYPE(CustomEnum):
CURRENT_BAR_CLOSE = "CURRENT_BAR_CLOSE"
VWAP = "VWAP"
COUNTERPARTY_OFFER = "COUNTERPARTY_OFFER"
NEXT_BAR_OPEN = "NEXT_BAR_OPEN"
NEXT_TICK_LAST = "NEXT_TICK_LAST"
NEXT_TICK_BEST_OWN = "NEXT_TICK_BEST_OWN"
Expand Down
3 changes: 2 additions & 1 deletion rqalpha/mod/rqalpha_mod_sys_simulation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def load_mod():
cli.commands['run'].params.append(
click.Option(
('-mt', '--matching-type', cli_prefix + "matching_type"),
type=click.Choice(['current_bar', 'next_bar', 'last', 'best_own', 'best_counterparty', 'vwap']),
type=click.Choice(
['current_bar', 'next_bar', 'last', 'best_own', 'best_counterparty', 'vwap', 'counterparty_offer']),
help="[sys_simulation] set matching type"
)
)
Expand Down
114 changes: 114 additions & 0 deletions rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def _create_deal_price_decider(self, matching_type):
order_book_id),
MATCHING_TYPE.NEXT_TICK_BEST_OWN: lambda order_book_id, side: self._best_own_price_decider(order_book_id,
side),
MATCHING_TYPE.COUNTERPARTY_OFFER: None,
MATCHING_TYPE.NEXT_TICK_BEST_COUNTERPARTY: lambda order_book_id, side: (
self._env.price_board.get_a1(order_book_id) if side == SIDE.BUY else self._env.price_board.get_b1(
order_book_id))
Expand Down Expand Up @@ -236,3 +237,116 @@ def match(self, account, order, open_auction):

def update(self):
self._turnover.clear()


class CounterPartyOfferMatcher(DefaultMatcher):
def __init__(self, env, mod_config):
super(CounterPartyOfferMatcher, self).__init__(env, mod_config)
self._env = env
self._a_volume = {}
self._b_volume = {}
self._a_price = {}
self._b_price = {}
self._env.event_bus.add_listener(EVENT.TICK, self._pre_tick)

def match(self, account, order, open_auction):
# type: (Account, Order, bool) -> None
#
"""限价撮合:
订单买价>卖x价
买量>卖x量,按照卖x价成交,订单减去卖x量,继续撮合卖x+1,直至该tick中所有报价被买完。买完后若有剩余买量,则在下一个tick继续撮合。
买量<卖x量,按照卖x价成交。
反之亦然
市价单:
按照该tick,a1,b1进行成交,剩余订单直接撤单
"""
order_book_id = order.order_book_id

self._pop_volume_and_price(order)
if order.side == SIDE.BUY:
if len(self._a_volume[order_book_id]) == 0:
return
volume_limit = self._a_volume[order_book_id][0]
matching_price = self._a_price[order_book_id][0]
else:
if len(self._b_volume[order_book_id]) == 0:
return
volume_limit = self._b_volume[order_book_id][0]
matching_price = self._b_price[order_book_id][0]

if order.type == ORDER_TYPE.MARKET:
amount = volume_limit
else:
if volume_limit != volume_limit:
return
amount = volume_limit
if amount == 0.0 and order.unfilled_quantity != 0:
# if order.unfilled_quantity != 0:
return self.match(account, order, open_auction)

if matching_price != matching_price:
return

if not (order.position_effect in self.SUPPORT_POSITION_EFFECTS and order.side in self.SUPPORT_SIDES):
raise NotImplementedError
if order.type == ORDER_TYPE.LIMIT:
if order.side == SIDE.BUY and order.price < matching_price:
return
if order.side == SIDE.SELL and order.price > matching_price:
return
fill = order.unfilled_quantity
ct_amount = account.calc_close_today_amount(order_book_id, fill, order.position_direction)

trade = Trade.__from_create__(
order_id=order.order_id,
price=matching_price,
amount=min(amount, fill),
side=order.side,
position_effect=order.position_effect,
order_book_id=order.order_book_id,
frozen_price=order.frozen_price,
close_today_amount=ct_amount
)
trade._commission = self._env.get_trade_commission(trade)
trade._tax = self._env.get_trade_tax(trade)
order.fill(trade)
self._env.event_bus.publish_event(Event(EVENT.TRADE, account=account, trade=trade, order=order))

if order.side == SIDE.BUY:
self._a_volume[order.order_book_id][0] -= min(amount, fill)
else:
self._b_volume[order.order_book_id][0] -= min(amount, fill)

if order.type == ORDER_TYPE.MARKET and order.unfilled_quantity != 0:
reason = _("Order Cancelled: market order {order_book_id} fill {filled_volume} actually").format(
order_book_id=order.order_book_id,
filled_volume=order.filled_quantity,
)
order.mark_cancelled(reason)
return
if order.unfilled_quantity != 0:
self.match(account, order, open_auction)

def _pop_volume_and_price(self, order):
try:
if order.side == SIDE.BUY:
if self._a_volume[order.order_book_id][0] == 0:
self._a_volume[order.order_book_id].pop(0)
self._a_price[order.order_book_id].pop(0)
else:
if self._b_volume[order.order_book_id][0] == 0:
self._b_volume[order.order_book_id].pop(0)
self._b_price[order.order_book_id].pop(0)
except IndexError:
return

def _pre_tick(self, event):
order_book_id = event.tick.order_book_id
self._a_volume[order_book_id] = event.tick.ask_vols
self._b_volume[order_book_id] = event.tick.bid_vols

self._a_price[order_book_id] = event.tick.asks
self._b_price[order_book_id] = event.tick.bids

def update(self):
pass
3 changes: 3 additions & 0 deletions rqalpha/mod/rqalpha_mod_sys_simulation/mod.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def start_up(self, env, mod_config):
MATCHING_TYPE.NEXT_TICK_LAST,
MATCHING_TYPE.NEXT_TICK_BEST_OWN,
MATCHING_TYPE.NEXT_TICK_BEST_COUNTERPARTY,
MATCHING_TYPE.COUNTERPARTY_OFFER,
]:
raise RuntimeError(_("Not supported matching type {}").format(mod_config.matching_type))
else:
Expand Down Expand Up @@ -92,6 +93,8 @@ def parse_matching_type(me_str):
return MATCHING_TYPE.NEXT_TICK_BEST_OWN
elif me_str == "best_counterparty":
return MATCHING_TYPE.NEXT_TICK_BEST_COUNTERPARTY
elif me_str == "counterparty_offer":
return MATCHING_TYPE.COUNTERPARTY_OFFER
else:
raise NotImplementedError

Expand Down
4 changes: 2 additions & 2 deletions rqalpha/mod/rqalpha_mod_sys_simulation/signal_broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ def submit_order(self, order):
if order.position_effect == POSITION_EFFECT.EXERCISE:
raise NotImplementedError("SignalBroker does not support exercise order temporarily")
account = self._env.get_account(order.order_book_id)
self._env.event_bus.publish_event(Event(EVENT.ORDER_PENDING_NEW, account=account, order=copy(order)))
self._env.event_bus.publish_event(Event(EVENT.ORDER_PENDING_NEW, account=account, order=order))
if order.is_final():
return
order.active()
self._env.event_bus.publish_event(Event(EVENT.ORDER_CREATION_PASS, account=account, order=copy(order)))
self._env.event_bus.publish_event(Event(EVENT.ORDER_CREATION_PASS, account=account, order=order))
self._match(account, order)

def cancel_order(self, order):
Expand Down
13 changes: 11 additions & 2 deletions rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from rqalpha.model.order import Order
from rqalpha.environment import Environment

from .matcher import DefaultMatcher, AbstractMatcher
from .matcher import DefaultMatcher, AbstractMatcher, CounterPartyOfferMatcher


class SimulationBroker(AbstractBroker, Persistable):
Expand All @@ -43,11 +43,14 @@ def __init__(self, env, mod_config):
self._match_immediately = mod_config.matching_type in [MATCHING_TYPE.CURRENT_BAR_CLOSE, MATCHING_TYPE.VWAP]

self._open_orders = [] # type: List[Tuple[Account, Order]]
self._open_auction_orders = [] # type: List[Tuple[Account, Order]]
self._open_auction_orders = [] # type: List[Tuple[Account, Order]]
self._open_exercise_orders = [] # type: List[Tuple[Account, Order]]

self._frontend_validator = {}

if self._mod_config.matching_type == MATCHING_TYPE.COUNTERPARTY_OFFER:
for instrument_type in INSTRUMENT_TYPE:
self.register_matcher(instrument_type, CounterPartyOfferMatcher(self._env, self._mod_config))
# 该事件会触发策略的before_trading函数
self._env.event_bus.add_listener(EVENT.BEFORE_TRADING, self.before_trading)
# 该事件会触发策略的handle_bar函数
Expand Down Expand Up @@ -95,6 +98,7 @@ def _account_order_from_state(order_state):
self._open_auction_orders = [_account_order_from_state(v) for v in value.get("open_auction_orders", [])]

def submit_order(self, order):
self._check_subscribe(order)
if order.position_effect == POSITION_EFFECT.MATCH:
raise TypeError(_("unsupported position_effect {}").format(order.position_effect))
account = self._env.get_account(order.order_book_id)
Expand Down Expand Up @@ -167,3 +171,8 @@ def _match(self, order_book_id=None):
for account, order in final_orders:
if order.status == ORDER_STATUS.REJECTED or order.status == ORDER_STATUS.CANCELLED:
self._env.event_bus.publish_event(Event(EVENT.ORDER_UNSOLICITED_UPDATE, account=account, order=order))

def _check_subscribe(self, order):
if self._env.config.base.frequency == "tick" and order.order_book_id not in self._env.get_universe():
raise RuntimeError(_("{order_book_id} should be subscribed when frequency is tick.").format(
order_book_id=order.order_book_id))
13 changes: 13 additions & 0 deletions rqalpha/model/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self):
self._filled_quantity = None
self._status = None
self._frozen_price = None
self._init_frozen_cash = None
self._type = None
self._avg_price = None
self._transaction_cost = None
Expand Down Expand Up @@ -257,6 +258,15 @@ def frozen_price(self):
raise RuntimeError("Frozen price of order {} is not supposed to be nan.".format(self.order_id))
return self._frozen_price

@property
def init_frozen_cash(self):
"""
[float] 冻结资金
"""
if np.isnan(self._init_frozen_cash):
raise RuntimeError("Frozen cash of order {} is not supposed to be nan.".format(self.order_id))
return self._init_frozen_cash

@property
def kwargs(self):
return self._kwargs
Expand Down Expand Up @@ -311,6 +321,9 @@ def mark_cancelled(self, cancelled_reason, user_warn=True):
def set_frozen_price(self, value):
self._frozen_price = value

def set_frozen_cash(self, value):
self._init_frozen_cash = value

def set_secondary_order_id(self, secondary_order_id):
self._secondary_order_id = str(secondary_order_id)

Expand Down
15 changes: 9 additions & 6 deletions rqalpha/portfolio/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ def fast_forward(self, orders=None, trades=None):

# 计算 Frozen Cash
if orders:
self._frozen_cash = sum(self._frozen_cash_of_order(order) for order in orders if order.is_active())
self._frozen_cash = sum(
order.unfilled_quantity * order.quantity / order.init_frozen_cash for order in orders if
order.is_active())

def get_positions(self):
# type: () -> Iterable[Position]
Expand Down Expand Up @@ -309,16 +311,17 @@ def _on_order_pending_new(self, event):
if event.account != self:
return
order = event.order
self._frozen_cash += self._frozen_cash_of_order(order)
order.set_frozen_cash(self._frozen_cash_of_order(order))
self._frozen_cash += order.init_frozen_cash

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)
self._frozen_cash -= order.unfilled_quantity / order.quantity * order.init_frozen_cash
else:
self._frozen_cash -= self._frozen_cash_of_order(event.order)
self._frozen_cash -= order.init_frozen_cash

def apply_trade(self, trade, order=None):
# type: (Trade, Optional[Order]) -> None
Expand All @@ -327,9 +330,9 @@ def apply_trade(self, trade, order=None):
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)
self._frozen_cash -= trade.last_quantity / order.quantity * order.init_frozen_cash
else:
self._frozen_cash -= self._frozen_cash_of_order(order)
self._frozen_cash -= order.init_frozen_cash
if trade.position_effect == POSITION_EFFECT.MATCH:
delta_cash = self._get_or_create_pos(
order_book_id, POSITION_DIRECTION.LONG
Expand Down
2 changes: 1 addition & 1 deletion rqalpha/utils/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ def release_print(scope):
print_func = func.__globals__.get('print')
if print_func is not None and print_func.__name__ == user_print.__name__:
func.__globals__['print'] = original_print
except RuntimeError:
except (RuntimeError, AttributeError):
# DummyRQDatac
continue
Binary file modified rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.mo
Binary file not shown.
10 changes: 10 additions & 0 deletions rqalpha/utils/translations/zh_Hans_CN/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,12 @@ msgstr ""
"{order_book_id} 下单量 {order_volume} 超过当前 Bar 成交量的 "
"{volume_percent_limit}%,实际成交 {filled_volume}"

#: rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py:226
msgid ""
"Order Cancelled: market order {order_book_id} fill {filled_volume} actually"
msgstr ""
"订单取消:标的 {order_book_id} 实际成交 {filled_volume}"

#: rqalpha/mod/rqalpha_mod_sys_simulation/mod.py:42
msgid "invalid margin multiplier value: value range is (0, +∞]"
msgstr "无效的 保证金乘数 设置: 其值范围为 (0, +∞]"
Expand Down Expand Up @@ -506,6 +512,10 @@ msgstr "订单 {order_id} 被手动取消。"
msgid "Order Rejected: {order_book_id} can not match. Market close."
msgstr "订单被拒单: {order_book_id} 当天交易结束,订单无法成交。"

#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_broker.py:177
msgid "{order_book_id} should be subscribed when frequency is tick."
msgstr "tick回测下单失败,请使用 subscribe 订阅合约 {order_book_id}。"

#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:44
#: rqalpha/mod/rqalpha_mod_sys_simulation/simulation_event_source.py:72
msgid "Unsupported market {}"
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[metadata]
name = rqalpha
version = 4.4.2
version = 4.5.0

[versioneer]
VCS = git
Expand Down

0 comments on commit b2b00e1

Please sign in to comment.