mxm-refdata 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mxm/refdata/__init__.py +1 -0
- mxm/refdata/api/__init__.py +1 -0
- mxm/refdata/api/ref_data_api.py +607 -0
- mxm/refdata/cli.py +218 -0
- mxm/refdata/data/first_day_of_interest_rule.json +88 -0
- mxm/refdata/data/futures_products.csv +6 -0
- mxm/refdata/data/last_trading_rule.json +34 -0
- mxm/refdata/database/__init__.py +1 -0
- mxm/refdata/database/sql_session_manager.py +135 -0
- mxm/refdata/mappings/__init__.py +31 -0
- mxm/refdata/mappings/futures_contract_vs_orm.py +51 -0
- mxm/refdata/mappings/futures_product_vs_orm.py +56 -0
- mxm/refdata/mappings/period_cycles_vs_orm.py +102 -0
- mxm/refdata/mappings/period_vs_orm.py +40 -0
- mxm/refdata/models/__init__.py +31 -0
- mxm/refdata/models/contracts/__init__.py +0 -0
- mxm/refdata/models/contracts/futures_contract.py +22 -0
- mxm/refdata/models/currencies.py +24 -0
- mxm/refdata/models/months.py +72 -0
- mxm/refdata/models/orm/__init__.py +16 -0
- mxm/refdata/models/orm/base.py +3 -0
- mxm/refdata/models/orm/futures_contracts.py +53 -0
- mxm/refdata/models/orm/futures_products.py +61 -0
- mxm/refdata/models/orm/period_cycles.py +84 -0
- mxm/refdata/models/orm/periods.py +30 -0
- mxm/refdata/models/period_cycles.py +73 -0
- mxm/refdata/models/periods.py +64 -0
- mxm/refdata/models/products/__init__.py +1 -0
- mxm/refdata/models/products/futures_product.py +38 -0
- mxm/refdata/models/products/settlement.py +10 -0
- mxm/refdata/models/reference_events.py +9 -0
- mxm/refdata/models/units.py +28 -0
- mxm/refdata/models/weekdays.py +66 -0
- mxm/refdata/parsing/__init__.py +1 -0
- mxm/refdata/parsing/futures_products_from_csv.py +76 -0
- mxm/refdata/py.typed +0 -0
- mxm/refdata/scripts/__init__.py +1 -0
- mxm/refdata/scripts/db_utils.py +61 -0
- mxm/refdata/scripts/manage_static_ref_data.py +73 -0
- mxm/refdata/services/__init__.py +0 -0
- mxm/refdata/services/bootstrap.py +84 -0
- mxm/refdata/services/futures_contract_factory.py +127 -0
- mxm/refdata/services/futures_product_factory.py +132 -0
- mxm/refdata/services/period_factory.py +315 -0
- mxm/refdata/services/ref_data_service.py +322 -0
- mxm/refdata/services/smokecheck.py +326 -0
- mxm/refdata/trading_calendars/__init__.py +0 -0
- mxm/refdata/trading_calendars/first_day_of_interest.py +74 -0
- mxm/refdata/trading_calendars/last_trading_day.py +117 -0
- mxm/refdata/trading_calendars/nth_business_day.py +52 -0
- mxm/refdata/trading_calendars/nth_calendar_day_of_period.py +43 -0
- mxm/refdata/trading_calendars/nth_weekday_of_period.py +50 -0
- mxm/refdata/trading_calendars/trading_calendar.py +186 -0
- mxm/refdata/utils/__init__.py +1 -0
- mxm/refdata/utils/cache_manager.py +33 -0
- mxm/refdata/utils/config.py +32 -0
- mxm/refdata/utils/period_types_codec.py +16 -0
- mxm/refdata/utils/regex_patterns.py +18 -0
- mxm/refdata/utils/resources.py +29 -0
- mxm_refdata-0.3.0.dist-info/METADATA +228 -0
- mxm_refdata-0.3.0.dist-info/RECORD +64 -0
- mxm_refdata-0.3.0.dist-info/WHEEL +4 -0
- mxm_refdata-0.3.0.dist-info/entry_points.txt +3 -0
- mxm_refdata-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mxm.refdata.mappings.period_cycles_vs_orm
|
|
3
|
+
|
|
4
|
+
ORM ↔ domain mappings for PeriodCycle and PeriodCycleMembership.
|
|
5
|
+
|
|
6
|
+
Purpose
|
|
7
|
+
-------
|
|
8
|
+
`mxm-refdata` stores Period cycles as authoritative reference-data artifacts.
|
|
9
|
+
A PeriodCycle is a definition of a cycle over delivery periods, and a
|
|
10
|
+
PeriodCycleMembership places a specific Period into a given cycle with a
|
|
11
|
+
(cycle_instance, cycle_element) assignment.
|
|
12
|
+
|
|
13
|
+
This module provides the pure mapping functions between:
|
|
14
|
+
- ORM models in `mxm.refdata.models.orm.period_cycles`
|
|
15
|
+
and
|
|
16
|
+
- domain models in `mxm.refdata.models.period_cycles`
|
|
17
|
+
|
|
18
|
+
Conventions
|
|
19
|
+
-----------
|
|
20
|
+
- Enums are stored in the database as strings.
|
|
21
|
+
- PeriodType is stored as `PeriodType.name` (e.g. "MONTH", "QUARTER").
|
|
22
|
+
- CycleInstanceKind is stored as its `.value` (e.g. "YEAR").
|
|
23
|
+
|
|
24
|
+
These conventions should be kept stable to preserve refdata portability and
|
|
25
|
+
auditability.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from mxm.refdata.models.orm.period_cycles import (
|
|
31
|
+
PeriodCycleMembershipORM,
|
|
32
|
+
PeriodCycleORM,
|
|
33
|
+
)
|
|
34
|
+
from mxm.refdata.models.period_cycles import (
|
|
35
|
+
CycleInstanceKind,
|
|
36
|
+
PeriodCycle,
|
|
37
|
+
PeriodCycleMembership,
|
|
38
|
+
)
|
|
39
|
+
from mxm.refdata.models.periods import PeriodType
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# PeriodCycle
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def period_cycle_from_orm(orm: PeriodCycleORM) -> PeriodCycle:
|
|
47
|
+
"""
|
|
48
|
+
Map ORM -> domain PeriodCycle.
|
|
49
|
+
"""
|
|
50
|
+
return PeriodCycle(
|
|
51
|
+
cycle_id=orm.cycle_id,
|
|
52
|
+
name=orm.name,
|
|
53
|
+
period_type=PeriodType[orm.period_type], # stored as PeriodType.name
|
|
54
|
+
cycle_size=int(orm.cycle_size),
|
|
55
|
+
instance_kind=CycleInstanceKind(orm.instance_kind), # stored as .value
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def period_cycle_to_orm(model: PeriodCycle) -> PeriodCycleORM:
|
|
60
|
+
"""
|
|
61
|
+
Map domain -> ORM PeriodCycleORM.
|
|
62
|
+
"""
|
|
63
|
+
return PeriodCycleORM(
|
|
64
|
+
cycle_id=model.cycle_id,
|
|
65
|
+
name=model.name,
|
|
66
|
+
period_type=model.period_type.name, # store as PeriodType.name
|
|
67
|
+
instance_kind=model.instance_kind.value, # store as .value
|
|
68
|
+
cycle_size=int(model.cycle_size),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# PeriodCycleMembership
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def period_cycle_membership_from_orm(
|
|
78
|
+
orm: PeriodCycleMembershipORM,
|
|
79
|
+
) -> PeriodCycleMembership:
|
|
80
|
+
"""
|
|
81
|
+
Map ORM -> domain PeriodCycleMembership.
|
|
82
|
+
"""
|
|
83
|
+
return PeriodCycleMembership(
|
|
84
|
+
cycle_id=orm.cycle_id,
|
|
85
|
+
period_id=orm.period_id,
|
|
86
|
+
cycle_instance=int(orm.cycle_instance),
|
|
87
|
+
cycle_element=int(orm.cycle_element),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def period_cycle_membership_to_orm(
|
|
92
|
+
model: PeriodCycleMembership,
|
|
93
|
+
) -> PeriodCycleMembershipORM:
|
|
94
|
+
"""
|
|
95
|
+
Map domain -> ORM PeriodCycleMembershipORM.
|
|
96
|
+
"""
|
|
97
|
+
return PeriodCycleMembershipORM(
|
|
98
|
+
cycle_id=model.cycle_id,
|
|
99
|
+
period_id=model.period_id,
|
|
100
|
+
cycle_instance=int(model.cycle_instance),
|
|
101
|
+
cycle_element=int(model.cycle_element),
|
|
102
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Mapping Period instances to and from PeriodORM instances."""
|
|
2
|
+
|
|
3
|
+
from mxm.refdata.models.orm.periods import PeriodORM
|
|
4
|
+
from mxm.refdata.models.periods import Period, PeriodType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def period_to_orm(period: Period) -> PeriodORM:
|
|
8
|
+
"""
|
|
9
|
+
Map an internal Period object to a PeriodORM instance.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
period (Period): Internal Period instance.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
PeriodORM: Corresponding ORM representation.
|
|
16
|
+
"""
|
|
17
|
+
return PeriodORM(
|
|
18
|
+
period_id=period.period_id,
|
|
19
|
+
period_type=period.period_type,
|
|
20
|
+
first_date=period.first_date,
|
|
21
|
+
last_date=period.last_date,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def period_from_orm(orm: PeriodORM) -> Period:
|
|
26
|
+
"""
|
|
27
|
+
Map a PeriodORM instance to an internal Period object.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
orm (PeriodORM): ORM representation of a Period.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Period: Internal representation of the Period.
|
|
34
|
+
"""
|
|
35
|
+
return Period(
|
|
36
|
+
period_id=orm.period_id,
|
|
37
|
+
period_type=PeriodType[orm.period_type.name],
|
|
38
|
+
first_date=orm.first_date,
|
|
39
|
+
last_date=orm.last_date,
|
|
40
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Internal Data Models and ORM for the reference data service."""
|
|
2
|
+
|
|
3
|
+
from .contracts.futures_contract import FuturesContract
|
|
4
|
+
from .currencies import Currency
|
|
5
|
+
from .months import Month
|
|
6
|
+
from .orm.base import Base
|
|
7
|
+
from .orm.futures_contracts import FuturesContractORM
|
|
8
|
+
from .orm.futures_products import FuturesProductORM
|
|
9
|
+
from .orm.periods import PeriodORM
|
|
10
|
+
from .periods import Period, PeriodType
|
|
11
|
+
from .products.futures_product import FuturesProduct, SettlementMethod
|
|
12
|
+
from .reference_events import ReferenceEvent
|
|
13
|
+
from .units import ProductUnit
|
|
14
|
+
from .weekdays import Weekday
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Base",
|
|
18
|
+
"Currency",
|
|
19
|
+
"FuturesContract",
|
|
20
|
+
"FuturesContractORM",
|
|
21
|
+
"FuturesProduct",
|
|
22
|
+
"FuturesProductORM",
|
|
23
|
+
"Month",
|
|
24
|
+
"Period",
|
|
25
|
+
"PeriodORM",
|
|
26
|
+
"PeriodType",
|
|
27
|
+
"ProductUnit",
|
|
28
|
+
"ReferenceEvent",
|
|
29
|
+
"SettlementMethod",
|
|
30
|
+
"Weekday",
|
|
31
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Future contract class."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from mxm.refdata.models.currencies import Currency
|
|
7
|
+
from mxm.refdata.models.units import ProductUnit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class FuturesContract:
|
|
12
|
+
"""Represents an individual futures contract."""
|
|
13
|
+
|
|
14
|
+
contract_id: str # Unique identifier for the contract
|
|
15
|
+
product_id: str # Reference to the product ID
|
|
16
|
+
period_id: str # Reference to the period ID
|
|
17
|
+
contract_size: float # The specific size of this contract
|
|
18
|
+
unit: ProductUnit # Pre-populated unit of the product
|
|
19
|
+
currency: Currency # Pre-populated currency of the product
|
|
20
|
+
trading_calendar: str # Pre-populated trading calendar of the product
|
|
21
|
+
first_day_of_interest: datetime.date # The first day of interest for the contract
|
|
22
|
+
last_trading_day: datetime.date # The last trading day for the contract
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""This module defines the Currency Enum for ISO 4217 currency codes."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Currency(Enum):
|
|
7
|
+
"""Currency Enum for ISO 4217 currency codes."""
|
|
8
|
+
|
|
9
|
+
AUD = "Australian Dollar"
|
|
10
|
+
CAD = "Canadian Dollar"
|
|
11
|
+
CHF = "Swiss Franc"
|
|
12
|
+
EUR = "Euro"
|
|
13
|
+
GBP = "Pound Sterling"
|
|
14
|
+
HKD = "Hong Kong Dollar"
|
|
15
|
+
INR = "Indian Rupee"
|
|
16
|
+
JPY = "Yen"
|
|
17
|
+
MXN = "Mexican Peso"
|
|
18
|
+
NOK = "Norwegian Krone"
|
|
19
|
+
NZD = "New Zealand Dollar"
|
|
20
|
+
SEK = "Swedish Krona"
|
|
21
|
+
SGD = "Singapore Dollar"
|
|
22
|
+
USD = "US Dollar"
|
|
23
|
+
BRL = "Brazilian Real"
|
|
24
|
+
CNY = "Yuan Renminbi"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""A data-class encapsulating calendar months and different representations."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
CME_MONTH_CODES = {
|
|
6
|
+
1: "F",
|
|
7
|
+
2: "G",
|
|
8
|
+
3: "H",
|
|
9
|
+
4: "J",
|
|
10
|
+
5: "K",
|
|
11
|
+
6: "M",
|
|
12
|
+
7: "N",
|
|
13
|
+
8: "Q",
|
|
14
|
+
9: "U",
|
|
15
|
+
10: "V",
|
|
16
|
+
11: "X",
|
|
17
|
+
12: "Z",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
MONTH_STRINGS = {
|
|
21
|
+
1: "Jan",
|
|
22
|
+
2: "Feb",
|
|
23
|
+
3: "Mar",
|
|
24
|
+
4: "Apr",
|
|
25
|
+
5: "May",
|
|
26
|
+
6: "Jun",
|
|
27
|
+
7: "Jul",
|
|
28
|
+
8: "Aug",
|
|
29
|
+
9: "Sep",
|
|
30
|
+
10: "Oct",
|
|
31
|
+
11: "Nov",
|
|
32
|
+
12: "Dec",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
MONTH_STRINGS_REVERSE = {v: k for k, v in MONTH_STRINGS.items()}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class Month:
|
|
40
|
+
"""Represents a month with various representations."""
|
|
41
|
+
|
|
42
|
+
month: int # 1 (January) to 12 (December)
|
|
43
|
+
|
|
44
|
+
def __post_init__(self):
|
|
45
|
+
if not (1 <= self.month <= 12):
|
|
46
|
+
raise ValueError(f"Invalid month: {self.month}. Must be between 1 and 12.")
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def as_int(self) -> int:
|
|
50
|
+
"""Return the month as an integer."""
|
|
51
|
+
return self.month
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def as_str(self) -> str:
|
|
55
|
+
"""Return the month as a three-letter abbreviation."""
|
|
56
|
+
return MONTH_STRINGS[self.month]
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def as_cme_code(self) -> str:
|
|
60
|
+
"""Return the month as a CME code."""
|
|
61
|
+
return CME_MONTH_CODES[self.month]
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_str(cls, month_str: str) -> "Month":
|
|
65
|
+
"""Create a Month instance from a three-letter abbreviation."""
|
|
66
|
+
month_int = MONTH_STRINGS_REVERSE.get(month_str)
|
|
67
|
+
if not month_int:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Invalid month abbreviation: {month_str}. "
|
|
70
|
+
f"Expected one of {list(MONTH_STRINGS_REVERSE.keys())}."
|
|
71
|
+
)
|
|
72
|
+
return cls(month_int)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""ORM models for DB integration."""
|
|
2
|
+
|
|
3
|
+
from .base import Base
|
|
4
|
+
from .futures_contracts import FuturesContractORM
|
|
5
|
+
from .futures_products import FuturesProductORM
|
|
6
|
+
from .period_cycles import PeriodCycleMembershipORM, PeriodCycleORM
|
|
7
|
+
from .periods import PeriodORM
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Base",
|
|
11
|
+
"FuturesContractORM",
|
|
12
|
+
"FuturesProductORM",
|
|
13
|
+
"PeriodCycleMembershipORM",
|
|
14
|
+
"PeriodCycleORM",
|
|
15
|
+
"PeriodORM",
|
|
16
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ORM model for the futures_contracts table.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import date
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from sqlalchemy import Date, Enum, Float, ForeignKey, String
|
|
11
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
12
|
+
|
|
13
|
+
from mxm.refdata.models.orm.base import Base
|
|
14
|
+
from mxm.refdata.models.products.futures_product import Currency, ProductUnit
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from mxm.refdata.models.orm.futures_products import FuturesProductORM
|
|
18
|
+
from mxm.refdata.models.orm.periods import PeriodORM
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FuturesContractORM(Base):
|
|
22
|
+
"""ORM model for the futures_contracts table."""
|
|
23
|
+
|
|
24
|
+
__tablename__ = "futures_contracts"
|
|
25
|
+
|
|
26
|
+
contract_id: Mapped[str] = mapped_column(String, primary_key=True, nullable=False)
|
|
27
|
+
|
|
28
|
+
product_id: Mapped[str] = mapped_column(
|
|
29
|
+
String,
|
|
30
|
+
ForeignKey("futures_products.product_id"),
|
|
31
|
+
nullable=False,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
period_id: Mapped[str] = mapped_column(
|
|
35
|
+
String,
|
|
36
|
+
ForeignKey("periods.period_id"),
|
|
37
|
+
nullable=False,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
contract_size: Mapped[float] = mapped_column(Float, nullable=False)
|
|
41
|
+
currency: Mapped[Currency] = mapped_column(Enum(Currency), nullable=False)
|
|
42
|
+
unit: Mapped[ProductUnit] = mapped_column(Enum(ProductUnit), nullable=False)
|
|
43
|
+
|
|
44
|
+
trading_calendar: Mapped[str] = mapped_column(String, nullable=False)
|
|
45
|
+
|
|
46
|
+
first_day_of_interest: Mapped[date] = mapped_column(Date, nullable=False)
|
|
47
|
+
last_trading_day: Mapped[date] = mapped_column(Date, nullable=False)
|
|
48
|
+
|
|
49
|
+
# Relationships
|
|
50
|
+
product: Mapped[FuturesProductORM] = relationship(
|
|
51
|
+
"FuturesProductORM", back_populates="contracts"
|
|
52
|
+
)
|
|
53
|
+
period: Mapped[PeriodORM] = relationship("PeriodORM", back_populates="contracts")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ORM model for the futures_products table.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import Enum, Float, String, Text
|
|
10
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
11
|
+
|
|
12
|
+
from mxm.refdata.models.orm.base import Base
|
|
13
|
+
from mxm.refdata.models.products.futures_product import (
|
|
14
|
+
Currency,
|
|
15
|
+
ProductUnit,
|
|
16
|
+
SettlementMethod,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from mxm.refdata.models.orm.futures_contracts import FuturesContractORM
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FuturesProductORM(Base):
|
|
24
|
+
"""ORM model for the futures_products table."""
|
|
25
|
+
|
|
26
|
+
__tablename__ = "futures_products"
|
|
27
|
+
|
|
28
|
+
product_id: Mapped[str] = mapped_column(String, primary_key=True, nullable=False)
|
|
29
|
+
venue: Mapped[str] = mapped_column(String, nullable=False)
|
|
30
|
+
description: Mapped[str] = mapped_column(Text, nullable=False)
|
|
31
|
+
|
|
32
|
+
currency: Mapped[Currency] = mapped_column(Enum(Currency), nullable=False)
|
|
33
|
+
unit: Mapped[ProductUnit] = mapped_column(Enum(ProductUnit), nullable=False)
|
|
34
|
+
contract_size: Mapped[float] = mapped_column(Float, nullable=False)
|
|
35
|
+
|
|
36
|
+
valid_period_rule: Mapped[str] = mapped_column(Text, nullable=False)
|
|
37
|
+
listing_rule: Mapped[str] = mapped_column(Text, nullable=False)
|
|
38
|
+
|
|
39
|
+
period_types: Mapped[str] = mapped_column(Text, nullable=False)
|
|
40
|
+
|
|
41
|
+
settlement: Mapped[SettlementMethod] = mapped_column(
|
|
42
|
+
Enum(SettlementMethod), nullable=False
|
|
43
|
+
)
|
|
44
|
+
last_trading_rule: Mapped[str] = mapped_column(Text, nullable=False)
|
|
45
|
+
expiry_rule: Mapped[str] = mapped_column(Text, nullable=False)
|
|
46
|
+
|
|
47
|
+
trading_calendar: Mapped[str] = mapped_column(String, nullable=False)
|
|
48
|
+
|
|
49
|
+
trading_hours: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
50
|
+
|
|
51
|
+
tick_size: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
52
|
+
tick_value: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
53
|
+
|
|
54
|
+
initial_margin: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
55
|
+
maintenance_margin: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
56
|
+
|
|
57
|
+
contracts: Mapped[list[FuturesContractORM]] = relationship(
|
|
58
|
+
"FuturesContractORM",
|
|
59
|
+
back_populates="product",
|
|
60
|
+
cascade="all, delete-orphan",
|
|
61
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
4
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
|
+
|
|
6
|
+
from mxm.refdata.models.orm.base import Base
|
|
7
|
+
from mxm.refdata.models.orm.periods import PeriodORM
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PeriodCycleORM(Base):
|
|
11
|
+
__tablename__ = "period_cycles"
|
|
12
|
+
|
|
13
|
+
cycle_id: Mapped[str] = mapped_column(primary_key=True)
|
|
14
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
|
15
|
+
period_type: Mapped[str] = mapped_column(nullable=False)
|
|
16
|
+
instance_kind: Mapped[str] = mapped_column(nullable=False)
|
|
17
|
+
cycle_size: Mapped[int] = mapped_column(nullable=False)
|
|
18
|
+
|
|
19
|
+
# Relationship: one cycle -> many memberships
|
|
20
|
+
memberships = relationship(
|
|
21
|
+
"PeriodCycleMembershipORM",
|
|
22
|
+
back_populates="cycle",
|
|
23
|
+
cascade="all, delete-orphan",
|
|
24
|
+
lazy="select",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def __repr__(self) -> str:
|
|
28
|
+
return (
|
|
29
|
+
f"PeriodCycleORM(cycle_id={self.cycle_id!r}, name={self.name!r}, "
|
|
30
|
+
f"period_type={self.period_type!r}, instance_kind={self.instance_kind!r}, "
|
|
31
|
+
f"cycle_size={self.cycle_size!r})"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PeriodCycleMembershipORM(Base):
|
|
36
|
+
"""
|
|
37
|
+
ORM table: period_cycle_memberships
|
|
38
|
+
|
|
39
|
+
Membership relation: places a specific Period into a given PeriodCycle.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
__tablename__ = "period_cycle_memberships"
|
|
43
|
+
|
|
44
|
+
# Composite primary key: one period per cycle
|
|
45
|
+
cycle_id: Mapped[str] = mapped_column(
|
|
46
|
+
ForeignKey("period_cycles.cycle_id", ondelete="CASCADE"),
|
|
47
|
+
primary_key=True,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
period_id: Mapped[str] = mapped_column(
|
|
51
|
+
ForeignKey("periods.period_id", ondelete="CASCADE"),
|
|
52
|
+
primary_key=True,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
cycle_instance: Mapped[int] = mapped_column(nullable=False)
|
|
56
|
+
cycle_element: Mapped[int] = mapped_column(nullable=False)
|
|
57
|
+
|
|
58
|
+
# Relationships
|
|
59
|
+
cycle: Mapped[PeriodCycleORM] = relationship(
|
|
60
|
+
back_populates="memberships",
|
|
61
|
+
lazy="select",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
period: Mapped[PeriodORM] = relationship(
|
|
65
|
+
lazy="select",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
__table_args__ = (
|
|
69
|
+
UniqueConstraint(
|
|
70
|
+
"cycle_id",
|
|
71
|
+
"cycle_instance",
|
|
72
|
+
"cycle_element",
|
|
73
|
+
name="uq_cycle_instance_element",
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return (
|
|
79
|
+
"PeriodCycleMembershipORM("
|
|
80
|
+
f"cycle_id={self.cycle_id!r}, "
|
|
81
|
+
f"period_id={self.period_id!r}, "
|
|
82
|
+
f"cycle_instance={self.cycle_instance!r}, "
|
|
83
|
+
f"cycle_element={self.cycle_element!r})"
|
|
84
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import Date, Enum, String
|
|
7
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
8
|
+
|
|
9
|
+
from mxm.refdata.models.orm.base import Base
|
|
10
|
+
from mxm.refdata.models.periods import PeriodType
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from mxm.refdata.models.orm.futures_contracts import FuturesContractORM
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PeriodORM(Base):
|
|
17
|
+
"""ORM model for the periods table."""
|
|
18
|
+
|
|
19
|
+
__tablename__ = "periods"
|
|
20
|
+
|
|
21
|
+
period_id: Mapped[str] = mapped_column(
|
|
22
|
+
String, primary_key=True, unique=True, nullable=False
|
|
23
|
+
)
|
|
24
|
+
period_type: Mapped[PeriodType] = mapped_column(Enum(PeriodType), nullable=False)
|
|
25
|
+
first_date: Mapped[date] = mapped_column(Date, nullable=False)
|
|
26
|
+
last_date: Mapped[date] = mapped_column(Date, nullable=False)
|
|
27
|
+
|
|
28
|
+
contracts: Mapped[list[FuturesContractORM]] = relationship(
|
|
29
|
+
"FuturesContractORM", back_populates="period"
|
|
30
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
from mxm.refdata.models.periods import PeriodType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CycleInstanceKind(str, Enum):
|
|
10
|
+
"""
|
|
11
|
+
Defines what 'cycle_instance' means for a membership row.
|
|
12
|
+
|
|
13
|
+
For calendar cycles, the instance is the calendar year.
|
|
14
|
+
Other instance kinds may be added later (e.g. gas_year).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
YEAR = "YEAR"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class PeriodCycle:
|
|
22
|
+
"""
|
|
23
|
+
Defines a cycle over periods.
|
|
24
|
+
|
|
25
|
+
A cycle is an interpretation layer over Periods, not a property of Period itself.
|
|
26
|
+
|
|
27
|
+
Example cycles:
|
|
28
|
+
- CALENDAR_MONTHS: element=1..12, instance=year, applies to PeriodType.MONTH
|
|
29
|
+
- CALENDAR_QUARTERS: element=1..4, instance=year, applies to PeriodType.QUARTER
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
cycle_id: str # stable identifier, e.g. "CALENDAR_MONTHS"
|
|
33
|
+
name: str # human readable
|
|
34
|
+
period_type: PeriodType # the period type this cycle classifies
|
|
35
|
+
cycle_size: int # number of elements in one cycle (12, 4, ...)
|
|
36
|
+
instance_kind: CycleInstanceKind = CycleInstanceKind.YEAR
|
|
37
|
+
|
|
38
|
+
def __post_init__(self) -> None:
|
|
39
|
+
if not self.cycle_id:
|
|
40
|
+
raise ValueError("cycle_id must be non-empty")
|
|
41
|
+
if not self.name:
|
|
42
|
+
raise ValueError("name must be non-empty")
|
|
43
|
+
if self.cycle_size < 1:
|
|
44
|
+
raise ValueError("cycle_size must be >= 1")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class PeriodCycleMembership:
|
|
49
|
+
"""
|
|
50
|
+
Membership relation: places a specific Period into a given cycle.
|
|
51
|
+
|
|
52
|
+
A Period may be a member of multiple cycles.
|
|
53
|
+
A cycle may contain many periods.
|
|
54
|
+
|
|
55
|
+
cycle_instance:
|
|
56
|
+
For YEAR instance_kind cycles, this is the calendar year.
|
|
57
|
+
(Other instance kinds may be introduced later.)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
cycle_id: str
|
|
61
|
+
period_id: str
|
|
62
|
+
cycle_instance: int
|
|
63
|
+
cycle_element: int # 1..cycle_size
|
|
64
|
+
|
|
65
|
+
def __post_init__(self) -> None:
|
|
66
|
+
if not self.cycle_id:
|
|
67
|
+
raise ValueError("cycle_id must be non-empty")
|
|
68
|
+
if not self.period_id:
|
|
69
|
+
raise ValueError("period_id must be non-empty")
|
|
70
|
+
if self.cycle_instance <= 0:
|
|
71
|
+
raise ValueError("cycle_instance must be > 0")
|
|
72
|
+
if self.cycle_element < 1:
|
|
73
|
+
raise ValueError("cycle_element must be >= 1")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Reference periods for futures contracts."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import date
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from functools import total_ordering
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PeriodType(Enum):
|
|
12
|
+
"""Period types for futures contracts."""
|
|
13
|
+
|
|
14
|
+
YEAR = "year"
|
|
15
|
+
QUARTER = "quarter"
|
|
16
|
+
MONTH = "month"
|
|
17
|
+
WEEK = "week"
|
|
18
|
+
DAY = "day"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
PERIOD_PRIORITY = {
|
|
22
|
+
PeriodType.YEAR: 1,
|
|
23
|
+
PeriodType.QUARTER: 2,
|
|
24
|
+
PeriodType.MONTH: 3,
|
|
25
|
+
PeriodType.WEEK: 4,
|
|
26
|
+
PeriodType.DAY: 5,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@total_ordering
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class Period:
|
|
33
|
+
"""Represents a specific calendar period."""
|
|
34
|
+
|
|
35
|
+
period_id: (
|
|
36
|
+
str # A unique identifier, like "2024", "Jan-2024", "2024-Q1", or "2024-W24"
|
|
37
|
+
)
|
|
38
|
+
period_type: PeriodType # The type of the period (year, month, quarter, week)
|
|
39
|
+
first_date: date # The first day of the period
|
|
40
|
+
last_date: date # The last day of the period
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
if self.first_date > self.last_date:
|
|
44
|
+
raise ValueError("first_date must not be after last_date")
|
|
45
|
+
|
|
46
|
+
def __str__(self):
|
|
47
|
+
return f"{self.period_type.name} Period: {self.period_id} ({self.first_date} to {self.last_date})"
|
|
48
|
+
|
|
49
|
+
def __repr__(self):
|
|
50
|
+
return f"Period(period_id='{self.period_id}', period_type={self.period_type}, first_date={self.first_date}, last_date={self.last_date})"
|
|
51
|
+
|
|
52
|
+
def __lt__(self, other: "Period") -> bool:
|
|
53
|
+
"""Define sorting: higher-level periods (Year > Month) first, then by start date."""
|
|
54
|
+
|
|
55
|
+
if PERIOD_PRIORITY[self.period_type] != PERIOD_PRIORITY[other.period_type]:
|
|
56
|
+
return (
|
|
57
|
+
PERIOD_PRIORITY[self.period_type] < PERIOD_PRIORITY[other.period_type]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return self.first_date < other.first_date
|
|
61
|
+
|
|
62
|
+
def to_daterange(self) -> pd.DatetimeIndex:
|
|
63
|
+
"""Return a pandas date_range from first_date to last_date, with daily frequency."""
|
|
64
|
+
return pd.date_range(self.first_date, self.last_date, freq="D")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Models for financial products."""
|