mainsequence 2.0.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.
- mainsequence/__init__.py +0 -0
- mainsequence/__main__.py +9 -0
- mainsequence/cli/__init__.py +1 -0
- mainsequence/cli/api.py +157 -0
- mainsequence/cli/cli.py +442 -0
- mainsequence/cli/config.py +78 -0
- mainsequence/cli/ssh_utils.py +126 -0
- mainsequence/client/__init__.py +17 -0
- mainsequence/client/base.py +431 -0
- mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- mainsequence/client/data_sources_interfaces/duckdb.py +1468 -0
- mainsequence/client/data_sources_interfaces/timescale.py +479 -0
- mainsequence/client/models_helpers.py +113 -0
- mainsequence/client/models_report_studio.py +412 -0
- mainsequence/client/models_tdag.py +2276 -0
- mainsequence/client/models_vam.py +1983 -0
- mainsequence/client/utils.py +387 -0
- mainsequence/dashboards/__init__.py +0 -0
- mainsequence/dashboards/streamlit/__init__.py +0 -0
- mainsequence/dashboards/streamlit/assets/config.toml +12 -0
- mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
- mainsequence/dashboards/streamlit/assets/logo.png +0 -0
- mainsequence/dashboards/streamlit/core/__init__.py +0 -0
- mainsequence/dashboards/streamlit/core/theme.py +212 -0
- mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
- mainsequence/dashboards/streamlit/scaffold.py +220 -0
- mainsequence/instrumentation/__init__.py +7 -0
- mainsequence/instrumentation/utils.py +101 -0
- mainsequence/instruments/__init__.py +1 -0
- mainsequence/instruments/data_interface/__init__.py +10 -0
- mainsequence/instruments/data_interface/data_interface.py +361 -0
- mainsequence/instruments/instruments/__init__.py +3 -0
- mainsequence/instruments/instruments/base_instrument.py +85 -0
- mainsequence/instruments/instruments/bond.py +447 -0
- mainsequence/instruments/instruments/european_option.py +74 -0
- mainsequence/instruments/instruments/interest_rate_swap.py +217 -0
- mainsequence/instruments/instruments/json_codec.py +585 -0
- mainsequence/instruments/instruments/knockout_fx_option.py +146 -0
- mainsequence/instruments/instruments/position.py +475 -0
- mainsequence/instruments/instruments/ql_fields.py +239 -0
- mainsequence/instruments/instruments/vanilla_fx_option.py +107 -0
- mainsequence/instruments/pricing_models/__init__.py +0 -0
- mainsequence/instruments/pricing_models/black_scholes.py +49 -0
- mainsequence/instruments/pricing_models/bond_pricer.py +182 -0
- mainsequence/instruments/pricing_models/fx_option_pricer.py +90 -0
- mainsequence/instruments/pricing_models/indices.py +350 -0
- mainsequence/instruments/pricing_models/knockout_fx_pricer.py +209 -0
- mainsequence/instruments/pricing_models/swap_pricer.py +502 -0
- mainsequence/instruments/settings.py +175 -0
- mainsequence/instruments/utils.py +29 -0
- mainsequence/logconf.py +284 -0
- mainsequence/reportbuilder/__init__.py +0 -0
- mainsequence/reportbuilder/__main__.py +0 -0
- mainsequence/reportbuilder/examples/ms_template_report.py +706 -0
- mainsequence/reportbuilder/model.py +713 -0
- mainsequence/reportbuilder/slide_templates.py +532 -0
- mainsequence/tdag/__init__.py +8 -0
- mainsequence/tdag/__main__.py +0 -0
- mainsequence/tdag/config.py +129 -0
- mainsequence/tdag/data_nodes/__init__.py +12 -0
- mainsequence/tdag/data_nodes/build_operations.py +751 -0
- mainsequence/tdag/data_nodes/data_nodes.py +1292 -0
- mainsequence/tdag/data_nodes/persist_managers.py +812 -0
- mainsequence/tdag/data_nodes/run_operations.py +543 -0
- mainsequence/tdag/data_nodes/utils.py +24 -0
- mainsequence/tdag/future_registry.py +25 -0
- mainsequence/tdag/utils.py +40 -0
- mainsequence/virtualfundbuilder/__init__.py +45 -0
- mainsequence/virtualfundbuilder/__main__.py +235 -0
- mainsequence/virtualfundbuilder/agent_interface.py +77 -0
- mainsequence/virtualfundbuilder/config_handling.py +86 -0
- mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
- mainsequence/virtualfundbuilder/contrib/apps/__init__.py +8 -0
- mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +164 -0
- mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +292 -0
- mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +107 -0
- mainsequence/virtualfundbuilder/contrib/apps/news_app.py +437 -0
- mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +91 -0
- mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +95 -0
- mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +45 -0
- mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +40 -0
- mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +147 -0
- mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +77 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +5 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +61 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +149 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +310 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +78 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +269 -0
- mainsequence/virtualfundbuilder/contrib/prices/__init__.py +1 -0
- mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +810 -0
- mainsequence/virtualfundbuilder/contrib/prices/utils.py +11 -0
- mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +1 -0
- mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +313 -0
- mainsequence/virtualfundbuilder/data_nodes.py +637 -0
- mainsequence/virtualfundbuilder/enums.py +23 -0
- mainsequence/virtualfundbuilder/models.py +282 -0
- mainsequence/virtualfundbuilder/notebook_handling.py +42 -0
- mainsequence/virtualfundbuilder/portfolio_interface.py +272 -0
- mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
- mainsequence/virtualfundbuilder/resource_factory/app_factory.py +170 -0
- mainsequence/virtualfundbuilder/resource_factory/base_factory.py +238 -0
- mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +101 -0
- mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +183 -0
- mainsequence/virtualfundbuilder/utils.py +381 -0
- mainsequence-2.0.0.dist-info/METADATA +105 -0
- mainsequence-2.0.0.dist-info/RECORD +110 -0
- mainsequence-2.0.0.dist-info/WHEEL +5 -0
- mainsequence-2.0.0.dist-info/licenses/LICENSE +40 -0
- mainsequence-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,217 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime
|
4
|
+
from typing import Optional, List, Dict, Any
|
5
|
+
|
6
|
+
import QuantLib as ql
|
7
|
+
from pydantic import Field, PrivateAttr, field_serializer, field_validator
|
8
|
+
|
9
|
+
import pandas as pd
|
10
|
+
|
11
|
+
from mainsequence.instruments.pricing_models.swap_pricer import get_swap_cashflows, price_vanilla_swap_with_curve
|
12
|
+
from mainsequence.instruments.pricing_models.indices import get_index
|
13
|
+
from mainsequence.instruments.utils import to_ql_date, to_py_date
|
14
|
+
from .base_instrument import InstrumentModel
|
15
|
+
|
16
|
+
from .ql_fields import (
|
17
|
+
QuantLibPeriod as QPeriod,
|
18
|
+
QuantLibDayCounter as QDayCounter,
|
19
|
+
QuantLibBDC as QBDC,
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
class InterestRateSwap(InstrumentModel):
|
24
|
+
"""Plain-vanilla fixed-for-floating interest rate swap.
|
25
|
+
|
26
|
+
Indices are referenced by NAME (string) to keep the model stateless/JSON-friendly.
|
27
|
+
The actual QuantLib index is materialized lazily after set_valuation_date().
|
28
|
+
"""
|
29
|
+
|
30
|
+
# ---- core economics ----
|
31
|
+
notional: float = Field(...)
|
32
|
+
start_date: datetime.date = Field(...)
|
33
|
+
maturity_date: datetime.date = Field(...)
|
34
|
+
fixed_rate: float = Field(...)
|
35
|
+
|
36
|
+
# ---- fixed leg ----
|
37
|
+
fixed_leg_tenor: QPeriod = Field(...)
|
38
|
+
fixed_leg_convention: QBDC = Field(...)
|
39
|
+
fixed_leg_daycount: QDayCounter = Field(...)
|
40
|
+
|
41
|
+
# ---- floating leg ----
|
42
|
+
float_leg_tenor: QPeriod = Field(...)
|
43
|
+
float_leg_spread: float = Field(...)
|
44
|
+
float_leg_index_name: str = Field(
|
45
|
+
|
46
|
+
)
|
47
|
+
|
48
|
+
tenor: Optional[ql.Period] = Field(
|
49
|
+
default=None,
|
50
|
+
description="If set (e.g. ql.Period('156W')), maturity is start + tenor using spot start (T+1)."
|
51
|
+
)
|
52
|
+
|
53
|
+
model_config = {"arbitrary_types_allowed": True}
|
54
|
+
|
55
|
+
# runtime-only
|
56
|
+
_swap: Optional[ql.VanillaSwap] = PrivateAttr(default=None)
|
57
|
+
_float_leg_index: Optional[ql.IborIndex] = PrivateAttr(default=None)
|
58
|
+
|
59
|
+
|
60
|
+
# ---------- convenience access to runtime index (NOT serialized) ----------
|
61
|
+
@property
|
62
|
+
def float_leg_ibor_index(self) -> Optional[ql.IborIndex]:
|
63
|
+
return self._float_leg_index
|
64
|
+
|
65
|
+
# ---------- lifecycle ----------
|
66
|
+
def _ensure_index(self) -> None:
|
67
|
+
if self._float_leg_index is not None:
|
68
|
+
return
|
69
|
+
if self.valuation_date is None:
|
70
|
+
raise ValueError("Set valuation_date before pricing: set_valuation_date(dt).")
|
71
|
+
# Date-aware registry (TIIE picks Valmer curve by default)
|
72
|
+
self._float_leg_index = get_index(
|
73
|
+
self.float_leg_index_name,
|
74
|
+
target_date=self.valuation_date,
|
75
|
+
hydrate_fixings=True,
|
76
|
+
)
|
77
|
+
|
78
|
+
def _reset_runtime(self) -> None:
|
79
|
+
self._swap = None
|
80
|
+
|
81
|
+
def _on_valuation_date_set(self) -> None:
|
82
|
+
self._float_leg_index = None
|
83
|
+
self._reset_runtime()
|
84
|
+
|
85
|
+
# Optional: allow injecting a custom curve for the float leg
|
86
|
+
def reset_curve(self, curve: ql.YieldTermStructure) -> None:
|
87
|
+
if self.valuation_date is None:
|
88
|
+
raise ValueError("Set valuation_date before reset_curve().")
|
89
|
+
self._float_leg_index = get_index(
|
90
|
+
self.float_leg_index_name,
|
91
|
+
target_date=self.valuation_date,
|
92
|
+
forwarding_curve=ql.YieldTermStructureHandle(curve),
|
93
|
+
hydrate_fixings=True,
|
94
|
+
)
|
95
|
+
self._swap = None
|
96
|
+
|
97
|
+
# ---------- pricing ----------
|
98
|
+
def _setup_pricer(self) -> None:
|
99
|
+
"""Sets up the initial pricer using the default Valmer curve."""
|
100
|
+
if self._swap is not None:
|
101
|
+
return
|
102
|
+
assert self.valuation_date is not None
|
103
|
+
# Build the default curve.
|
104
|
+
self._ensure_index()
|
105
|
+
default_curve = build_zero_curve(target_date=self.valuation_date,
|
106
|
+
index_identifier=self.float_leg_ibor_index.familyName(),
|
107
|
+
)
|
108
|
+
|
109
|
+
# Call the common swap construction logic.
|
110
|
+
self._build_swap(default_curve)
|
111
|
+
|
112
|
+
def price(self) -> float:
|
113
|
+
self._setup_pricer()
|
114
|
+
return float(self._swap.NPV())
|
115
|
+
|
116
|
+
def get_cashflows(self) -> Dict[str, List[Dict[str, Any]]]:
|
117
|
+
self._setup_pricer()
|
118
|
+
return get_swap_cashflows(self._swap)
|
119
|
+
|
120
|
+
def get_net_cashflows(self) -> pd.Series:
|
121
|
+
cashflows = self.get_cashflows()
|
122
|
+
fixed_df = pd.DataFrame(cashflows["fixed"]).set_index("payment_date")
|
123
|
+
float_df = pd.DataFrame(cashflows["floating"]).set_index("payment_date")
|
124
|
+
joint = fixed_df.index.union(float_df.index)
|
125
|
+
fixed_df = fixed_df.reindex(joint).fillna(0.0)
|
126
|
+
float_df = float_df.reindex(joint).fillna(0.0)
|
127
|
+
net = fixed_df["amount"] - float_df["amount"]
|
128
|
+
net.name = "net_cashflow"
|
129
|
+
return net
|
130
|
+
|
131
|
+
def _build_swap(self, curve: ql.YieldTermStructure) -> None:
|
132
|
+
"""
|
133
|
+
Private helper method to construct the QuantLib swap object.
|
134
|
+
This contains the common logic previously duplicated in _setup_pricer and reset_curve.
|
135
|
+
"""
|
136
|
+
ql_val = to_ql_date(self.valuation_date)
|
137
|
+
ql.Settings.instance().evaluationDate = ql_val
|
138
|
+
ql.Settings.instance().includeReferenceDateEvents = False
|
139
|
+
ql.Settings.instance().enforceTodaysHistoricFixings = True
|
140
|
+
|
141
|
+
|
142
|
+
cal = self.float_leg_ibor_index.fixingCalendar()
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
# 3) Effective end
|
147
|
+
if self.tenor is not None:
|
148
|
+
eff_end = cal.advance(self.start_date, self.tenor)
|
149
|
+
else:
|
150
|
+
eff_end = to_ql_date(self.maturity_date)
|
151
|
+
|
152
|
+
# 4) Price vanilla IRS using the schedule and the provided curve
|
153
|
+
self._swap = price_vanilla_swap_with_curve(
|
154
|
+
calculation_date=ql_val,
|
155
|
+
notional=self.notional,
|
156
|
+
start_date=to_ql_date(self.start_date),
|
157
|
+
maturity_date=eff_end,
|
158
|
+
fixed_rate=self.fixed_rate,
|
159
|
+
fixed_leg_tenor=self.fixed_leg_tenor,
|
160
|
+
fixed_leg_convention=self.fixed_leg_convention,
|
161
|
+
fixed_leg_daycount=self.fixed_leg_daycount,
|
162
|
+
float_leg_tenor=self.float_leg_tenor,
|
163
|
+
float_leg_spread=self.float_leg_spread,
|
164
|
+
ibor_index=self.float_leg_ibor_index,
|
165
|
+
curve=curve,
|
166
|
+
)
|
167
|
+
|
168
|
+
# ---------- FACTORY: TIIE(28D) swap --------------------------------------
|
169
|
+
@classmethod
|
170
|
+
def from_tiie(
|
171
|
+
cls,
|
172
|
+
*,
|
173
|
+
notional: float,
|
174
|
+
start_date:datetime.date,
|
175
|
+
fixed_rate: float,
|
176
|
+
float_leg_spread: float = 0.0,
|
177
|
+
# choose exactly one of (tenor, maturity_date)
|
178
|
+
tenor: Optional[ql.Period] = None,
|
179
|
+
maturity_date: Optional[datetime.date] = None,
|
180
|
+
) -> InterestRateSwap:
|
181
|
+
"""
|
182
|
+
Build a MXN TIIE(28D) IRS with standard conventions:
|
183
|
+
|
184
|
+
- Fixed leg: tenor=28D, daycount=ACT/360, BDC=ModifiedFollowing
|
185
|
+
- Float leg: tenor=28D, index name='TIIE_28D'
|
186
|
+
- Effective start: T+1 adjusted on Mexico calendar from trade_date
|
187
|
+
- Maturity: eff_start + tenor (if tenor provided) OR explicit maturity_date
|
188
|
+
"""
|
189
|
+
if (tenor is None) == (maturity_date is None):
|
190
|
+
raise ValueError("Provide exactly one of 'tenor' or 'maturity_date'.")
|
191
|
+
|
192
|
+
cal = ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()
|
193
|
+
eff_start_ql = cal.adjust(to_ql_date(start_date), ql.Following)
|
194
|
+
eff_start = to_py_date(eff_start_ql)
|
195
|
+
|
196
|
+
if tenor is not None:
|
197
|
+
eff_end_ql = cal.advance(eff_start_ql, tenor)
|
198
|
+
maturity = to_py_date(eff_end_ql)
|
199
|
+
else:
|
200
|
+
maturity = maturity_date # type: ignore[assignment]
|
201
|
+
|
202
|
+
return cls(
|
203
|
+
notional=notional,
|
204
|
+
start_date=eff_start,
|
205
|
+
maturity_date=maturity,
|
206
|
+
fixed_rate=fixed_rate,
|
207
|
+
fixed_leg_tenor=ql.Period("28D"),
|
208
|
+
fixed_leg_convention=ql.ModifiedFollowing,
|
209
|
+
fixed_leg_daycount=ql.Actual360(),
|
210
|
+
float_leg_tenor=ql.Period("28D"),
|
211
|
+
float_leg_spread=float_leg_spread,
|
212
|
+
float_leg_index_name="TIIE_28D",
|
213
|
+
)
|
214
|
+
|
215
|
+
|
216
|
+
|
217
|
+
|