mainsequence 3.3.1__tar.gz → 3.3.3__tar.gz
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-3.3.1 → mainsequence-3.3.3}/PKG-INFO +1 -1
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/bond.py +232 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/indices.py +0 -2
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-3.3.1 → mainsequence-3.3.3}/pyproject.toml +1 -1
- {mainsequence-3.3.1 → mainsequence-3.3.3}/LICENSE +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/README.md +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/__main__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/cli/api.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/cli/cli.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/cli/config.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/base.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/data_sources_interfaces/timescale.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/models_report_studio.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/models_tdag.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/models_vam.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/config.toml +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/image_1_base64.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/image_2_base64.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/image_3_base64.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/image_4_base64.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/image_5_base64.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/logo.png +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/core/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/core/theme.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/scaffold.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/data_interface/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/data_interface/data_interface.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/base_instrument.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/european_option.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/interest_rate_swap.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/json_codec.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/knockout_fx_option.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/position.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/ql_fields.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/vanilla_fx_option.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments.default.toml +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/black_scholes.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/bond_pricer.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/fx_option_pricer.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/knockout_fx_pricer.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/swap_pricer.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/settings.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/logconf.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/reportbuilder/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/reportbuilder/__main__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/reportbuilder/examples/ms_template_report.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/reportbuilder/model.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/reportbuilder/slide_templates.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/__main__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/config.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/data_nodes/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/data_nodes/build_operations.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/data_nodes/utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/future_registry.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/tdag/utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/agent_interface.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/config_handling.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/news_app.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/prices/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/prices/utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/data_nodes.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/enums.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/models.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/notebook_handling.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/portfolio_interface.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/resource_factory/app_factory.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/resource_factory/base_factory.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/utils.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/setup.cfg +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/tests/test_agent.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/tests/test_portfolio.py +0 -0
- {mainsequence-3.3.1 → mainsequence-3.3.3}/tests/test_replicator.py +0 -0
|
@@ -38,6 +38,9 @@ BOND_CACHE_PER_INSTRUMENT_LIMIT = 256
|
|
|
38
38
|
_BOND_CACHE_LOCK = threading.RLock()
|
|
39
39
|
_BOND_PRICE_CACHE: dict[str, "OrderedDict[str, float]"] = {}
|
|
40
40
|
_BOND_ZSPREAD_CACHE: dict[str, "OrderedDict[str, float]"] = {}
|
|
41
|
+
_BOND_DURATION_CACHE: dict[str, "OrderedDict[str, float]"] = {} # <- NEW
|
|
42
|
+
|
|
43
|
+
|
|
41
44
|
|
|
42
45
|
def clear_global_bond_cache() -> None:
|
|
43
46
|
with _BOND_CACHE_LOCK:
|
|
@@ -724,6 +727,235 @@ class Bond(InstrumentModel):
|
|
|
724
727
|
)
|
|
725
728
|
return self._bond
|
|
726
729
|
|
|
730
|
+
def duration(
|
|
731
|
+
self,
|
|
732
|
+
with_yield: float | None = None,
|
|
733
|
+
*,
|
|
734
|
+
duration_type = ql.Duration.Modified,
|
|
735
|
+
) -> float:
|
|
736
|
+
"""
|
|
737
|
+
Return bond duration (default: Modified) and cache it using the same
|
|
738
|
+
hashed context design as price()/z_spread().
|
|
739
|
+
|
|
740
|
+
Notes
|
|
741
|
+
-----
|
|
742
|
+
- Zero-coupon bonds: for Modified duration we follow the provided guide and
|
|
743
|
+
return time-to-maturity in year fractions (0 if matured).
|
|
744
|
+
- Coupon-bearing bonds: we compute YTM from the current clean price and then
|
|
745
|
+
call QuantLib's BondFunctions.duration(..).
|
|
746
|
+
- Cache key includes the instrument hash and the pricing context (flat yield
|
|
747
|
+
or default curve + version ticks + valuation date), plus the duration type.
|
|
748
|
+
"""
|
|
749
|
+
|
|
750
|
+
if self.valuation_date is None:
|
|
751
|
+
raise ValueError("Set valuation_date before computing duration: set_valuation_date(dt).")
|
|
752
|
+
|
|
753
|
+
# ---------- build cache keys ----------
|
|
754
|
+
inst_key = self._instrument_cache_key()
|
|
755
|
+
|
|
756
|
+
# Build a context key. For zero-coupon we may not need a curve; avoid raising if none.
|
|
757
|
+
try:
|
|
758
|
+
ctx_key = self._price_context_key(with_yield)
|
|
759
|
+
except Exception:
|
|
760
|
+
# e.g. zero-coupon duration with no curve/yield doesn't need a curve context;
|
|
761
|
+
# still include valuation date to remain stable across time.
|
|
762
|
+
ctx_key = f"val:{self._val_ordinal()}"
|
|
763
|
+
|
|
764
|
+
# Tag the duration type to avoid collisions if caller requests different types.
|
|
765
|
+
if duration_type == ql.Duration.Modified:
|
|
766
|
+
dtype_str = "Modified"
|
|
767
|
+
elif duration_type == ql.Duration.Macaulay:
|
|
768
|
+
dtype_str = "Macaulay"
|
|
769
|
+
elif duration_type == ql.Duration.Simple:
|
|
770
|
+
dtype_str = "Simple"
|
|
771
|
+
elif duration_type == ql.Duration.Effective:
|
|
772
|
+
dtype_str = "Effective"
|
|
773
|
+
else:
|
|
774
|
+
dtype_str = f"Type{int(duration_type)}"
|
|
775
|
+
|
|
776
|
+
dur_key = f"dur|{ctx_key}|dtype:{dtype_str}"
|
|
777
|
+
|
|
778
|
+
# ---------- cache hit ----------
|
|
779
|
+
with _BOND_CACHE_LOCK:
|
|
780
|
+
bucket = _BOND_DURATION_CACHE.get(inst_key)
|
|
781
|
+
if bucket is not None and dur_key in bucket:
|
|
782
|
+
val = bucket[dur_key]
|
|
783
|
+
bucket.move_to_end(dur_key) # LRU promote
|
|
784
|
+
return val
|
|
785
|
+
|
|
786
|
+
# ---------- compute ----------
|
|
787
|
+
# Special-case ZeroCouponBond per your guide
|
|
788
|
+
if isinstance(self, ZeroCouponBond) and duration_type == ql.Duration.Modified:
|
|
789
|
+
# Build only the instrument (no engine needed)
|
|
790
|
+
self._ensure_instrument()
|
|
791
|
+
|
|
792
|
+
vd = self.valuation_date
|
|
793
|
+
mty = self.maturity_date
|
|
794
|
+
if mty <= vd:
|
|
795
|
+
dur_val = 0.0
|
|
796
|
+
else:
|
|
797
|
+
dcc: ql.DayCounter = self.day_count
|
|
798
|
+
dur_val = max(0.0, dcc.yearFraction(to_ql_date(vd), to_ql_date(mty)))
|
|
799
|
+
|
|
800
|
+
else:
|
|
801
|
+
# Coupon-bearing bonds (and any non-default type): use QL functions
|
|
802
|
+
# Ensure pricer so cleanPrice/settlementDate/etc. are available
|
|
803
|
+
self._setup_pricer(with_yield=with_yield)
|
|
804
|
+
qb: ql.Bond = self._bond # type: ignore[assignment]
|
|
805
|
+
dcc: ql.DayCounter = self.day_count
|
|
806
|
+
|
|
807
|
+
# Frequency: use instrument's coupon frequency if available; else NoFrequency
|
|
808
|
+
try:
|
|
809
|
+
freq: ql.Frequency = self.coupon_frequency.frequency() # Fixed & Floating have this
|
|
810
|
+
except Exception:
|
|
811
|
+
freq = ql.NoFrequency
|
|
812
|
+
|
|
813
|
+
comp = ql.Compounded
|
|
814
|
+
bp = ql.BondPrice(float(qb.cleanPrice()), ql.BondPrice.Clean)
|
|
815
|
+
settle = qb.settlementDate()
|
|
816
|
+
ytm = qb.bondYield(bp, dcc, comp, freq, settle)
|
|
817
|
+
|
|
818
|
+
try:
|
|
819
|
+
dur_val = float(
|
|
820
|
+
ql.BondFunctions.duration(qb, ytm, dcc, comp, freq, duration_type)
|
|
821
|
+
)
|
|
822
|
+
except Exception as e:
|
|
823
|
+
# Stay robust (consistent with your guide)
|
|
824
|
+
print(e)
|
|
825
|
+
dur_val = 0.0
|
|
826
|
+
|
|
827
|
+
# ---------- store in cache ----------
|
|
828
|
+
with _BOND_CACHE_LOCK:
|
|
829
|
+
bucket = _BOND_DURATION_CACHE.setdefault(inst_key, OrderedDict())
|
|
830
|
+
bucket[dur_key] = float(dur_val)
|
|
831
|
+
bucket.move_to_end(dur_key)
|
|
832
|
+
while len(bucket) > BOND_CACHE_PER_INSTRUMENT_LIMIT:
|
|
833
|
+
bucket.popitem(last=False)
|
|
834
|
+
|
|
835
|
+
return float(dur_val)
|
|
836
|
+
|
|
837
|
+
def carry_roll_down(
|
|
838
|
+
self,
|
|
839
|
+
horizon: ql.Period | int | datetime.timedelta | datetime.date,
|
|
840
|
+
*,
|
|
841
|
+
clean: bool = False,
|
|
842
|
+
) -> dict[str, float]:
|
|
843
|
+
"""
|
|
844
|
+
Compute carry + roll-down over a horizon using the already-built engine & curve.
|
|
845
|
+
- Uses self.analytics(...) for today's clean/dirty/accrued (no rebuild if engine is set).
|
|
846
|
+
- No relinks, no engine setup here. Raises if the bond wasn't priced first.
|
|
847
|
+
|
|
848
|
+
Returns per-100 (except *_ccy which are currency):
|
|
849
|
+
p0_dirty_per100, p0_clean_per100,
|
|
850
|
+
p1_dirty_per100_unchanged_curve, p1_dirty_per100_const_yield,
|
|
851
|
+
cr_dirty, carry_const_dirty, roll_down_dirty,
|
|
852
|
+
coupons_between_ccy, cr_plus_coupons_dirty,
|
|
853
|
+
and (if clean=True) clean-price counterparts and accrued at horizon.
|
|
854
|
+
"""
|
|
855
|
+
# ---- Preconditions: must already be priced and linked to a curve/yield ----
|
|
856
|
+
if self.valuation_date is None:
|
|
857
|
+
raise ValueError("Set valuation_date before carry_roll_down().")
|
|
858
|
+
if self._bond is None or self._engine is None or self._last_discount_curve_handle is None:
|
|
859
|
+
raise RuntimeError("Price the bond first (price() or analytics()) before carry_roll_down().")
|
|
860
|
+
|
|
861
|
+
qb: ql.Bond = self._bond
|
|
862
|
+
h = self._last_discount_curve_handle
|
|
863
|
+
scale = 100.0 / float(self.face_value)
|
|
864
|
+
|
|
865
|
+
# --- Today's prices via your analytics() (uses existing engine; no rebuild if with_yield unchanged) ---
|
|
866
|
+
an = self.analytics(with_yield=self._with_yield)
|
|
867
|
+
p0_clean = float(an["clean_price"])
|
|
868
|
+
p0_dirty = float(an["dirty_price"])
|
|
869
|
+
a0_ccy = float(an["accrued_amount"])
|
|
870
|
+
|
|
871
|
+
# Settlement today (uses current QL Settings already set when you priced)
|
|
872
|
+
s0: ql.Date = qb.settlementDate()
|
|
873
|
+
|
|
874
|
+
# Current YTM from today's clean (for constant-yield carry)
|
|
875
|
+
dcc: ql.DayCounter = self.day_count
|
|
876
|
+
try:
|
|
877
|
+
freq: ql.Frequency = self.coupon_frequency.frequency()
|
|
878
|
+
except Exception:
|
|
879
|
+
freq = ql.NoFrequency
|
|
880
|
+
comp = ql.Compounded
|
|
881
|
+
ytm0 = qb.bondYield(ql.BondPrice(p0_clean, ql.BondPrice.Clean), dcc, comp, freq, s0)
|
|
882
|
+
|
|
883
|
+
# ---- Horizon valuation date and settlement (no global Settings change) ----
|
|
884
|
+
asof_qld = to_ql_date(self.valuation_date)
|
|
885
|
+
if isinstance(horizon, ql.Period):
|
|
886
|
+
vd1 = self.calendar.advance(asof_qld, horizon, self.business_day_convention)
|
|
887
|
+
elif isinstance(horizon, int):
|
|
888
|
+
vd1 = self.calendar.advance(asof_qld, ql.Period(int(horizon), ql.Days), self.business_day_convention)
|
|
889
|
+
elif isinstance(horizon, datetime.timedelta):
|
|
890
|
+
vd1 = to_ql_date(self.valuation_date + horizon)
|
|
891
|
+
elif isinstance(horizon, datetime.date):
|
|
892
|
+
vd1 = to_ql_date(horizon)
|
|
893
|
+
else:
|
|
894
|
+
raise ValueError("Unsupported horizon type. Use ql.Period | int(days) | timedelta | date.")
|
|
895
|
+
|
|
896
|
+
s1 = self.calendar.advance(vd1, ql.Period(self.settlement_days, ql.Days), ql.Following)
|
|
897
|
+
if s1 <= s0:
|
|
898
|
+
raise ValueError("Horizon/settlement must be after today's settlement date.")
|
|
899
|
+
|
|
900
|
+
# ---- Unchanged-curve forward dirty at horizon: sum DF(t0,T)*CF / DF(t0,S1) ----
|
|
901
|
+
def _df(d: ql.Date) -> float:
|
|
902
|
+
return float(h.discount(d))
|
|
903
|
+
|
|
904
|
+
pv_after_s1 = 0.0
|
|
905
|
+
coupons_between_ccy = 0.0
|
|
906
|
+
for cf in qb.cashflows():
|
|
907
|
+
d = cf.date()
|
|
908
|
+
amt = float(cf.amount())
|
|
909
|
+
if d > s1:
|
|
910
|
+
pv_after_s1 += amt * _df(d)
|
|
911
|
+
elif d > s0: # cashflows received in (s0, s1]
|
|
912
|
+
coupons_between_ccy += amt
|
|
913
|
+
|
|
914
|
+
df_s1 = _df(s1)
|
|
915
|
+
p1_dirty_curve = 0.0 if df_s1 == 0.0 else (pv_after_s1 / df_s1) * scale
|
|
916
|
+
|
|
917
|
+
# ---- Constant-yield dirty at horizon (no engine; BondFunctions) ----
|
|
918
|
+
try:
|
|
919
|
+
p1_clean_const = float(ql.BondFunctions.cleanPrice(qb, ytm0, dcc, comp, freq, s1))
|
|
920
|
+
except Exception:
|
|
921
|
+
p1_clean_const = 0.0
|
|
922
|
+
try:
|
|
923
|
+
a1_ccy = float(ql.BondFunctions.accruedAmount(qb, s1))
|
|
924
|
+
except Exception:
|
|
925
|
+
a1_ccy = 0.0
|
|
926
|
+
p1_dirty_const = p1_clean_const + a1_ccy * scale
|
|
927
|
+
|
|
928
|
+
# ---- Returns (per 100) ----
|
|
929
|
+
cr_dirty = p1_dirty_curve - p0_dirty # carry + roll (ex-coupon)
|
|
930
|
+
carry_const_dirty = p1_dirty_const - p0_dirty # constant-yield carry (ex-coupon)
|
|
931
|
+
roll_down_dirty = p1_dirty_curve - p1_dirty_const # roll-down
|
|
932
|
+
|
|
933
|
+
out: dict[str, float] = {
|
|
934
|
+
"p0_dirty_per100": p0_dirty,
|
|
935
|
+
"p0_clean_per100": p0_clean,
|
|
936
|
+
"p1_dirty_per100_unchanged_curve": p1_dirty_curve,
|
|
937
|
+
"p1_dirty_per100_const_yield": p1_dirty_const,
|
|
938
|
+
"cr_dirty": cr_dirty,
|
|
939
|
+
"carry_const_dirty": carry_const_dirty,
|
|
940
|
+
"roll_down_dirty": roll_down_dirty,
|
|
941
|
+
"coupons_between_ccy": coupons_between_ccy,
|
|
942
|
+
"cr_plus_coupons_dirty": cr_dirty + coupons_between_ccy * scale,
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if clean:
|
|
946
|
+
p1_clean_curve = p1_dirty_curve - a1_ccy * scale
|
|
947
|
+
out.update({
|
|
948
|
+
"accrued0_ccy": a0_ccy,
|
|
949
|
+
"accrued1_ccy": a1_ccy,
|
|
950
|
+
"accrued1_per100": a1_ccy * scale,
|
|
951
|
+
"p1_clean_per100_unchanged_curve": p1_clean_curve,
|
|
952
|
+
"p1_clean_per100_const_yield": p1_clean_const,
|
|
953
|
+
"cr_clean": p1_clean_curve - p0_clean,
|
|
954
|
+
"carry_const_clean": p1_clean_const - p0_clean,
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
return out
|
|
958
|
+
|
|
727
959
|
|
|
728
960
|
class FixedRateBond(Bond):
|
|
729
961
|
"""Plain-vanilla fixed-rate bond following the shared Bond lifecycle."""
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/indices.py
RENAMED
|
@@ -122,9 +122,7 @@ _INDEX_TEMPLATES: dict[str, Callable[[], dict[str, Any]]] = {
|
|
|
122
122
|
"REFERENCE_RATE__CETE_28": _cete(28),
|
|
123
123
|
"REFERENCE_RATE__CETE_91": _cete(91),
|
|
124
124
|
"REFERENCE_RATE__CETE_182": _cete(182),
|
|
125
|
-
"REFERENCE_RATE__CETE_1": _cete(1),
|
|
126
125
|
|
|
127
|
-
"REFERENCE_RATE__TIIE_OVERNIGHT_BONDES":_mx_gov_overnight(1),
|
|
128
126
|
|
|
129
127
|
"REFERENCE_RATE__USD_SOFR": lambda: dict(
|
|
130
128
|
curve_uid=_const("ZERO_CURVE__UST_CMT_ZERO_CURVE_UID"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/data_sources_interfaces/__init__.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/data_sources_interfaces/duckdb.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/client/data_sources_interfaces/timescale.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/config.toml
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/assets/favicon.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/dashboards/streamlit/pages/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/data_interface/__init__.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/data_interface/data_interface.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/base_instrument.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/european_option.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/interest_rate_swap.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/json_codec.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/knockout_fx_option.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/instruments/vanilla_fx_option.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/__init__.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/black_scholes.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/bond_pricer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/instruments/pricing_models/swap_pricer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/reportbuilder/examples/ms_template_report.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/agent_interface.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/config_handling.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/__init__.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/apps/news_app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/prices/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/contrib/prices/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/notebook_handling.py
RENAMED
|
File without changes
|
{mainsequence-3.3.1 → mainsequence-3.3.3}/mainsequence/virtualfundbuilder/portfolio_interface.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|