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.
Files changed (110) hide show
  1. mainsequence/__init__.py +0 -0
  2. mainsequence/__main__.py +9 -0
  3. mainsequence/cli/__init__.py +1 -0
  4. mainsequence/cli/api.py +157 -0
  5. mainsequence/cli/cli.py +442 -0
  6. mainsequence/cli/config.py +78 -0
  7. mainsequence/cli/ssh_utils.py +126 -0
  8. mainsequence/client/__init__.py +17 -0
  9. mainsequence/client/base.py +431 -0
  10. mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  11. mainsequence/client/data_sources_interfaces/duckdb.py +1468 -0
  12. mainsequence/client/data_sources_interfaces/timescale.py +479 -0
  13. mainsequence/client/models_helpers.py +113 -0
  14. mainsequence/client/models_report_studio.py +412 -0
  15. mainsequence/client/models_tdag.py +2276 -0
  16. mainsequence/client/models_vam.py +1983 -0
  17. mainsequence/client/utils.py +387 -0
  18. mainsequence/dashboards/__init__.py +0 -0
  19. mainsequence/dashboards/streamlit/__init__.py +0 -0
  20. mainsequence/dashboards/streamlit/assets/config.toml +12 -0
  21. mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
  22. mainsequence/dashboards/streamlit/assets/logo.png +0 -0
  23. mainsequence/dashboards/streamlit/core/__init__.py +0 -0
  24. mainsequence/dashboards/streamlit/core/theme.py +212 -0
  25. mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
  26. mainsequence/dashboards/streamlit/scaffold.py +220 -0
  27. mainsequence/instrumentation/__init__.py +7 -0
  28. mainsequence/instrumentation/utils.py +101 -0
  29. mainsequence/instruments/__init__.py +1 -0
  30. mainsequence/instruments/data_interface/__init__.py +10 -0
  31. mainsequence/instruments/data_interface/data_interface.py +361 -0
  32. mainsequence/instruments/instruments/__init__.py +3 -0
  33. mainsequence/instruments/instruments/base_instrument.py +85 -0
  34. mainsequence/instruments/instruments/bond.py +447 -0
  35. mainsequence/instruments/instruments/european_option.py +74 -0
  36. mainsequence/instruments/instruments/interest_rate_swap.py +217 -0
  37. mainsequence/instruments/instruments/json_codec.py +585 -0
  38. mainsequence/instruments/instruments/knockout_fx_option.py +146 -0
  39. mainsequence/instruments/instruments/position.py +475 -0
  40. mainsequence/instruments/instruments/ql_fields.py +239 -0
  41. mainsequence/instruments/instruments/vanilla_fx_option.py +107 -0
  42. mainsequence/instruments/pricing_models/__init__.py +0 -0
  43. mainsequence/instruments/pricing_models/black_scholes.py +49 -0
  44. mainsequence/instruments/pricing_models/bond_pricer.py +182 -0
  45. mainsequence/instruments/pricing_models/fx_option_pricer.py +90 -0
  46. mainsequence/instruments/pricing_models/indices.py +350 -0
  47. mainsequence/instruments/pricing_models/knockout_fx_pricer.py +209 -0
  48. mainsequence/instruments/pricing_models/swap_pricer.py +502 -0
  49. mainsequence/instruments/settings.py +175 -0
  50. mainsequence/instruments/utils.py +29 -0
  51. mainsequence/logconf.py +284 -0
  52. mainsequence/reportbuilder/__init__.py +0 -0
  53. mainsequence/reportbuilder/__main__.py +0 -0
  54. mainsequence/reportbuilder/examples/ms_template_report.py +706 -0
  55. mainsequence/reportbuilder/model.py +713 -0
  56. mainsequence/reportbuilder/slide_templates.py +532 -0
  57. mainsequence/tdag/__init__.py +8 -0
  58. mainsequence/tdag/__main__.py +0 -0
  59. mainsequence/tdag/config.py +129 -0
  60. mainsequence/tdag/data_nodes/__init__.py +12 -0
  61. mainsequence/tdag/data_nodes/build_operations.py +751 -0
  62. mainsequence/tdag/data_nodes/data_nodes.py +1292 -0
  63. mainsequence/tdag/data_nodes/persist_managers.py +812 -0
  64. mainsequence/tdag/data_nodes/run_operations.py +543 -0
  65. mainsequence/tdag/data_nodes/utils.py +24 -0
  66. mainsequence/tdag/future_registry.py +25 -0
  67. mainsequence/tdag/utils.py +40 -0
  68. mainsequence/virtualfundbuilder/__init__.py +45 -0
  69. mainsequence/virtualfundbuilder/__main__.py +235 -0
  70. mainsequence/virtualfundbuilder/agent_interface.py +77 -0
  71. mainsequence/virtualfundbuilder/config_handling.py +86 -0
  72. mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
  73. mainsequence/virtualfundbuilder/contrib/apps/__init__.py +8 -0
  74. mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +164 -0
  75. mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +292 -0
  76. mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +107 -0
  77. mainsequence/virtualfundbuilder/contrib/apps/news_app.py +437 -0
  78. mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +91 -0
  79. mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +95 -0
  80. mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +45 -0
  81. mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +40 -0
  82. mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +147 -0
  83. mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +77 -0
  84. mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +5 -0
  85. mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +61 -0
  86. mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +149 -0
  87. mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +310 -0
  88. mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +78 -0
  89. mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +269 -0
  90. mainsequence/virtualfundbuilder/contrib/prices/__init__.py +1 -0
  91. mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +810 -0
  92. mainsequence/virtualfundbuilder/contrib/prices/utils.py +11 -0
  93. mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +1 -0
  94. mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +313 -0
  95. mainsequence/virtualfundbuilder/data_nodes.py +637 -0
  96. mainsequence/virtualfundbuilder/enums.py +23 -0
  97. mainsequence/virtualfundbuilder/models.py +282 -0
  98. mainsequence/virtualfundbuilder/notebook_handling.py +42 -0
  99. mainsequence/virtualfundbuilder/portfolio_interface.py +272 -0
  100. mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
  101. mainsequence/virtualfundbuilder/resource_factory/app_factory.py +170 -0
  102. mainsequence/virtualfundbuilder/resource_factory/base_factory.py +238 -0
  103. mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +101 -0
  104. mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +183 -0
  105. mainsequence/virtualfundbuilder/utils.py +381 -0
  106. mainsequence-2.0.0.dist-info/METADATA +105 -0
  107. mainsequence-2.0.0.dist-info/RECORD +110 -0
  108. mainsequence-2.0.0.dist-info/WHEEL +5 -0
  109. mainsequence-2.0.0.dist-info/licenses/LICENSE +40 -0
  110. 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
+