Skip to content

Commit

Permalink
Refine futures and options contracts expiration
Browse files Browse the repository at this point in the history
  • Loading branch information
cjdsellers committed Nov 1, 2023
1 parent 0914297 commit 3bf7e1c
Show file tree
Hide file tree
Showing 23 changed files with 326 additions and 123 deletions.
16 changes: 14 additions & 2 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@ Released on TBC (UTC).
- Added `support_contingent_orders` option for venues (to simulate venues which do not support contingent orders)
- Added `StrategyConfig.manage_contingent_orders` option (to automatically manage **open** contingenct orders)
- Improved `RedisCacheDatabase` client connection error handling with retries

### Breaking Changes
- Added `FuturesContract.activation_utc` property which returns a `pd.Timestamp` tz-aware (UTC)
- Added `OptionsContract.activation_utc` property which returns a `pd.Timestamp` tz-aware (UTC)
- Added `CryptoFuture.activation_utc` property which returns a `pd.Timestamp` tz-aware (UTC)
- Added `FuturesContract.expiration_utc` property which returns a `pd.Timestamp` tz-aware (UTC)
- Added `OptionsContract.expiration_utc` property which returns a `pd.Timestamp` tz-aware (UTC)
- Added `CryptoFuture.expiration_utc` property which returns a `pd.Timestamp` tz-aware (UTC)

### Breaking Changes
- Renamed `FuturesContract.expiry_date` to `expiration_ns` (and associated params) as `uint64_t` UNIX nanoseconds
- Renamed `OptionsContract.expiry_date` to `expiration_ns` (and associated params) as `uint64_t` UNIX nanoseconds
- Renamed `CryptoFuture.expiry_date` to `expiration_ns` (and associated params) as `uint64_t` UNIX nanoseconds
- Changed `FuturesContract` arrow schema
- Changed `OptionsContract` arrow schema
- Changed `CryptoFuture` arrow schema
- Transformed orders will now retain the original `ts_init` timestamp
- Removed unimplemented `batch_more` option for `Strategy.modify_order`
- Removed `InstrumentProvider.venue` property (redundant as a provider may have many venues)
Expand Down
11 changes: 8 additions & 3 deletions nautilus_core/model/src/instruments/crypto_future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ pub struct CryptoFuture {
pub underlying: Currency,
pub quote_currency: Currency,
pub settlement_currency: Currency,
pub expiration: UnixNanos,
pub activation_ns: UnixNanos,
pub expiration_ns: UnixNanos,
pub price_precision: u8,
pub size_precision: u8,
pub price_increment: Price,
Expand All @@ -68,7 +69,8 @@ impl CryptoFuture {
underlying: Currency,
quote_currency: Currency,
settlement_currency: Currency,
expiration: UnixNanos,
activation_ns: UnixNanos,
expiration_ns: UnixNanos,
price_precision: u8,
size_precision: u8,
price_increment: Price,
Expand All @@ -91,7 +93,8 @@ impl CryptoFuture {
underlying,
quote_currency,
settlement_currency,
expiration,
activation_ns,
expiration_ns,
price_precision,
size_precision,
price_increment,
Expand Down Expand Up @@ -236,13 +239,15 @@ pub mod stubs {

#[fixture]
pub fn crypto_future_btcusdt() -> CryptoFuture {
let activation = Utc.with_ymd_and_hms(2014, 4, 8, 0, 0, 0).unwrap();
let expiration = Utc.with_ymd_and_hms(2014, 7, 8, 0, 0, 0).unwrap();
CryptoFuture::new(
InstrumentId::from("ETHUSDT-123.BINANCE"),
Symbol::from("BTCUSDT"),
Currency::from("BTC"),
Currency::from("USDT"),
Currency::from("USDT"),
activation.timestamp_nanos_opt().unwrap() as UnixNanos,
expiration.timestamp_nanos_opt().unwrap() as UnixNanos,
2,
6,
Expand Down
11 changes: 8 additions & 3 deletions nautilus_core/model/src/instruments/futures_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ pub struct FuturesContract {
pub raw_symbol: Symbol,
pub asset_class: AssetClass,
pub underlying: String,
pub expiration: UnixNanos,
pub activation_ns: UnixNanos,
pub expiration_ns: UnixNanos,
pub currency: Currency,
pub price_precision: u8,
pub price_increment: Price,
Expand All @@ -64,7 +65,8 @@ impl FuturesContract {
raw_symbol: Symbol,
asset_class: AssetClass,
underlying: String,
expiration: UnixNanos,
activation_ns: UnixNanos,
expiration_ns: UnixNanos,
currency: Currency,
price_precision: u8,
price_increment: Price,
Expand All @@ -84,7 +86,8 @@ impl FuturesContract {
raw_symbol,
asset_class,
underlying,
expiration,
activation_ns,
expiration_ns,
currency,
price_precision,
price_increment,
Expand Down Expand Up @@ -227,12 +230,14 @@ pub mod stubs {

#[fixture]
pub fn futures_contract_es() -> FuturesContract {
let activation = Utc.with_ymd_and_hms(2021, 4, 8, 0, 0, 0).unwrap();
let expiration = Utc.with_ymd_and_hms(2021, 7, 8, 0, 0, 0).unwrap();
FuturesContract::new(
InstrumentId::new(Symbol::from("ESZ21"), Venue::from("CME")),
Symbol::from("ESZ21"),
AssetClass::Index,
String::from("ES"),
activation.timestamp_nanos_opt().unwrap() as UnixNanos,
expiration.timestamp_nanos_opt().unwrap() as UnixNanos,
Currency::USD(),
2,
Expand Down
11 changes: 8 additions & 3 deletions nautilus_core/model/src/instruments/options_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ pub struct OptionsContract {
pub asset_class: AssetClass,
pub underlying: String,
pub option_kind: OptionKind,
pub expiration: UnixNanos,
pub activation_ns: UnixNanos,
pub expiration_ns: UnixNanos,
pub strike_price: Price,
pub currency: Currency,
pub price_precision: u8,
Expand All @@ -66,7 +67,8 @@ impl OptionsContract {
asset_class: AssetClass,
underlying: String,
option_kind: OptionKind,
expiration: UnixNanos,
activation_ns: UnixNanos,
expiration_ns: UnixNanos,
strike_price: Price,
currency: Currency,
price_precision: u8,
Expand All @@ -87,7 +89,8 @@ impl OptionsContract {
asset_class,
underlying,
option_kind,
expiration,
activation_ns,
expiration_ns,
strike_price,
currency,
price_precision,
Expand Down Expand Up @@ -230,13 +233,15 @@ pub mod stubs {

#[fixture]
pub fn options_contract_appl() -> OptionsContract {
let activation = Utc.with_ymd_and_hms(2021, 9, 17, 0, 0, 0).unwrap();
let expiration = Utc.with_ymd_and_hms(2021, 12, 17, 0, 0, 0).unwrap();
OptionsContract::new(
InstrumentId::from("AAPL211217C00150000.OPRA"),
Symbol::from("AAPL211217C00150000"),
AssetClass::Equity,
String::from("AAPL"),
OptionKind::Call,
activation.timestamp_nanos_opt().unwrap() as UnixNanos,
expiration.timestamp_nanos_opt().unwrap() as UnixNanos,
Price::from("149.0"),
Currency::USD(),
Expand Down
9 changes: 6 additions & 3 deletions nautilus_core/model/src/python/instruments/crypto_future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ impl CryptoFuture {
underlying: Currency,
quote_currency: Currency,
settlement_currency: Currency,
expiration: UnixNanos,
activation_ns: UnixNanos,
expiration_ns: UnixNanos,
price_precision: u8,
size_precision: u8,
price_increment: Price,
Expand All @@ -64,7 +65,8 @@ impl CryptoFuture {
underlying,
quote_currency,
settlement_currency,
expiration,
activation_ns,
expiration_ns,
price_precision,
size_precision,
price_increment,
Expand Down Expand Up @@ -114,7 +116,8 @@ impl CryptoFuture {
"settlement_currency",
self.settlement_currency.code.to_string(),
)?;
dict.set_item("expiration", self.expiration.to_i64())?;
dict.set_item("activation_ns", self.activation_ns.to_u64())?;
dict.set_item("expiration_ns", self.expiration_ns.to_u64())?;
dict.set_item("price_precision", self.price_precision)?;
dict.set_item("size_precision", self.size_precision)?;
dict.set_item("price_increment", self.price_increment.to_string())?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ impl FuturesContract {
raw_symbol: Symbol,
asset_class: AssetClass,
underlying: String,
expiration: UnixNanos,
activation_ns: UnixNanos,
expiration_ns: UnixNanos,
currency: Currency,
price_precision: u8,
price_increment: Price,
Expand All @@ -61,7 +62,8 @@ impl FuturesContract {
raw_symbol,
asset_class,
underlying,
expiration,
activation_ns,
expiration_ns,
currency,
price_precision,
price_increment,
Expand Down Expand Up @@ -106,7 +108,8 @@ impl FuturesContract {
dict.set_item("raw_symbol", self.raw_symbol.to_string())?;
dict.set_item("asset_class", self.asset_class.to_string())?;
dict.set_item("underlying", self.underlying.to_string())?;
dict.set_item("expiration", self.expiration.to_i64())?;
dict.set_item("activation_ns", self.activation_ns.to_u64())?;
dict.set_item("expiration_ns", self.expiration_ns.to_u64())?;
dict.set_item("currency", self.currency.code.to_string())?;
dict.set_item("price_precision", self.price_precision)?;
dict.set_item("price_increment", self.price_increment.to_string())?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ impl OptionsContract {
asset_class: AssetClass,
underlying: String,
option_kind: OptionKind,
expiration: UnixNanos,
activation_ns: UnixNanos,
expiration_ns: UnixNanos,
strike_price: Price,
currency: Currency,
price_precision: u8,
Expand All @@ -63,7 +64,8 @@ impl OptionsContract {
asset_class,
underlying,
option_kind,
expiration,
activation_ns,
expiration_ns,
strike_price,
currency,
price_precision,
Expand Down Expand Up @@ -109,7 +111,8 @@ impl OptionsContract {
dict.set_item("asset_class", self.asset_class.to_string())?;
dict.set_item("underlying", self.underlying.to_string())?;
dict.set_item("option_kind", self.option_kind.to_string())?;
dict.set_item("expiration", self.expiration.to_i64())?;
dict.set_item("activation_ns", self.activation_ns.to_u64())?;
dict.set_item("expiration_ns", self.expiration_ns.to_u64())?;
dict.set_item("strike_price", self.strike_price.to_string())?;
dict.set_item("currency", self.currency.code.to_string())?;
dict.set_item("price_precision", self.price_precision)?;
Expand Down
11 changes: 9 additions & 2 deletions nautilus_trader/adapters/binance/futures/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
# limitations under the License.
# -------------------------------------------------------------------------------------------------

from datetime import datetime as dt
from decimal import Decimal

import msgspec
import pandas as pd

from nautilus_trader.adapters.binance.common.constants import BINANCE_VENUE
from nautilus_trader.adapters.binance.common.enums import BinanceAccountType
Expand Down Expand Up @@ -328,13 +328,20 @@ def _parse_instrument(
BinanceFuturesContractType.NEXT_MONTH,
BinanceFuturesContractType.NEXT_QUARTER,
):
expiry_date_part = symbol_info.symbol.partition("_")[2]
expiration = pd.to_datetime(expiry_date_part, format="%y%m%d", utc=True)
expiration += pd.Timedelta(hours=8)

activation = expiration - pd.Timedelta(days=90) # TODO: Improve accuracy

instrument = CryptoFuture(
instrument_id=instrument_id,
raw_symbol=raw_symbol,
underlying=base_currency,
quote_currency=quote_currency,
settlement_currency=settlement_currency,
expiry_date=dt.strptime(symbol_info.symbol.partition("_")[2], "%y%m%d").date(),
activation_ns=activation.value,
expiration_ns=expiration.value,
price_precision=price_precision,
size_precision=size_precision,
price_increment=price_increment,
Expand Down
35 changes: 25 additions & 10 deletions nautilus_trader/adapters/interactive_brokers/parsing/instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
from decimal import Decimal

import msgspec
import pandas as pd

# fmt: off
from nautilus_trader.adapters.interactive_brokers.common import IBContract
from nautilus_trader.adapters.interactive_brokers.common import IBContractDetails
from nautilus_trader.core.correctness import PyCondition
from nautilus_trader.model.currency import Currency
from nautilus_trader.model.enums import AssetClass
from nautilus_trader.model.enums import OptionKind
from nautilus_trader.model.enums import asset_class_from_str
from nautilus_trader.model.identifiers import InstrumentId
Expand Down Expand Up @@ -94,7 +96,7 @@
re_crypto = re.compile(r"^(?P<symbol>[A-Z]*)\/(?P<currency>[A-Z]{3})$")


def _extract_isin(details: IBContractDetails):
def _extract_isin(details: IBContractDetails) -> int:
for tag_value in details.secIdList:
if tag_value.tag == "ISIN":
return tag_value.value
Expand All @@ -106,7 +108,7 @@ def _tick_size_to_precision(tick_size: float | Decimal) -> int:
return len(tick_size_str.partition(".")[2].rstrip("0"))


def sec_type_to_asset_class(sec_type: str):
def sec_type_to_asset_class(sec_type: str) -> AssetClass:
mapping = {
"STK": "EQUITY",
"IND": "INDEX",
Expand Down Expand Up @@ -145,6 +147,7 @@ def parse_equity_contract(details: IBContractDetails) -> Equity:
price_precision: int = _tick_size_to_precision(details.minTick)
timestamp = time.time_ns()
instrument_id = ib_contract_to_instrument_id(details.contract)

return Equity(
instrument_id=instrument_id,
raw_symbol=Symbol(details.contract.localSymbol),
Expand All @@ -166,6 +169,13 @@ def parse_futures_contract(
price_precision: int = _tick_size_to_precision(details.minTick)
timestamp = time.time_ns()
instrument_id = ib_contract_to_instrument_id(details.contract)
expiration = pd.to_datetime( # TODO: Check correctness
details.contract.lastTradeDateOrContractMonth,
format="%Y%m%d",
utc=True,
)
activation = expiration - pd.Timedelta(days=90) # TODO: Make this more accurate

return FuturesContract(
instrument_id=instrument_id,
raw_symbol=Symbol(details.contract.localSymbol),
Expand All @@ -176,10 +186,8 @@ def parse_futures_contract(
multiplier=Quantity.from_str(details.contract.multiplier),
lot_size=Quantity.from_int(1),
underlying=details.underSymbol,
expiry_date=datetime.datetime.strptime(
details.contract.lastTradeDateOrContractMonth,
"%Y%m%d",
).date(),
activation_ns=activation.value,
expiration_ns=expiration.value,
ts_event=timestamp,
ts_init=timestamp,
info=contract_details_to_dict(details),
Expand All @@ -197,6 +205,13 @@ def parse_options_contract(
"C": OptionKind.CALL,
"P": OptionKind.PUT,
}[details.contract.right]
expiration = pd.to_datetime( # TODO: Check correctness
details.contract.lastTradeDateOrContractMonth,
format="%Y%m%d",
utc=True,
)
activation = expiration - pd.Timedelta(days=90) # TODO: Make this more accurate

return OptionsContract(
instrument_id=instrument_id,
raw_symbol=Symbol(details.contract.localSymbol),
Expand All @@ -208,10 +223,8 @@ def parse_options_contract(
lot_size=Quantity.from_int(1),
underlying=details.underSymbol,
strike_price=Price(details.contract.strike, price_precision),
expiry_date=datetime.datetime.strptime(
details.contract.lastTradeDateOrContractMonth,
"%Y%m%d",
).date(),
activation_ns=activation.value,
expiration_ns=expiration.value,
kind=kind,
ts_event=timestamp,
ts_init=timestamp,
Expand All @@ -226,6 +239,7 @@ def parse_forex_contract(
size_precision: int = _tick_size_to_precision(details.minSize)
timestamp = time.time_ns()
instrument_id = ib_contract_to_instrument_id(details.contract)

return CurrencyPair(
instrument_id=instrument_id,
raw_symbol=Symbol(details.contract.localSymbol),
Expand Down Expand Up @@ -259,6 +273,7 @@ def parse_crypto_contract(
size_precision: int = _tick_size_to_precision(details.minSize)
timestamp = time.time_ns()
instrument_id = ib_contract_to_instrument_id(details.contract)

return CryptoPerpetual(
instrument_id=instrument_id,
raw_symbol=Symbol(details.contract.localSymbol),
Expand Down
Loading

0 comments on commit 3bf7e1c

Please sign in to comment.