Skip to content

Commit

Permalink
Refactor option greeks feature
Browse files Browse the repository at this point in the history
  • Loading branch information
faysou committed Jan 30, 2025
1 parent 92e0444 commit d6e7b75
Show file tree
Hide file tree
Showing 12 changed files with 525 additions and 495 deletions.
53 changes: 25 additions & 28 deletions examples/backtest/databento_option_greeks.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# Note: Use the python extension jupytext to be able to open this python file in jupyter as a notebook

# %%
# from nautilus_trader.model.data import DataType
from nautilus_trader.adapters.databento.data_utils import data_path
from nautilus_trader.adapters.databento.data_utils import databento_data
from nautilus_trader.adapters.databento.data_utils import load_catalog
Expand All @@ -44,10 +45,8 @@
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.objects import Price
from nautilus_trader.model.objects import Quantity
from nautilus_trader.risk.greeks import GreeksCalculator
from nautilus_trader.risk.greeks import GreeksCalculatorConfig
from nautilus_trader.risk.greeks import InterestRateProvider
from nautilus_trader.risk.greeks import InterestRateProviderConfig
from nautilus_trader.persistence.loaders import InterestRateProvider
from nautilus_trader.persistence.loaders import InterestRateProviderConfig
from nautilus_trader.trading.strategy import Strategy


Expand Down Expand Up @@ -125,35 +124,38 @@ def on_start(self):
bar_type = BarType.from_str(f"{self.config.future_id}-1-MINUTE-LAST-EXTERNAL")
self.subscribe_bars(bar_type)

if self.config.load_greeks:
self.greeks.subscribe_greeks("ES")
# self.subscribe_data(DataType(GreeksData, metadata={"instrument_id": "ES*"}))

def init_portfolio(self):
self.submit_market_order(instrument_id=self.config.option_id, quantity=-10)
self.submit_market_order(instrument_id=self.config.option_id2, quantity=10)
self.submit_market_order(instrument_id=self.config.future_id, quantity=1)

self.start_orders_done = True

# def on_data(self, data):
# self.user_log(data)

def on_bar(self, bar):
self.user_log(f"bar ts_init = {unix_nanos_to_iso8601(bar.ts_init)}")
self.user_log(
f"bar ts_init = {unix_nanos_to_iso8601(bar.ts_init)}, bar close = {bar.close}",
)

if not self.start_orders_done:
self.user_log("Initializing the portfolio with some trades")
self.init_portfolio()
return

if self.config.load_greeks:
# when greeks are loaded from a catalog a small delay is needed so all greeks are updated
# note that loading greeks is not required, it's actually faster to just compute them every time
self.clock.set_time_alert(
"display greeks",
self.clock.utc_now().replace(microsecond=100),
self.display_greeks,
override=True,
)
else:
self.display_greeks()
self.display_greeks()

def display_greeks(self, alert=None):
portfolio_greeks = self.portfolio_greeks()
portfolio_greeks = self.greeks.portfolio_greeks(
use_cached_greeks=self.config.load_greeks,
publish_greeks=(not self.config.load_greeks),
vol_shock=0.0,
)
self.user_log(f"{portfolio_greeks=}")

def submit_market_order(self, instrument_id, quantity):
Expand Down Expand Up @@ -185,17 +187,10 @@ def user_log(self, msg):
# %%
# BacktestEngineConfig

# for saving and loading custom data greeks, use False, True then True, False below
load_greeks, stream_data = False, False
# for saving and loading custom data greeks, use True, False then False, True below
stream_data, load_greeks = False, True

actors = [
ImportableActorConfig(
actor_path=GreeksCalculator.fully_qualified_name(),
config_path=GreeksCalculatorConfig.fully_qualified_name(),
config={
"load_greeks": load_greeks,
},
),
ImportableActorConfig(
actor_path=InterestRateProvider.fully_qualified_name(),
config_path=InterestRateProviderConfig.fully_qualified_name(),
Expand Down Expand Up @@ -259,14 +254,16 @@ def user_log(self, msg):
]

if load_greeks:
data.append(
# Important note: when prepending custom data to usual market data, it will reach actors/strategies earlier
data = [
BacktestDataConfig(
data_cls=GreeksData.fully_qualified_name(),
catalog_path=catalog.path,
client_id="GreeksDataProvider",
metadata={"instrument_id": "ES"},
),
)
*data,
]

venues = [
BacktestVenueConfig(
Expand Down
7 changes: 7 additions & 0 deletions nautilus_trader/cache/base.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,10 @@ cdef class CacheFacade:

cpdef StrategyId strategy_id_for_order(self, ClientOrderId client_order_id)
cpdef StrategyId strategy_id_for_position(self, PositionId position_id)

# -- GREEKS QUERIES ---------------------------------------------------------------------------

cpdef void add_greeks(self, object greeks)
cpdef void add_interest_rate_curve(self, object interest_rate_curve)
cpdef object greeks(self, InstrumentId instrument_id)
cpdef object interest_rate_curve(self, str currency)
21 changes: 21 additions & 0 deletions nautilus_trader/cache/base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
# limitations under the License.
# -------------------------------------------------------------------------------------------------

from nautilus_trader.model.greeks import GreeksData
from nautilus_trader.model.greeks import InterestRateCurveData

from nautilus_trader.core.rust.model cimport PriceType
from nautilus_trader.model.data cimport Bar
from nautilus_trader.model.data cimport BarType
Expand Down Expand Up @@ -406,3 +409,21 @@ cdef class CacheFacade:
cpdef StrategyId strategy_id_for_position(self, PositionId position_id):
"""Abstract method (implement in subclass)."""
raise NotImplementedError("method `strategy_id_for_position` must be implemented in the subclass") # pragma: no cover

# -- STRATEGY QUERIES -----------------------------------------------------------------------------

cpdef void add_greeks(self, object greeks):
"""Abstract method (implement in subclass)."""
raise NotImplementedError("method `add_greeks` must be implemented in the subclass") # pragma: no cover

cpdef void add_interest_rate_curve(self, object interest_rate_curve):
"""Abstract method (implement in subclass)."""
raise NotImplementedError("method `add_interest_rate_curve` must be implemented in the subclass") # pragma: no cover

cpdef object greeks(self, InstrumentId instrument_id):
"""Abstract method (implement in subclass)."""
raise NotImplementedError("method `greeks` must be implemented in the subclass") # pragma: no cover

cpdef object interest_rate_curve(self, str currency):
"""Abstract method (implement in subclass)."""
raise NotImplementedError("method `interest_rate_curve` must be implemented in the subclass") # pragma: no cover
8 changes: 3 additions & 5 deletions nautilus_trader/cache/cache.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,9 @@ from nautilus_trader.cache.base cimport CacheFacade
from nautilus_trader.cache.facade cimport CacheDatabaseFacade
from nautilus_trader.common.actor cimport Actor
from nautilus_trader.common.component cimport Logger
from nautilus_trader.core.rust.model cimport AggregationSource
from nautilus_trader.core.rust.model cimport OmsType
from nautilus_trader.core.rust.model cimport OrderSide
from nautilus_trader.core.rust.model cimport PositionSide
from nautilus_trader.execution.messages cimport SubmitOrder
from nautilus_trader.execution.messages cimport SubmitOrderList
from nautilus_trader.model.book cimport OrderBook
from nautilus_trader.model.data cimport Bar
from nautilus_trader.model.data cimport BarType
Expand All @@ -38,7 +35,6 @@ from nautilus_trader.model.identifiers cimport AccountId
from nautilus_trader.model.identifiers cimport ClientId
from nautilus_trader.model.identifiers cimport ClientOrderId
from nautilus_trader.model.identifiers cimport InstrumentId
from nautilus_trader.model.identifiers cimport OrderListId
from nautilus_trader.model.identifiers cimport PositionId
from nautilus_trader.model.identifiers cimport StrategyId
from nautilus_trader.model.identifiers cimport Venue
Expand All @@ -47,7 +43,6 @@ from nautilus_trader.model.instruments.base cimport Instrument
from nautilus_trader.model.instruments.synthetic cimport SyntheticInstrument
from nautilus_trader.model.objects cimport Currency
from nautilus_trader.model.objects cimport Money
from nautilus_trader.model.objects cimport Quantity
from nautilus_trader.model.orders.base cimport Order
from nautilus_trader.model.orders.list cimport OrderList
from nautilus_trader.model.position cimport Position
Expand Down Expand Up @@ -75,6 +70,8 @@ cdef class Cache(CacheFacade):
cdef dict _order_lists
cdef dict _positions
cdef dict _position_snapshots
cdef dict _greeks
cdef dict _interest_rate_curves

cdef dict _index_venue_account
cdef dict _index_venue_orders
Expand Down Expand Up @@ -165,6 +162,7 @@ cdef class Cache(CacheFacade):
cpdef void add_order_list(self, OrderList order_list)
cpdef void add_position_id(self, PositionId position_id, Venue venue, ClientOrderId client_order_id, StrategyId strategy_id)
cpdef void add_position(self, Position position, OmsType oms_type)

cpdef void snapshot_position(self, Position position)
cpdef void snapshot_position_state(self, Position position, uint64_t ts_snapshot, Money unrealized_pnl=*, bint open_only=*)
cpdef void snapshot_order_state(self, Order order)
Expand Down
15 changes: 14 additions & 1 deletion nautilus_trader/cache/cache.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ from nautilus_trader.core.rust.model cimport OrderSide
from nautilus_trader.core.rust.model cimport PositionSide
from nautilus_trader.core.rust.model cimport PriceType
from nautilus_trader.core.rust.model cimport TriggerType
from nautilus_trader.execution.messages cimport SubmitOrder
from nautilus_trader.model.data cimport Bar
from nautilus_trader.model.data cimport BarAggregation
from nautilus_trader.model.data cimport BarSpecification
Expand Down Expand Up @@ -126,6 +125,8 @@ cdef class Cache(CacheFacade):
self._order_lists: dict[OrderListId, OrderList] = {}
self._positions: dict[PositionId, Position] = {}
self._position_snapshots: dict[PositionId, list[bytes]] = {}
self._greeks: dict[InstrumentId, object] = {}
self._interest_rate_curves: dict[str, object] = {}

# Cache index
self._index_venue_account: dict[Venue, AccountId] = {}
Expand Down Expand Up @@ -1688,6 +1689,18 @@ cdef class Cache(CacheFacade):
# Update database
self._database.add_position(position)

cpdef void add_greeks(self, object greeks):
self._greeks[greeks.instrument_id] = greeks

cpdef void add_interest_rate_curve(self, object interest_rate_curve):
self._interest_rate_curves[interest_rate_curve.currency] = interest_rate_curve

cpdef object greeks(self, InstrumentId instrument_id):
return self._greeks.get(instrument_id)

cpdef object interest_rate_curve(self, str currency):
return self._interest_rate_curves.get(currency)

cpdef void snapshot_position(self, Position position):
"""
Snapshot the given position in its current state.
Expand Down
8 changes: 3 additions & 5 deletions nautilus_trader/common/actor.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,9 @@
# limitations under the License.
# -------------------------------------------------------------------------------------------------

from typing import Callable

from cpython.datetime cimport datetime
from libc.stdint cimport uint64_t

from nautilus_trader.risk.greeks import GreeksData

from nautilus_trader.cache.base cimport CacheFacade
from nautilus_trader.common.component cimport Clock
from nautilus_trader.common.component cimport Component
Expand Down Expand Up @@ -47,6 +43,7 @@ from nautilus_trader.model.identifiers cimport Venue
from nautilus_trader.model.instruments.base cimport Instrument
from nautilus_trader.model.instruments.synthetic cimport SyntheticInstrument
from nautilus_trader.portfolio.base cimport PortfolioFacade
from nautilus_trader.risk.greeks cimport GreeksCalculator


cdef class Actor(Component):
Expand All @@ -56,7 +53,6 @@ cdef class Actor(Component):
cdef set[type] _warning_events
cdef dict[UUID4, object] _pending_requests
cdef set[type] _pyo3_conversion_types
cdef dict[InstrumentId, list[GreeksData]] _future_greeks
cdef dict[str, type] _signal_classes
cdef list[Indicator] _indicators
cdef dict[InstrumentId, list[Indicator]] _indicators_for_quotes
Expand All @@ -75,6 +71,8 @@ cdef class Actor(Component):
"""The message bus for the actor (if registered).\n\n:returns: `MessageBus` or ``None``"""
cdef readonly CacheFacade cache
"""The read-only cache for the actor.\n\n:returns: `CacheFacade`"""
cdef readonly GreeksCalculator greeks
"""The read-only greeks calculator for the actor.\n\n:returns: `GreeksCalculator`"""

cpdef bint indicators_initialized(self)

Expand Down
Loading

0 comments on commit d6e7b75

Please sign in to comment.