Skip to content

Commit

Permalink
Implement CommissionModel (#1584)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsmb7z authored Apr 9, 2024
1 parent a6e66ef commit 5258143
Show file tree
Hide file tree
Showing 22 changed files with 294 additions and 20 deletions.
2 changes: 2 additions & 0 deletions nautilus_trader/adapters/sandbox/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from nautilus_trader.backtest.exchange import SimulatedExchange
from nautilus_trader.backtest.execution_client import BacktestExecClient
from nautilus_trader.backtest.models import FillModel
from nautilus_trader.backtest.models import InstrumentSpecificPercentCommissionModel
from nautilus_trader.backtest.models import LatencyModel
from nautilus_trader.cache.cache import Cache
from nautilus_trader.common.component import LiveClock
Expand Down Expand Up @@ -119,6 +120,7 @@ def __init__(
msgbus=self._msgbus,
cache=cache,
fill_model=FillModel(),
commission_model=InstrumentSpecificPercentCommissionModel(),
latency_model=LatencyModel(0),
clock=self.test_clock,
frozen_account=True, # <-- Freezing account
Expand Down
6 changes: 6 additions & 0 deletions nautilus_trader/backtest/engine.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ from nautilus_trader.backtest.data_client cimport BacktestDataClient
from nautilus_trader.backtest.data_client cimport BacktestMarketDataClient
from nautilus_trader.backtest.exchange cimport SimulatedExchange
from nautilus_trader.backtest.execution_client cimport BacktestExecClient
from nautilus_trader.backtest.models cimport CommissionModel
from nautilus_trader.backtest.models cimport FillModel
from nautilus_trader.backtest.models cimport InstrumentSpecificPercentCommissionModel
from nautilus_trader.backtest.models cimport LatencyModel
from nautilus_trader.backtest.modules cimport SimulationModule
from nautilus_trader.cache.base cimport CacheFacade
Expand Down Expand Up @@ -366,6 +368,7 @@ cdef class BacktestEngine:
leverages: dict[InstrumentId, Decimal] | None = None,
modules: list[SimulationModule] | None = None,
fill_model: FillModel | None = None,
commission_model: CommissionModel = InstrumentSpecificPercentCommissionModel(),
latency_model: LatencyModel | None = None,
book_type: BookType = BookType.L1_MBP,
routing: bool = False,
Expand Down Expand Up @@ -402,6 +405,8 @@ cdef class BacktestEngine:
The simulation modules to load into the exchange.
fill_model : FillModel, optional
The fill model for the exchange.
commission_model : CommissionModel, optional
The commission model for the exchange.
latency_model : LatencyModel, optional
The latency model for the exchange.
book_type : BookType, default ``BookType.L1_MBP``
Expand Down Expand Up @@ -463,6 +468,7 @@ cdef class BacktestEngine:
msgbus=self.kernel.msgbus,
cache=self.kernel.cache,
fill_model=fill_model,
commission_model=commission_model,
latency_model=latency_model,
book_type=book_type,
clock=self.kernel.clock,
Expand Down
3 changes: 3 additions & 0 deletions nautilus_trader/backtest/exchange.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ from libc.stdint cimport uint64_t
from nautilus_trader.accounting.accounts.base cimport Account
from nautilus_trader.backtest.execution_client cimport BacktestExecClient
from nautilus_trader.backtest.matching_engine cimport OrderMatchingEngine
from nautilus_trader.backtest.models cimport CommissionModel
from nautilus_trader.backtest.models cimport FillModel
from nautilus_trader.backtest.models cimport LatencyModel
from nautilus_trader.cache.cache cimport Cache
Expand Down Expand Up @@ -78,6 +79,8 @@ cdef class SimulatedExchange:
"""The latency model for the exchange.\n\n:returns: `LatencyModel`"""
cdef readonly FillModel fill_model
"""The fill model for the exchange.\n\n:returns: `FillModel`"""
cdef readonly CommissionModel commission_model
"""The commission model for the exchange.\n\n:returns: `CommissionModel`"""
cdef readonly bint bar_execution
"""If bars should be processed by the matching engine(s) (and move the market).\n\n:returns: `bool`"""
cdef readonly bint reject_stop_orders
Expand Down
6 changes: 6 additions & 0 deletions nautilus_trader/backtest/exchange.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ from libc.stdint cimport uint64_t
from nautilus_trader.accounting.accounts.base cimport Account
from nautilus_trader.backtest.execution_client cimport BacktestExecClient
from nautilus_trader.backtest.matching_engine cimport OrderMatchingEngine
from nautilus_trader.backtest.models cimport CommissionModel
from nautilus_trader.backtest.models cimport FillModel
from nautilus_trader.backtest.models cimport LatencyModel
from nautilus_trader.backtest.modules cimport SimulationModule
Expand Down Expand Up @@ -88,6 +89,8 @@ cdef class SimulatedExchange:
The read-only cache for the exchange.
fill_model : FillModel
The fill model for the exchange.
commission_model : CommissionModel
The commission model for the matching engine.
latency_model : LatencyModel, optional
The latency model for the exchange.
clock : TestClock
Expand Down Expand Up @@ -144,6 +147,7 @@ cdef class SimulatedExchange:
CacheFacade cache not None,
TestClock clock not None,
FillModel fill_model not None,
CommissionModel commission_model not None,
LatencyModel latency_model = None,
BookType book_type = BookType.L1_MBP,
bint frozen_account = False,
Expand Down Expand Up @@ -193,6 +197,7 @@ cdef class SimulatedExchange:
self.use_random_ids = use_random_ids
self.use_reduce_only = use_reduce_only
self.fill_model = fill_model
self.commission_model = commission_model
self.latency_model = latency_model

# Load modules
Expand Down Expand Up @@ -328,6 +333,7 @@ cdef class SimulatedExchange:
instrument=instrument,
raw_id=len(self.instruments),
fill_model=self.fill_model,
commission_model=self.commission_model,
book_type=self.book_type,
oms_type=self.oms_type,
account_type=self.account_type,
Expand Down
2 changes: 2 additions & 0 deletions nautilus_trader/backtest/matching_engine.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ from libc.stdint cimport int64_t
from libc.stdint cimport uint32_t
from libc.stdint cimport uint64_t

from nautilus_trader.backtest.models cimport CommissionModel
from nautilus_trader.backtest.models cimport FillModel
from nautilus_trader.cache.base cimport CacheFacade
from nautilus_trader.common.component cimport Clock
Expand Down Expand Up @@ -76,6 +77,7 @@ cdef class OrderMatchingEngine:
cdef OrderBook _opening_auction_book
cdef OrderBook _closing_auction_book
cdef FillModel _fill_model
cdef CommissionModel _commission_model
# cdef object _auction_match_algo
cdef bint _bar_execution
cdef bint _reject_stop_orders
Expand Down
31 changes: 11 additions & 20 deletions nautilus_trader/backtest/matching_engine.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import uuid
from cpython.datetime cimport timedelta
from libc.stdint cimport uint64_t

from nautilus_trader.backtest.models cimport CommissionModel
from nautilus_trader.backtest.models cimport FillModel
from nautilus_trader.cache.base cimport CacheFacade
from nautilus_trader.common.component cimport LogColor
Expand Down Expand Up @@ -110,6 +111,8 @@ cdef class OrderMatchingEngine:
The raw integer ID for the instrument.
fill_model : FillModel
The fill model for the matching engine.
commission_model : CommissionModel
The commission model for the matching engine.
book_type : BookType
The order book type for the engine.
oms_type : OmsType
Expand Down Expand Up @@ -150,6 +153,7 @@ cdef class OrderMatchingEngine:
Instrument instrument not None,
uint32_t raw_id,
FillModel fill_model not None,
CommissionModel commission_model not None,
BookType book_type,
OmsType oms_type,
AccountType account_type,
Expand Down Expand Up @@ -187,6 +191,7 @@ cdef class OrderMatchingEngine:
self._use_reduce_only = use_reduce_only
# self._auction_match_algo = auction_match_algo
self._fill_model = fill_model
self._commission_model = commission_model
self._book = OrderBook(
instrument_id=instrument.id,
book_type=book_type,
Expand Down Expand Up @@ -1765,27 +1770,13 @@ cdef class OrderMatchingEngine:
order.liquidity_side = liquidity_side

# Calculate commission
cdef double notional = self.instrument.notional_value(
quantity=last_qty,
price=last_px,
use_quote_for_inverse=False,
).as_f64_c()

cdef double commission_f64
if order.liquidity_side == LiquiditySide.MAKER:
commission_f64 = notional * float(self.instrument.maker_fee)
elif order.liquidity_side == LiquiditySide.TAKER:
commission_f64 = notional * float(self.instrument.taker_fee)
else:
raise ValueError(
f"invalid `LiquiditySide`, was {liquidity_side_to_str(order.liquidity_side)}"
)

cdef Money commission
if self.instrument.is_inverse: # Not using quote for inverse (see above):
commission = Money(commission_f64, self.instrument.base_currency)
else:
commission = Money(commission_f64, self.instrument.quote_currency)
commission = self._commission_model.get_commission(
order=order,
fill_qty=last_qty,
fill_px=last_px,
instrument=self.instrument,
)

self._generate_order_filled(
order=order,
Expand Down
22 changes: 22 additions & 0 deletions nautilus_trader/backtest/models.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@

from libc.stdint cimport uint64_t

from nautilus_trader.model.instruments.base cimport Instrument
from nautilus_trader.model.objects cimport Money
from nautilus_trader.model.objects cimport Price
from nautilus_trader.model.objects cimport Quantity
from nautilus_trader.model.orders.base cimport Order


cdef class FillModel:
cdef readonly double prob_fill_on_limit
Expand All @@ -40,3 +46,19 @@ cdef class LatencyModel:
"""The latency (nanoseconds) for order update messages to reach the exchange.\n\n:returns: `int`"""
cdef readonly uint64_t cancel_latency_nanos
"""The latency (nanoseconds) for order cancel messages to reach the exchange.\n\n:returns: `int`"""


cdef class CommissionModel:
cpdef Money get_commission(self, Order order, Quantity fill_qty, Price fill_px, Instrument instrument)


cdef class InstrumentSpecificPercentCommissionModel(CommissionModel):
"""
Provide a commission model for trades based on a percentage of the notional value
of the trade.
"""

cdef class FixedCommissionModel(CommissionModel):
cdef Money commission
"""The constant commission."""
109 changes: 109 additions & 0 deletions nautilus_trader/backtest/models.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ import random
from libc.stdint cimport uint64_t

from nautilus_trader.core.correctness cimport Condition
from nautilus_trader.core.rust.model cimport LiquiditySide
from nautilus_trader.model.functions cimport liquidity_side_to_str
from nautilus_trader.model.instruments.base cimport Instrument
from nautilus_trader.model.objects cimport Money
from nautilus_trader.model.objects cimport Price
from nautilus_trader.model.objects cimport Quantity
from nautilus_trader.model.orders.base cimport Order


cdef uint64_t NANOSECONDS_IN_MILLISECOND = 1_000_000
Expand Down Expand Up @@ -154,3 +161,105 @@ cdef class LatencyModel:
self.insert_latency_nanos = base_latency_nanos + insert_latency_nanos
self.update_latency_nanos = base_latency_nanos + update_latency_nanos
self.cancel_latency_nanos = base_latency_nanos + cancel_latency_nanos


cdef class CommissionModel:
"""
Provide an abstract commission model for trades.
"""
cpdef Money get_commission(
self,
Order order,
Quantity fill_qty,
Price fill_px,
Instrument instrument,
):
"""
Return the commission for a trade.
Parameters
----------
order : Order
The order to calculate the commission for.
fill_qty : Quantity
The fill quantity of the order.
fill_px : Price
The fill price of the order.
instrument : Instrument
The instrument for the order.
Returns
-------
Money
"""
raise NotImplementedError("Method 'get_commission' must be implemented in a subclass.")


cdef class InstrumentSpecificPercentCommissionModel(CommissionModel):
"""
Provide a commission model for trades based on a percentage of the notional value
of the trade.
"""

cpdef Money get_commission(
self,
Order order,
Quantity fill_qty,
Price fill_px,
Instrument instrument,
):
cdef double notional = instrument.notional_value(
quantity=fill_qty,
price=fill_px,
use_quote_for_inverse=False,
).as_f64_c()

cdef double commission_f64
if order.liquidity_side == LiquiditySide.MAKER:
commission_f64 = notional * float(instrument.maker_fee)
elif order.liquidity_side == LiquiditySide.TAKER:
commission_f64 = notional * float(instrument.taker_fee)
else:
raise ValueError(
f"invalid `LiquiditySide`, was {liquidity_side_to_str(order.liquidity_side)}"
)

cdef Money commission
if instrument.is_inverse: # Not using quote for inverse (see above):
commission = Money(commission_f64, instrument.base_currency)
else:
commission = Money(commission_f64, instrument.quote_currency)

return commission


cdef class FixedCommissionModel(CommissionModel):
"""
Provides a fixed commission model for trades.
Parameters
----------
commission : Money
The fixed commission amount for trades.
Raises
------
ValueError
If `commission` is not a positive amount.
"""

def __init__(self, Money commission):
Condition.type(commission, Money, "commission")
self.commission = commission

cpdef Money get_commission(
self,
Order order,
Quantity fill_qty,
Price fill_px,
Instrument instrument,
):
return self.commission
Loading

0 comments on commit 5258143

Please sign in to comment.