Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/quantlib_st/sysdata/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Futures Example: External API Stub

This folder contains a minimal, working example that shows how to implement a futures data source for carry and backadjustment.

## Files

- [futures_simplified.py](futures_simplified.py) — A mock implementation of `futuresSimData` that returns synthetic prices for one instrument (`BRT`) with pre-built `PRICE/CARRY/FORWARD` series.
- [futures_simplified_with_roll.py](futures_simplified_with_roll.py) — A mock implementation that derives `PRICE/CARRY/FORWARD` from per-contract prices using roll calendars and roll parameters.

## What you need to implement

The example class `MockApiFuturesSimData` in [futures_simplified.py](futures_simplified.py) shows how to implement the abstract methods from `futuresSimData`:

1. **Instrument catalog**
- `get_instrument_list()`
- `get_instrument_asset_classes()`

2. **Costs + metadata**
- `get_spread_cost()`
- `get_instrument_meta_data()`
- `get_instrument_object_with_meta_data()`

3. **Roll setup**
- `get_roll_parameters()`

4. **Core price methods**
- `get_multiple_prices_from_start_date()`
- `get_backadjusted_futures_price()` (usually calls the helper `_get_backadjusted_futures_price_from_multiple_prices`)

## What to replace with API calls

In the examples, the following are mock implementations:

- `get_multiple_prices_from_start_date()` in [futures_simplified.py](futures_simplified.py) → replace with your API call to fetch:
- price series for the traded contract (PRICE)
- price series for the carry contract (CARRY)
- price series for the forward contract (FORWARD)
- the corresponding contract IDs (`PRICE_CONTRACT`, `CARRY_CONTRACT`, `FORWARD_CONTRACT`)

- `_mock_contract_prices()` in [futures_simplified_with_roll.py](futures_simplified_with_roll.py) → replace with your API call to fetch per-contract final prices:
- one price series per contract (keyed by `YYYYMM`)
- these are used to build a roll calendar and derive `PRICE/CARRY/FORWARD`

The example uses `BRT` with contract IDs `202512`, `202511`, `202510`, etc., purely as placeholders.

## Data shape required for futuresMultiplePrices

Your API output must be converted into a `futuresMultiplePrices` object with a `DatetimeIndex` and these columns:

- `PRICE`
- `CARRY`
- `FORWARD`
- `PRICE_CONTRACT`
- `CARRY_CONTRACT`
- `FORWARD_CONTRACT`

Once you have this, `futuresSimData` can compute:

- **Carry metrics** (roll, annualised roll)
- **Backadjusted futures prices** using `_get_backadjusted_futures_price_from_multiple_prices`

## When you might need per-contract data

If your API provides raw per-contract data (one contract per ticker), you can also implement a data source that inherits from `futuresContractPriceData`.

See:

- [sysdata/futures/README.md](../futures/README.md)
- [sysdata/futures/futures_per_contract_prices.py](../futures/futures_per_contract_prices.py)
- [objects/dict_of_futures_per_contract_prices.py](../../objects/dict_of_futures_per_contract_prices.py)

These per-contract classes are **optional** for carry/backadjustment if you already have continuous `PRICE`, `CARRY`, and `FORWARD` series.
Empty file.
155 changes: 155 additions & 0 deletions src/quantlib_st/sysdata/examples/futures_simplified.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""
Example: Minimal futuresSimData implementation with mock data.

This module is intended as a template for plugging in an external API that returns
per-contract futures prices. Replace the mock data generators with API calls.
"""

from __future__ import annotations

import datetime as dt
import numpy as np
import pandas as pd

from quantlib_st.objects.instruments import (
assetClassesAndInstruments,
futuresInstrument,
futuresInstrumentWithMetaData,
instrumentMetaData,
)
from quantlib_st.objects.multiple_prices import futuresMultiplePrices
from quantlib_st.objects.dict_of_named_futures_per_contract_prices import (
price_name,
carry_name,
forward_name,
contract_name_from_column_name,
)
from quantlib_st.objects.adjusted_prices import futuresAdjustedPrices
from quantlib_st.objects.rolls import rollParameters
from quantlib_st.sysdata.sim.futures_sim_data import futuresSimData

price_contract_name = contract_name_from_column_name(price_name)
carry_contract_name = contract_name_from_column_name(carry_name)
forward_contract_name = contract_name_from_column_name(forward_name)


class MockApiFuturesSimData(futuresSimData):
"""
Minimal, end-to-end futuresSimData example.

Replace the mock methods with your external API calls.
"""

def __init__(self, start_date: dt.datetime | None = None):
super().__init__()
self._start_date_for_data_from_config = start_date or dt.datetime(2024, 1, 2)

def get_instrument_list(self) -> list[str]:
return ["BRT"]

def get_instrument_asset_classes(self) -> assetClassesAndInstruments:
series = pd.Series({"BRT": "Energy"})
return assetClassesAndInstruments.from_pd_series(series)

def get_spread_cost(self, instrument_code: str) -> float:
return 0.01

def get_roll_parameters(self, instrument_code: str) -> rollParameters:
return rollParameters(
hold_rollcycle="FGHJKMNQUVXZ",
priced_rollcycle="FGHJKMNQUVXZ",
roll_offset_day=0,
carry_offset=-1,
approx_expiry_offset=0,
)

def get_instrument_meta_data(
self, instrument_code: str
) -> futuresInstrumentWithMetaData:
instrument = futuresInstrument(instrument_code)
meta = instrumentMetaData(
Description="Brent Crude Oil (Mock)",
Pointsize=1000.0,
Currency="USD",
AssetClass="Energy",
PerBlock=0.0,
Percentage=0.0,
PerTrade=0.0,
Region="Global",
)
return futuresInstrumentWithMetaData(instrument, meta)

def get_instrument_object_with_meta_data(
self, instrument_code: str
) -> futuresInstrumentWithMetaData:
return self.get_instrument_meta_data(instrument_code)

def get_backadjusted_futures_price(
self, instrument_code: str
) -> futuresAdjustedPrices:
return self._get_backadjusted_futures_price_from_multiple_prices(
instrument_code,
backadjust_methodology="diff_adjusted",
)

def get_multiple_prices_from_start_date(
self, instrument_code: str, start_date: dt.datetime
) -> futuresMultiplePrices:
"""
Mock implementation using synthetic prices and a rolling contract schedule.

Replace `_mock_multiple_prices` with API calls returning the raw contract
prices and contract IDs.
"""
if instrument_code != "BRT":
return futuresMultiplePrices.create_empty()

return self._mock_multiple_prices(start_date)

def _mock_multiple_prices(
self, start_date: dt.datetime, days: int = 15
) -> futuresMultiplePrices:
index = pd.date_range(start=start_date, periods=days, freq="B")

# Synthetic price series
base_price = 75.0 + np.linspace(0, 1.4, num=days)
price_series = pd.Series(base_price, index=index)
carry_series = price_series + 0.2
forward_series = price_series + 0.4

# Mock contract IDs (YYYYMM) that change over time
contract_schedule = ["202512", "202511", "202510"]
chunk = int(np.ceil(days / len(contract_schedule)))
price_contracts = (
[contract for contract in contract_schedule for _ in range(chunk)]
)[:days]

contract_map = {
"202512": ("202511", "202510"),
"202511": ("202510", "202509"),
"202510": ("202509", "202508"),
}
carry_contracts = [contract_map[c][0] for c in price_contracts]
forward_contracts = [contract_map[c][1] for c in price_contracts]

data = pd.DataFrame(
{
price_name: price_series,
carry_name: carry_series,
forward_name: forward_series,
price_contract_name: price_contracts,
carry_contract_name: carry_contracts,
forward_contract_name: forward_contracts,
},
index=index,
)

return futuresMultiplePrices(data)


if __name__ == "__main__":
data = MockApiFuturesSimData()
multiple_prices = data.get_multiple_prices("BRT")
backadjusted = data.get_backadjusted_futures_price("BRT")
print(multiple_prices.tail(3))
print(backadjusted.tail(3))
139 changes: 139 additions & 0 deletions src/quantlib_st/sysdata/examples/futures_simplified_with_roll.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""
Example: futuresSimData implementation using roll calendars derived from per-contract prices.

This shows how to:
1) Provide per-contract final prices (dictFuturesContractFinalPrices)
2) Build a roll calendar from roll parameters
3) Build multiple prices (PRICE/CARRY/FORWARD) from that calendar

Replace the mock price generation with API calls.
"""

from __future__ import annotations

import datetime as dt

import numpy as np
import pandas as pd

from quantlib_st.objects.instruments import (
assetClassesAndInstruments,
futuresInstrument,
futuresInstrumentWithMetaData,
instrumentMetaData,
)
from quantlib_st.objects.dict_of_futures_per_contract_prices import (
dictFuturesContractFinalPrices,
)
from quantlib_st.objects.multiple_prices import futuresMultiplePrices
from quantlib_st.objects.adjusted_prices import futuresAdjustedPrices
from quantlib_st.objects.rolls import rollParameters
from quantlib_st.objects.roll_calendars import rollCalendar
from quantlib_st.sysdata.sim.futures_sim_data import futuresSimData


class MockApiFuturesSimDataWithRoll(futuresSimData):
"""
Example implementation that derives PRICE/CARRY/FORWARD from per-contract prices.

Replace `_mock_contract_prices` with calls to your external API.
"""

def __init__(self, start_date: dt.datetime | None = None):
super().__init__()
self._start_date_for_data_from_config = start_date or dt.datetime(2024, 1, 2)

def get_instrument_list(self) -> list[str]:
return ["BRT"]

def get_instrument_asset_classes(self) -> assetClassesAndInstruments:
series = pd.Series({"BRT": "Energy"})
return assetClassesAndInstruments.from_pd_series(series)

def get_spread_cost(self, instrument_code: str) -> float:
return 0.01

def get_roll_parameters(self, instrument_code: str) -> rollParameters:
return rollParameters(
hold_rollcycle="FGHJKMNQUVXZ",
priced_rollcycle="FGHJKMNQUVXZ",
roll_offset_day=0,
carry_offset=-1,
approx_expiry_offset=0,
)

def get_instrument_meta_data(
self, instrument_code: str
) -> futuresInstrumentWithMetaData:
instrument = futuresInstrument(instrument_code)
meta = instrumentMetaData(
Description="Brent Crude Oil (Mock)",
Pointsize=1000.0,
Currency="USD",
AssetClass="Energy",
PerBlock=0.0,
Percentage=0.0,
PerTrade=0.0,
Region="Global",
)
return futuresInstrumentWithMetaData(instrument, meta)

def get_instrument_object_with_meta_data(
self, instrument_code: str
) -> futuresInstrumentWithMetaData:
return self.get_instrument_meta_data(instrument_code)

def get_backadjusted_futures_price(
self, instrument_code: str
) -> futuresAdjustedPrices:
return self._get_backadjusted_futures_price_from_multiple_prices(
instrument_code,
backadjust_methodology="diff_adjusted",
)

def get_multiple_prices_from_start_date(
self, instrument_code: str, start_date: dt.datetime
) -> futuresMultiplePrices:
if instrument_code != "BRT":
return futuresMultiplePrices.create_empty()

# 1) Build per-contract final prices (mocked here)
contract_prices = self._mock_contract_prices(start_date)

# 2) Build roll calendar based on roll parameters + available contracts
roll_params = self.get_roll_parameters(instrument_code)
calendar = rollCalendar.create_from_prices(contract_prices, roll_params)

# 3) Convert per-contract data into multiple prices (PRICE/CARRY/FORWARD)
multiple_prices = futuresMultiplePrices.create_from_raw_data(
calendar, contract_prices
)

# Ensure we respect the requested start date
filtered = pd.DataFrame(multiple_prices).loc[start_date:]
return futuresMultiplePrices(filtered)

def _mock_contract_prices(
self, start_date: dt.datetime, days: int = 30
) -> dictFuturesContractFinalPrices:
index = pd.date_range(start=start_date, periods=days, freq="B")

# Mock contract list (YYYYMM)
contract_ids = ["202510", "202511", "202512", "202601"]

price_dict = {}
for i, contract_id in enumerate(contract_ids):
# Each contract series is a smooth trend with a small offset
base = 75.0 + (i * 0.5)
series = pd.Series(base + np.linspace(0, 1.0, num=len(index)), index=index)
price_dict[contract_id] = series

return dictFuturesContractFinalPrices(price_dict)


if __name__ == "__main__":
data = MockApiFuturesSimDataWithRoll()
mp = data.get_multiple_prices("BRT")
backadj = data.get_backadjusted_futures_price("BRT")
print(mp.tail(3))
print(backadj.tail(3))