mainsequence 3.0.1__tar.gz → 3.0.2__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.
Files changed (127) hide show
  1. {mainsequence-3.0.1 → mainsequence-3.0.2}/PKG-INFO +1 -1
  2. mainsequence-3.0.2/mainsequence/client/exceptions.py +11 -0
  3. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/models_tdag.py +8 -1
  4. mainsequence-3.0.2/mainsequence/dashboards/streamlit/core/theme.py +231 -0
  5. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/scaffold.py +3 -0
  6. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/data_interface/data_interface.py +29 -8
  7. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/bond.py +8 -0
  8. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/pricing_models/indices.py +5 -8
  9. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence.egg-info/PKG-INFO +1 -1
  10. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence.egg-info/SOURCES.txt +1 -0
  11. {mainsequence-3.0.1 → mainsequence-3.0.2}/pyproject.toml +1 -1
  12. mainsequence-3.0.1/mainsequence/dashboards/streamlit/core/theme.py +0 -212
  13. {mainsequence-3.0.1 → mainsequence-3.0.2}/LICENSE +0 -0
  14. {mainsequence-3.0.1 → mainsequence-3.0.2}/README.md +0 -0
  15. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/__init__.py +0 -0
  16. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/__main__.py +0 -0
  17. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/cli/__init__.py +0 -0
  18. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/cli/api.py +0 -0
  19. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/cli/cli.py +0 -0
  20. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/cli/config.py +0 -0
  21. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/cli/ssh_utils.py +0 -0
  22. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/__init__.py +0 -0
  23. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/base.py +0 -0
  24. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  25. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  26. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/data_sources_interfaces/timescale.py +0 -0
  27. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/models_helpers.py +0 -0
  28. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/models_report_studio.py +0 -0
  29. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/models_vam.py +0 -0
  30. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/client/utils.py +0 -0
  31. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/__init__.py +0 -0
  32. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/__init__.py +0 -0
  33. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/config.toml +0 -0
  34. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
  35. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/image_1_base64.txt +0 -0
  36. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/image_2_base64.txt +0 -0
  37. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/image_3_base64.txt +0 -0
  38. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/image_4_base64.txt +0 -0
  39. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/image_5_base64.txt +0 -0
  40. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/assets/logo.png +0 -0
  41. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/core/__init__.py +0 -0
  42. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
  43. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instrumentation/__init__.py +0 -0
  44. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instrumentation/utils.py +0 -0
  45. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/__init__.py +0 -0
  46. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/data_interface/__init__.py +0 -0
  47. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/__init__.py +0 -0
  48. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/base_instrument.py +0 -0
  49. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/european_option.py +0 -0
  50. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/interest_rate_swap.py +0 -0
  51. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/json_codec.py +0 -0
  52. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/knockout_fx_option.py +0 -0
  53. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/position.py +0 -0
  54. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/ql_fields.py +0 -0
  55. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments/vanilla_fx_option.py +0 -0
  56. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/instruments.default.toml +0 -0
  57. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/pricing_models/__init__.py +0 -0
  58. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/pricing_models/black_scholes.py +0 -0
  59. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/pricing_models/bond_pricer.py +0 -0
  60. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/pricing_models/fx_option_pricer.py +0 -0
  61. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/pricing_models/knockout_fx_pricer.py +0 -0
  62. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/pricing_models/swap_pricer.py +0 -0
  63. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/settings.py +0 -0
  64. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/instruments/utils.py +0 -0
  65. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/logconf.py +0 -0
  66. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/reportbuilder/__init__.py +0 -0
  67. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/reportbuilder/__main__.py +0 -0
  68. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/reportbuilder/examples/ms_template_report.py +0 -0
  69. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/reportbuilder/model.py +0 -0
  70. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/reportbuilder/slide_templates.py +0 -0
  71. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/__init__.py +0 -0
  72. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/__main__.py +0 -0
  73. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/config.py +0 -0
  74. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/data_nodes/__init__.py +0 -0
  75. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/data_nodes/build_operations.py +0 -0
  76. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
  77. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
  78. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
  79. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/data_nodes/utils.py +0 -0
  80. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/future_registry.py +0 -0
  81. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/tdag/utils.py +0 -0
  82. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/__init__.py +0 -0
  83. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/__main__.py +0 -0
  84. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/agent_interface.py +0 -0
  85. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/config_handling.py +0 -0
  86. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
  87. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/__init__.py +0 -0
  88. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +0 -0
  89. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +0 -0
  90. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +0 -0
  91. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/news_app.py +0 -0
  92. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +0 -0
  93. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +0 -0
  94. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +0 -0
  95. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +0 -0
  96. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +0 -0
  97. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +0 -0
  98. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +0 -0
  99. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +0 -0
  100. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +0 -0
  101. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +0 -0
  102. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +0 -0
  103. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +0 -0
  104. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/prices/__init__.py +0 -0
  105. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +0 -0
  106. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/prices/utils.py +0 -0
  107. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +0 -0
  108. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +0 -0
  109. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/data_nodes.py +0 -0
  110. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/enums.py +0 -0
  111. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/models.py +0 -0
  112. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/notebook_handling.py +0 -0
  113. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/portfolio_interface.py +0 -0
  114. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
  115. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/resource_factory/app_factory.py +0 -0
  116. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/resource_factory/base_factory.py +0 -0
  117. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +0 -0
  118. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +0 -0
  119. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence/virtualfundbuilder/utils.py +0 -0
  120. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence.egg-info/dependency_links.txt +0 -0
  121. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence.egg-info/entry_points.txt +0 -0
  122. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence.egg-info/requires.txt +0 -0
  123. {mainsequence-3.0.1 → mainsequence-3.0.2}/mainsequence.egg-info/top_level.txt +0 -0
  124. {mainsequence-3.0.1 → mainsequence-3.0.2}/setup.cfg +0 -0
  125. {mainsequence-3.0.1 → mainsequence-3.0.2}/tests/test_agent.py +0 -0
  126. {mainsequence-3.0.1 → mainsequence-3.0.2}/tests/test_portfolio.py +0 -0
  127. {mainsequence-3.0.1 → mainsequence-3.0.2}/tests/test_replicator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -0,0 +1,11 @@
1
+
2
+
3
+ class ApiError(Exception):
4
+ def __init__(self, message, response=None, payload=None):
5
+ super().__init__(message)
6
+ self.response = response
7
+ self.payload = payload
8
+ self.status_code = getattr(response, "status_code", None)
9
+
10
+ class ConflictError(ApiError):
11
+ pass
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from asyncio import exceptions
3
4
 
4
5
  import yaml
5
6
 
@@ -33,6 +34,9 @@ import concurrent.futures
33
34
 
34
35
  from cachetools import TTLCache, cachedmethod
35
36
  from operator import attrgetter
37
+ from mainsequence.client import exceptions
38
+
39
+
36
40
 
37
41
  _default_data_source = None # Module-level cache
38
42
 
@@ -818,7 +822,10 @@ class DataNodeStorage(BasePydanticModel, BaseObjectOrm):
818
822
  s = self.build_session()
819
823
  r = make_request(s=s, loaders=self.LOADERS, r_type="PATCH", url=url, payload=payload, time_out=time_out)
820
824
  if r.status_code != 200:
821
- raise Exception(f"Error in request {r.text}")
825
+ data = r.json() # guaranteed JSON from your backend
826
+ if r.status_code == 409:
827
+ raise exceptions.ConflictError(data["error"])
828
+ raise exceptions.ApiError(data["error"])
822
829
  return self.__class__(**r.json())
823
830
 
824
831
  @classmethod
@@ -0,0 +1,231 @@
1
+ from __future__ import annotations
2
+ from pathlib import Path
3
+ import streamlit as st
4
+
5
+
6
+ # --------------------- small theme helpers ---------------------
7
+
8
+ def inject_css_for_dark_accents():
9
+ st.markdown(
10
+ """
11
+ <style>
12
+ /* Subtle tweaks; Streamlit theme itself comes from .streamlit/config.toml */
13
+ .stMetric > div { background: rgba(255,255,255,0.04); border-radius: 6px; padding: .5rem .75rem; }
14
+ div[data-testid="stMetricDelta"] svg { display: none; } /* clean deltas, hide the arrow */
15
+ </style>
16
+ """,
17
+ unsafe_allow_html=True
18
+ )
19
+
20
+ def explain_theming():
21
+ st.info(
22
+ "Theme colors come from `.streamlit/config.toml`. "
23
+ "You can’t switch Streamlit’s theme at runtime, but you can tune Plotly’s colors and inject light CSS."
24
+ )
25
+
26
+
27
+ # --------------------- spinner frame loader (runs once on import) ---------------------
28
+
29
+ def _read_txt(p: Path) -> str:
30
+ return p.read_text(encoding="utf-8").strip()
31
+
32
+ def _load_spinner_frames_for_this_template() -> list[str]:
33
+ """
34
+ Looks under: <repo>/mainsequence/dashboards/streamlit/assets/
35
+
36
+ Order of precedence:
37
+ 1) image_1_base64.txt ... image_5_base64.txt
38
+ 2) image_base64.txt (single file, replicated to 5 frames)
39
+ 3) spinner_1.txt ... spinner_5.txt
40
+ 4) Any *base64.txt (sorted) or *.txt (sorted), up to 5 frames
41
+ - If only one file is found, it is replicated to 5 frames.
42
+ - If 2-4 files are found, the last one is repeated to reach 5.
43
+ On total failure, returns five copies of a 1x1 transparent PNG.
44
+ """
45
+ assets = Path(__file__).resolve().parent.parent / "assets"
46
+
47
+ # 1) Named sequence: image_1_base64.txt .. image_5_base64.txt
48
+ seq = [assets / f"image_{i}_base64.txt" for i in range(1, 6)]
49
+ if all(p.exists() for p in seq):
50
+ return [_read_txt(p) for p in seq]
51
+
52
+ # 2) Single file replicated
53
+ single = assets / "image_base64.txt"
54
+ if single.exists():
55
+ s = _read_txt(single)
56
+ return [s] * 5
57
+
58
+ # 3) Alternate sequence
59
+ alt_seq = [assets / f"spinner_{i}.txt" for i in range(1, 6)]
60
+ if all(p.exists() for p in alt_seq):
61
+ return [_read_txt(p) for p in alt_seq]
62
+
63
+ # 4) Any *base64.txt, then any *.txt
64
+ candidates = sorted(assets.glob("*base64.txt")) or sorted(assets.glob("*.txt"))
65
+ frames = [_read_txt(p) for p in candidates[:5]]
66
+ if frames:
67
+ if len(frames) == 1:
68
+ frames = frames * 5
69
+ elif len(frames) < 5:
70
+ frames += [frames[-1]] * (5 - len(frames))
71
+ return frames
72
+
73
+ # Fallback: 1x1 transparent PNG
74
+ transparent_png = ("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8"
75
+ "/w8AAn8B9p7u3t8AAAAASUVORK5CYII=")
76
+ return [transparent_png] * 5
77
+
78
+ try:
79
+ _SPINNER_FRAMES_RAW = _load_spinner_frames_for_this_template()
80
+ except Exception:
81
+ # Never break import due to spinner assets
82
+ transparent_png = ("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8"
83
+ "/w8AAn8B9p7u3t8AAAAASUVORK5CYII=")
84
+ _SPINNER_FRAMES_RAW = [transparent_png] * 5
85
+
86
+ # Public constants (used only within this module, but left as globals for clarity)
87
+ IMAGE_1_B64, IMAGE_2_B64, IMAGE_3_B64, IMAGE_4_B64, IMAGE_5_B64 = _SPINNER_FRAMES_RAW
88
+
89
+
90
+ # --------------------- spinner override (CSS only) ---------------------
91
+
92
+ def override_spinners(
93
+ hide_deploy_button: bool = False,
94
+ *,
95
+ # Sizes
96
+ top_px: int = 20, # top-right toolbar spinner size
97
+ inline_px: int = 96, # inline/status spinner size
98
+ # Timing
99
+ duration_ms: int = 900,
100
+ # Toolbar micro-positioning
101
+ toolbar_nudge_px: int = -2,
102
+ toolbar_gap_left_px: int = 2,
103
+ toolbar_left_offset_px: int = 0,
104
+ # Overlay options (for inline/status)
105
+ center_non_toolbar: bool = True,
106
+ dim_backdrop: bool = False,
107
+ overlay_blur_px: float = 0.0,
108
+ overlay_opacity: float = 0.0,
109
+ overlay_z_index: int = 9990,
110
+ ) -> None:
111
+ """Replace Streamlit's spinners with a 5‑frame bitmap animation.
112
+
113
+ This injects CSS only (no JS). It hides native SVGs and applies the frames
114
+ to the toolbar spinner, inline st.spinner, and st.status icon.
115
+ """
116
+
117
+ def as_data_uri(s: str, mime: str = "image/png") -> str:
118
+ s = (s or "").strip()
119
+ return s if s.startswith("data:") else f"data:{mime};base64,{s}"
120
+
121
+ i1 = as_data_uri(IMAGE_1_B64)
122
+ i2 = as_data_uri(IMAGE_2_B64)
123
+ i3 = as_data_uri(IMAGE_3_B64)
124
+ i4 = as_data_uri(IMAGE_4_B64)
125
+ i5 = as_data_uri(IMAGE_5_B64)
126
+
127
+ st.markdown(f"""
128
+ <style>
129
+ /* ===== 5-frame animation (fixed: do NOT use '0%%') ===== */
130
+ @keyframes st-fiveframe {{
131
+ 0% {{ background-image:url("{i1}"); }}
132
+ 20% {{ background-image:url("{i2}"); }}
133
+ 40% {{ background-image:url("{i3}"); }}
134
+ 60% {{ background-image:url("{i4}"); }}
135
+ 80% {{ background-image:url("{i5}"); }}
136
+ 100%{{ background-image:url("{i5}"); }}
137
+ }}
138
+
139
+ :root {{
140
+ --st-spin-top:{top_px}px;
141
+ --st-spin-inline:{inline_px}px;
142
+ --st-spin-dur:{duration_ms}ms;
143
+
144
+ --st-toolbar-nudge:{toolbar_nudge_px}px;
145
+ --st-toolbar-gap:{toolbar_gap_left_px}px;
146
+ --st-toolbar-left:{toolbar_left_offset_px}px;
147
+
148
+ --st-overlay-z:{overlay_z_index};
149
+ --st-overlay-bg: rgba(0,0,0,{overlay_opacity});
150
+ --st-overlay-blur:{overlay_blur_px}px;
151
+ }}
152
+
153
+ /* ===== ensure toolbar itself stays clickable above overlays ===== */
154
+ div[data-testid="stToolbar"],
155
+ [data-testid="stStatusWidget"] {{
156
+ position: relative;
157
+ z-index: calc(var(--st-overlay-z) + 5);
158
+ }}
159
+
160
+ /* ===== hide every built-in spinner glyph (SVG/img) ===== */
161
+ [data-testid="stSpinner"] svg,
162
+ [data-testid="stSpinnerIcon"] svg,
163
+ [data-testid="stStatusWidget"] svg,
164
+ header [data-testid="stSpinner"] svg {{
165
+ display: none !important;
166
+ }}
167
+
168
+ /* ===== toolbar spinner (top-right) ===== */
169
+ [data-testid="stStatusWidget"] {{
170
+ position:relative;
171
+ padding-left: calc(var(--st-spin-top) + var(--st-toolbar-gap));
172
+ }}
173
+ [data-testid="stStatusWidget"]::before {{
174
+ content:"";
175
+ position:absolute;
176
+ left: var(--st-toolbar-left);
177
+ top:50%;
178
+ transform: translateY(calc(-50% + var(--st-toolbar-nudge)));
179
+ width:var(--st-spin-top);
180
+ height:var(--st-spin-top);
181
+ background-image:url("{i1}");
182
+ background-repeat:no-repeat;
183
+ background-position:center center;
184
+ background-size:contain;
185
+ animation: st-fiveframe var(--st-spin-dur) steps(1, end) infinite;
186
+ }}
187
+
188
+ /* Optionally hide Deploy/Stop toolbar entirely */
189
+ {"div[data-testid='stToolbar']{display:none !important;}" if hide_deploy_button else ""}
190
+
191
+ /* ===== inline st.spinner ===== */
192
+ [data-testid="stSpinner"] {{
193
+ min-height: 0 !important;
194
+ }}
195
+ { "[data-testid='stSpinner']::after { content:''; position:fixed; inset:0; background:var(--st-overlay-bg); backdrop-filter: blur(var(--st-overlay-blur)); z-index: var(--st-overlay-z); pointer-events: none; }" if dim_backdrop else "" }
196
+ [data-testid="stSpinner"]::before {{
197
+ content:"";
198
+ position: fixed;
199
+ left: 50%;
200
+ top: 50%;
201
+ transform: translate(-50%,-50%);
202
+ width: var(--st-spin-inline);
203
+ height: var(--st-spin-inline);
204
+ background-image:url("{i1}");
205
+ background-repeat:no-repeat;
206
+ background-position:center center;
207
+ background-size:contain;
208
+ animation: st-fiveframe var(--st-spin-dur) steps(1, end) infinite;
209
+ z-index: calc(var(--st-overlay-z) + 1);
210
+ }}
211
+
212
+ /* ===== st.status(...) icon ===== */
213
+ [data-testid="stStatus"] [data-testid="stStatusIcon"] svg {{ display:none !important; }}
214
+ { "[data-testid='stStatus']::after { content:''; position:fixed; inset:0; background:var(--st-overlay-bg); backdrop-filter: blur(var(--st-overlay-blur)); z-index: var(--st-overlay-z); pointer-events: none; }" if dim_backdrop else "" }
215
+ [data-testid="stStatus"] [data-testid="stStatusIcon"]::before {{
216
+ content:"";
217
+ position: fixed;
218
+ left: 50%;
219
+ top: 50%;
220
+ transform: translate(-50%,-50%);
221
+ width: var(--st-spin-inline);
222
+ height: var(--st-spin-inline);
223
+ background-image:url("{i1}");
224
+ background-repeat:no-repeat;
225
+ background-position:center center;
226
+ background-size:contain;
227
+ animation: st-fiveframe var(--st-spin-dur) steps(1, end) infinite;
228
+ z-index: calc(var(--st-overlay-z) + 1);
229
+ }}
230
+ </style>
231
+ """, unsafe_allow_html=True)
@@ -10,6 +10,9 @@ from importlib.resources import files as _pkg_files
10
10
  import sys
11
11
  import os
12
12
 
13
+
14
+
15
+
13
16
  def _detect_app_dir() -> Path:
14
17
  """
15
18
  Best-effort detection of the directory that contains the running Streamlit app.
@@ -9,6 +9,7 @@ import pandas as pd
9
9
  from pathlib import Path
10
10
 
11
11
 
12
+
12
13
  DISCOUNT_CURVES_TABLE=msc.Constant.get_value(name="DISCOUNT_CURVES_TABLE")
13
14
  REFERENCE_RATES_FIXING_TABLE = msc.Constant.get_value(name="REFERENCE_RATES_FIXING_TABLE")
14
15
 
@@ -287,20 +288,22 @@ class MSInterface():
287
288
  @cachedmethod(cache=attrgetter("_curve_cache"), lock=attrgetter("_curve_cache_lock"))
288
289
  def get_historical_discount_curve(self, curve_name, target_date):
289
290
  from mainsequence.tdag import APIDataNode
291
+ from mainsequence.logconf import logger
290
292
  data_node = APIDataNode.build_from_identifier(identifier=DISCOUNT_CURVES_TABLE)
291
293
 
292
294
 
293
295
 
294
296
  # for test purposes only get lats observations
295
- update_statistics = data_node.get_update_statistics()
296
- target_date = update_statistics.asset_time_statistics[curve_name]
297
- print("REMOVE ABOCVE ONLY FOR TESTING")
297
+ use_last_observation=os.environ.get("USE_LAST_OBSERVATION_MS_INSTRUMENT","true").lower()=="true"
298
+ if use_last_observation:
299
+ update_statistics = data_node.get_update_statistics()
300
+ target_date = update_statistics.asset_time_statistics[curve_name]
301
+ logger.warning("Curve is using last observation")
298
302
 
299
303
 
300
- try:
301
- limit = target_date + datetime.timedelta(days=1)
302
- except Exception as e:
303
- raise e
304
+
305
+ limit = target_date + datetime.timedelta(days=1)
306
+
304
307
 
305
308
 
306
309
 
@@ -336,18 +339,36 @@ class MSInterface():
336
339
  :return:
337
340
  """
338
341
  from mainsequence.tdag import APIDataNode
342
+ from mainsequence.logconf import logger
343
+ import pytz # patch
339
344
 
340
345
  data_node = APIDataNode.build_from_identifier(identifier=REFERENCE_RATES_FIXING_TABLE)
341
346
 
342
- import pytz # patch
343
347
  start_date = datetime.datetime(2024, 9, 10, tzinfo=pytz.utc)
344
348
  end_date=datetime.datetime(2025, 9, 17, tzinfo=pytz.utc)
349
+
350
+
351
+
345
352
 
346
353
  fixings_df = data_node.get_ranged_data_per_asset(
347
354
  range_descriptor={reference_rate_uid: {"start_date": start_date, "start_date_operand": ">=",
348
355
  "end_date": end_date, "end_date_operand": "<=", }}
349
356
  )
350
357
  if fixings_df.empty:
358
+
359
+ use_last_observation = os.environ.get("USE_LAST_OBSERVATION_MS_INSTRUMENT", "true").lower() == "true"
360
+ if use_last_observation:
361
+ logger.warning("Fixings are using last observation and filled forward")
362
+ fixings_df = data_node.get_ranged_data_per_asset(
363
+ range_descriptor={reference_rate_uid: {"start_date": datetime.datetime(1900,1,1,tzinfo=pytz.utc),
364
+ "start_date_operand": ">=",
365
+ }}
366
+
367
+
368
+ )
369
+
370
+ a=5
371
+
351
372
  raise Exception(f"{reference_rate_uid} has not data between {start_date} and {end_date}.")
352
373
  fixings_df = fixings_df.reset_index().rename(columns={"time_index": "date"})
353
374
  fixings_df["date"] = fixings_df["date"].dt.date
@@ -40,6 +40,10 @@ class Bond(InstrumentModel):
40
40
  settlement_days: int = Field(default=2)
41
41
  schedule: Optional[QSchedule] = Field(None)
42
42
 
43
+ benchmark_rate_index_name: Optional[str] = Field(...,description="A default index benchmark rate, helpful when doing"
44
+ "analysis and we want to map the bond to a bencharmk for example to"
45
+ "the SOFR Curve or to de US Treasury curve etc")
46
+
43
47
  model_config = {"arbitrary_types_allowed": True}
44
48
 
45
49
  _bond: Optional[ql.Bond] = PrivateAttr(default=None)
@@ -279,11 +283,15 @@ class FixedRateBond(Bond):
279
283
  """Plain-vanilla fixed-rate bond following the shared Bond lifecycle."""
280
284
 
281
285
  coupon_rate: float = Field(...)
286
+
282
287
  # Optional market curve if you want to discount off a curve instead of a flat yield
283
288
  discount_curve: Optional[ql.YieldTermStructureHandle] = Field(default=None)
284
289
 
285
290
  model_config = {"arbitrary_types_allowed": True}
286
291
 
292
+ def reset_curve(self, curve: ql.YieldTermStructureHandle) -> None:
293
+ self.discount_curve = curve
294
+
287
295
  def _get_default_discount_curve(self) -> Optional[ql.YieldTermStructureHandle]:
288
296
  return self.discount_curve
289
297
 
@@ -42,11 +42,7 @@ _INDEX_CACHE: Dict[_IndexCacheKey, ql.Index] = {}
42
42
  def clear_index_cache() -> None:
43
43
  _INDEX_CACHE.clear()
44
44
 
45
- constants_to_create = dict(
46
- UST="UST",
47
- )
48
45
 
49
- _C.create_constants_if_not_exist(constants_to_create)
50
46
  # ----------------------------- Config ----------------------------- #
51
47
  # Put every supported identifier here with its curve + index construction config.
52
48
  # No tenor tokens; we store the QuantLib Period directly.
@@ -125,14 +121,15 @@ INDEX_CONFIGS: Dict[str, Dict] = {
125
121
  end_of_month=False, # Irrelevant when scheduling by days
126
122
  ),
127
123
 
128
- _C.get_value(name="REFERENCE_RATE__UST"): dict(
124
+
125
+ _C.get_value(name="REFERENCE_RATE__USD_SOFR"): dict(
129
126
  curve_uid=_C.get_value(name="ZERO_CURVE__UST_CMT_ZERO_CURVE_UID"),
130
- calendar=ql.UnitedStates(ql.UnitedStates.GovernmentBond),
131
- day_counter=ql.ActualActual(ql.ActualActual.Bond), # Treasuries accrue Act/Act (Bond/ICMA)
127
+ calendar=ql.UnitedStates(ql.UnitedStates.FederalReserve),
128
+ day_counter=ql.Actual360(),
132
129
  currency=ql.USDCurrency(),
133
130
  period=ql.Period(6, ql.Months), # Semiannual coupons
134
131
  settlement_days=1, # T+1
135
- bdc=ql.Following, # “next banking business day” => Following
132
+ bdc=ql.ModifiedFollowing,
136
133
  end_of_month=False, # Irrelevant when scheduling by days
137
134
  ),
138
135
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -17,6 +17,7 @@ mainsequence/cli/config.py
17
17
  mainsequence/cli/ssh_utils.py
18
18
  mainsequence/client/__init__.py
19
19
  mainsequence/client/base.py
20
+ mainsequence/client/exceptions.py
20
21
  mainsequence/client/models_helpers.py
21
22
  mainsequence/client/models_report_studio.py
22
23
  mainsequence/client/models_tdag.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "3.0.1"
7
+ version = "3.0.2"
8
8
  description = "Main Sequence SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,212 +0,0 @@
1
- from __future__ import annotations
2
- import streamlit as st
3
- from pathlib import Path
4
-
5
-
6
- def inject_css_for_dark_accents():
7
- st.markdown(
8
- """
9
- <style>
10
- /* Subtle tweaks; Streamlit theme itself comes from .streamlit/config.toml */
11
- .stMetric > div { background: rgba(255,255,255,0.04); border-radius: 6px; padding: .5rem .75rem; }
12
- div[data-testid="stMetricDelta"] svg { display: none; } /* clean deltas, hide the arrow */
13
- </style>
14
- """,
15
- unsafe_allow_html=True
16
- )
17
-
18
- def explain_theming():
19
- st.info(
20
- "Theme colors come from `.streamlit/config.toml`. "
21
- "You can’t switch Streamlit’s theme at runtime, but you can tune Plotly’s colors and inject light CSS."
22
- )
23
-
24
-
25
-
26
-
27
- # --- Load spinner frames ONCE from two levels above, files: image_1_base64.txt ... image_5_base64.txt ---
28
- def _load_spinner_frames_for_this_template() -> list[str]:
29
- base_dir = Path(__file__).resolve().parent.parent
30
- frames: list[str] = []
31
- for i in range(1, 6):
32
- p = base_dir / f"assets/image_{i}_base64.txt"
33
- if not p.exists():
34
- raise FileNotFoundError(f"Missing spinner frame file: {p}")
35
- frames.append(p.read_text(encoding="utf-8").strip())
36
- return frames
37
-
38
-
39
- _SPINNER_FRAMES_RAW = _load_spinner_frames_for_this_template()
40
-
41
-
42
- # Expose constants for the function (keeps the code below simple)
43
- IMAGE_1_B64, IMAGE_2_B64, IMAGE_3_B64, IMAGE_4_B64, IMAGE_5_B64 = _SPINNER_FRAMES_RAW
44
-
45
- def override_spinners(
46
- hide_deploy_button: bool = False,
47
- *,
48
- # Sizes
49
- top_px: int = 35, # top-right toolbar & st.status icon base size
50
- inline_px: int = 288, # animation size when centered
51
- # Timing
52
- duration_ms: int = 900,
53
- # Toolbar nudges / spacing
54
- toolbar_nudge_px: int = -3,
55
- toolbar_gap_left_px: int = 2,
56
- toolbar_left_offset_px: int = 0,
57
- # Centered overlay styling
58
- center_non_toolbar: bool = True, # << keep True to center inline + status
59
- dim_backdrop: bool = True, # << set False to hide the dark veil
60
- overlay_blur_px: float = 1.5,
61
- overlay_opacity: float = 0.35,
62
- overlay_z_index: int = 9990, # keep below toolbar; we also lift toolbar above
63
- ) -> None:
64
- """Override Streamlit spinners with a 4-frame animation.
65
- - Toolbar spinner stays in the toolbar (top-right).
66
- - All other spinners (inline + st.status icon) are centered on screen.
67
- """
68
-
69
- def as_data_uri(s: str, mime="image/png") -> str:
70
- s = s.strip()
71
- return s if s.startswith("data:") else f"data:{mime};base64,{s}"
72
-
73
- i1 = as_data_uri(IMAGE_1_B64)
74
- i2 = as_data_uri(IMAGE_2_B64)
75
- i3 = as_data_uri(IMAGE_3_B64)
76
- i4 = as_data_uri(IMAGE_4_B64)
77
- i5= as_data_uri(IMAGE_5_B64)
78
-
79
- veil_bg = f"rgba(0,0,0,{overlay_opacity})"
80
-
81
- st.markdown(f"""
82
- <style>
83
- /* ---- 4-frame animation ---- */
84
- @keyframes st-fourframe {{
85
- 0%% {{ background-image:url("{i1}"); }}
86
- 20% {{ background-image:url("{i2}"); }}
87
- 40% {{ background-image:url("{i3}"); }}
88
- 60% {{ background-image:url("{i4}"); }}
89
- 80% {{ background-image:url("{i5}"); }}
90
- 100% {{ background-image:url("{i5}"); }}
91
- }}
92
-
93
- /* ---- CSS variables ---- */
94
- :root {{
95
- --st-spin-top:{top_px}px; /* toolbar/status base size */
96
- --st-spin-inline:{inline_px}px; /* centered spinner size */
97
- --st-spin-dur:{duration_ms}ms;
98
-
99
- --st-spin-toolbar-nudge:{toolbar_nudge_px}px;
100
- --st-spin-toolbar-gap:{toolbar_gap_left_px}px;
101
- --st-spin-toolbar-left:{toolbar_left_offset_px}px;
102
-
103
- --st-overlay-z:{overlay_z_index};
104
- --st-overlay-bg:{veil_bg};
105
- --st-overlay-blur:{overlay_blur_px}px;
106
- }}
107
-
108
- /* Lift toolbar above any overlay so Stop/Deploy remain clickable */
109
- div[data-testid="stToolbar"],
110
- [data-testid="stStatusWidget"] {{
111
- position: relative;
112
- z-index: calc(var(--st-overlay-z) + 5);
113
- }}
114
-
115
- /* =======================================================================
116
- 1) Top-right toolbar widget (kept in place, not centered)
117
- ======================================================================= */
118
- [data-testid="stStatusWidget"] {{
119
- position:relative;
120
- padding-left: calc(var(--st-spin-top) + var(--st-spin-toolbar-gap));
121
- }}
122
- [data-testid="stStatusWidget"] svg,
123
- [data-testid="stStatusWidget"] img {{ display:none !important; }}
124
- [data-testid="stStatusWidget"]::before {{
125
- content:"";
126
- position:absolute;
127
- left: var(--st-spin-toolbar-left);
128
- top:50%;
129
- transform:translateY(-50%) translateY(var(--st-spin-toolbar-nudge));
130
- width:var(--st-spin-top);
131
- height:var(--st-spin-top);
132
- background:no-repeat center/contain;
133
- animation:st-fourframe var(--st-spin-dur) linear infinite;
134
- }}
135
-
136
- /* Hide the entire toolbar if requested */
137
- {"div[data-testid='stToolbar']{display:none !important;}" if hide_deploy_button else ""}
138
-
139
- /* =======================================================================
140
- 2) Inline spinner (st.spinner) — centered overlay
141
- ======================================================================= */
142
- [data-testid="stSpinner"] svg {{ display:none !important; }}
143
- [data-testid="stSpinner"] {{
144
- min-height: 0 !important; /* avoid layout jump, since we center globally */
145
- }}
146
- { "[data-testid='stSpinner']::after { content:''; position:fixed; inset:0; background:var(--st-overlay-bg); backdrop-filter: blur(var(--st-overlay-blur)); z-index: var(--st-overlay-z); pointer-events: none; }" if dim_backdrop else "" }
147
- [data-testid="stSpinner"]::before {{
148
- content:"";
149
- position: fixed;
150
- left: 50%;
151
- top: 50%;
152
- transform: translate(-50%,-50%);
153
- width: var(--st-spin-inline);
154
- height: var(--st-spin-inline);
155
- background:no-repeat center/contain;
156
- animation:st-fourframe var(--st-spin-dur) linear infinite;
157
- z-index: calc(var(--st-overlay-z) + 1);
158
- }}
159
-
160
- /* Center the spinner message below the animation (works in sidebar or main) */
161
- [data-testid="stSpinner"] [data-testid="stSpinnerMessage"],
162
- [data-testid="stSpinner"] > div > div:last-child,
163
- [data-testid="stSpinner"] > div > div:only-child {{
164
- position: fixed !important;
165
- left: 50% !important;
166
- top: calc(50% + var(--st-spin-inline) / 2 + 12px) !important;
167
- transform: translateX(-50%) !important;
168
- z-index: calc(var(--st-overlay-z) + 2) !important;
169
- text-align: center !important;
170
- margin: 0 !important;
171
- padding: .25rem .75rem !important;
172
- max-width: min(80vw, 900px) !important; /* keeps long text from stretching off-screen */
173
- white-space: normal !important; /* use `nowrap` if you prefer single-line */
174
- font-weight: 500 !important;
175
- }}
176
-
177
- /* Kill the tiny default glyph wrapper so you don't get a stray dot in the sidebar */
178
- [data-testid="stSpinner"] > div > div:first-child {{
179
- display: none !important;
180
- }}
181
-
182
- /* We still hide the default SVG everywhere */
183
- [data-testid="stSpinner"] svg {{
184
- display: none !important;
185
- }}
186
-
187
- /* =======================================================================
188
- 3) st.status(...) icon — centered overlay
189
- ======================================================================= */
190
- [data-testid="stStatus"] [data-testid="stStatusIcon"] svg,
191
- [data-testid="stStatus"] [data-testid="stStatusIcon"] img {{ display:none !important; }}
192
- {"[data-testid='stStatus']::after { content:''; position:fixed; inset:0; background:var(--st-overlay-bg); backdrop-filter: blur(var(--st-overlay-blur)); z-index: var(--st-overlay-z); pointer-events: none; }" if dim_backdrop else ""}
193
- [data-testid="stStatus"] [data-testid="stStatusIcon"]::before {{
194
- content:"";
195
- position: fixed;
196
- left: 50%;
197
- top: 50%;
198
- transform: translate(-50%,-50%);
199
- width: var(--st-spin-inline); /* use same size as inline */
200
- height: var(--st-spin-inline);
201
- background:no-repeat center/contain;
202
- animation:st-fourframe var(--st-spin-dur) linear infinite;
203
- z-index: calc(var(--st-overlay-z) + 1);
204
- }}
205
-
206
- /* Optional: allow 'esc' feel without blocking clicks — achieved via pointer-events:none above. */
207
- </style>
208
- """, unsafe_allow_html=True)
209
-
210
-
211
-
212
-
File without changes
File without changes
File without changes