mainsequence 3.0.1__py3-none-any.whl → 3.0.2__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/client/exceptions.py +11 -0
- mainsequence/client/models_tdag.py +8 -1
- mainsequence/dashboards/streamlit/core/theme.py +128 -109
- mainsequence/dashboards/streamlit/scaffold.py +3 -0
- mainsequence/instruments/data_interface/data_interface.py +29 -8
- mainsequence/instruments/instruments/bond.py +8 -0
- mainsequence/instruments/pricing_models/indices.py +5 -8
- {mainsequence-3.0.1.dist-info → mainsequence-3.0.2.dist-info}/METADATA +1 -1
- {mainsequence-3.0.1.dist-info → mainsequence-3.0.2.dist-info}/RECORD +13 -12
- {mainsequence-3.0.1.dist-info → mainsequence-3.0.2.dist-info}/WHEEL +0 -0
- {mainsequence-3.0.1.dist-info → mainsequence-3.0.2.dist-info}/entry_points.txt +0 -0
- {mainsequence-3.0.1.dist-info → mainsequence-3.0.2.dist-info}/licenses/LICENSE +0 -0
- {mainsequence-3.0.1.dist-info → mainsequence-3.0.2.dist-info}/top_level.txt +0 -0
@@ -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
|
-
|
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
|
@@ -1,8 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import streamlit as st
|
3
2
|
from pathlib import Path
|
3
|
+
import streamlit as st
|
4
4
|
|
5
5
|
|
6
|
+
# --------------------- small theme helpers ---------------------
|
7
|
+
|
6
8
|
def inject_css_for_dark_accents():
|
7
9
|
st.markdown(
|
8
10
|
"""
|
@@ -22,126 +24,173 @@ def explain_theming():
|
|
22
24
|
)
|
23
25
|
|
24
26
|
|
27
|
+
# --------------------- spinner frame loader (runs once on import) ---------------------
|
25
28
|
|
29
|
+
def _read_txt(p: Path) -> str:
|
30
|
+
return p.read_text(encoding="utf-8").strip()
|
26
31
|
|
27
|
-
# --- Load spinner frames ONCE from two levels above, files: image_1_base64.txt ... image_5_base64.txt ---
|
28
32
|
def _load_spinner_frames_for_this_template() -> list[str]:
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
40
88
|
|
41
89
|
|
42
|
-
#
|
43
|
-
IMAGE_1_B64, IMAGE_2_B64, IMAGE_3_B64, IMAGE_4_B64, IMAGE_5_B64 = _SPINNER_FRAMES_RAW
|
90
|
+
# --------------------- spinner override (CSS only) ---------------------
|
44
91
|
|
45
92
|
def override_spinners(
|
46
93
|
hide_deploy_button: bool = False,
|
47
94
|
*,
|
48
95
|
# Sizes
|
49
|
-
top_px: int =
|
50
|
-
inline_px: int =
|
96
|
+
top_px: int = 20, # top-right toolbar spinner size
|
97
|
+
inline_px: int = 96, # inline/status spinner size
|
51
98
|
# Timing
|
52
99
|
duration_ms: int = 900,
|
53
|
-
# Toolbar
|
54
|
-
toolbar_nudge_px: int = -
|
100
|
+
# Toolbar micro-positioning
|
101
|
+
toolbar_nudge_px: int = -2,
|
55
102
|
toolbar_gap_left_px: int = 2,
|
56
103
|
toolbar_left_offset_px: int = 0,
|
57
|
-
#
|
58
|
-
center_non_toolbar: bool = True,
|
59
|
-
dim_backdrop: bool =
|
60
|
-
overlay_blur_px: float =
|
61
|
-
overlay_opacity: float = 0.
|
62
|
-
overlay_z_index: int = 9990,
|
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,
|
63
110
|
) -> None:
|
64
|
-
"""
|
65
|
-
|
66
|
-
|
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.
|
67
115
|
"""
|
68
116
|
|
69
|
-
def as_data_uri(s: str, mime="image/png") -> str:
|
70
|
-
s = s.strip()
|
117
|
+
def as_data_uri(s: str, mime: str = "image/png") -> str:
|
118
|
+
s = (s or "").strip()
|
71
119
|
return s if s.startswith("data:") else f"data:{mime};base64,{s}"
|
72
120
|
|
73
121
|
i1 = as_data_uri(IMAGE_1_B64)
|
74
122
|
i2 = as_data_uri(IMAGE_2_B64)
|
75
123
|
i3 = as_data_uri(IMAGE_3_B64)
|
76
124
|
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})"
|
125
|
+
i5 = as_data_uri(IMAGE_5_B64)
|
80
126
|
|
81
127
|
st.markdown(f"""
|
82
128
|
<style>
|
83
|
-
/*
|
84
|
-
@keyframes st-
|
85
|
-
0
|
86
|
-
20%
|
87
|
-
40%
|
88
|
-
60%
|
89
|
-
|
90
|
-
|
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}"); }}
|
91
137
|
}}
|
92
138
|
|
93
|
-
/* ---- CSS variables ---- */
|
94
139
|
:root {{
|
95
|
-
--st-spin-top:{top_px}px;
|
96
|
-
--st-spin-inline:{inline_px}px;
|
140
|
+
--st-spin-top:{top_px}px;
|
141
|
+
--st-spin-inline:{inline_px}px;
|
97
142
|
--st-spin-dur:{duration_ms}ms;
|
98
143
|
|
99
|
-
--st-
|
100
|
-
--st-
|
101
|
-
--st-
|
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;
|
102
147
|
|
103
148
|
--st-overlay-z:{overlay_z_index};
|
104
|
-
--st-overlay-bg:{
|
149
|
+
--st-overlay-bg: rgba(0,0,0,{overlay_opacity});
|
105
150
|
--st-overlay-blur:{overlay_blur_px}px;
|
106
151
|
}}
|
107
152
|
|
108
|
-
/*
|
153
|
+
/* ===== ensure toolbar itself stays clickable above overlays ===== */
|
109
154
|
div[data-testid="stToolbar"],
|
110
155
|
[data-testid="stStatusWidget"] {{
|
111
156
|
position: relative;
|
112
157
|
z-index: calc(var(--st-overlay-z) + 5);
|
113
158
|
}}
|
114
159
|
|
115
|
-
/*
|
116
|
-
|
117
|
-
|
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) ===== */
|
118
169
|
[data-testid="stStatusWidget"] {{
|
119
170
|
position:relative;
|
120
|
-
padding-left: calc(var(--st-spin-top) + var(--st-
|
171
|
+
padding-left: calc(var(--st-spin-top) + var(--st-toolbar-gap));
|
121
172
|
}}
|
122
|
-
[data-testid="stStatusWidget"] svg,
|
123
|
-
[data-testid="stStatusWidget"] img {{ display:none !important; }}
|
124
173
|
[data-testid="stStatusWidget"]::before {{
|
125
174
|
content:"";
|
126
175
|
position:absolute;
|
127
|
-
left: var(--st-
|
176
|
+
left: var(--st-toolbar-left);
|
128
177
|
top:50%;
|
129
|
-
transform:translateY(-50%
|
178
|
+
transform: translateY(calc(-50% + var(--st-toolbar-nudge)));
|
130
179
|
width:var(--st-spin-top);
|
131
180
|
height:var(--st-spin-top);
|
132
|
-
background:
|
133
|
-
|
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;
|
134
186
|
}}
|
135
187
|
|
136
|
-
/*
|
188
|
+
/* Optionally hide Deploy/Stop toolbar entirely */
|
137
189
|
{"div[data-testid='stToolbar']{display:none !important;}" if hide_deploy_button else ""}
|
138
190
|
|
139
|
-
/*
|
140
|
-
2) Inline spinner (st.spinner) — centered overlay
|
141
|
-
======================================================================= */
|
142
|
-
[data-testid="stSpinner"] svg {{ display:none !important; }}
|
191
|
+
/* ===== inline st.spinner ===== */
|
143
192
|
[data-testid="stSpinner"] {{
|
144
|
-
min-height: 0 !important;
|
193
|
+
min-height: 0 !important;
|
145
194
|
}}
|
146
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 "" }
|
147
196
|
[data-testid="stSpinner"]::before {{
|
@@ -152,61 +201,31 @@ div[data-testid="stToolbar"],
|
|
152
201
|
transform: translate(-50%,-50%);
|
153
202
|
width: var(--st-spin-inline);
|
154
203
|
height: var(--st-spin-inline);
|
155
|
-
background:
|
156
|
-
|
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;
|
157
209
|
z-index: calc(var(--st-overlay-z) + 1);
|
158
210
|
}}
|
159
211
|
|
160
|
-
/*
|
161
|
-
[data-testid="
|
162
|
-
[data-testid=
|
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 ""}
|
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 "" }
|
193
215
|
[data-testid="stStatus"] [data-testid="stStatusIcon"]::before {{
|
194
216
|
content:"";
|
195
217
|
position: fixed;
|
196
218
|
left: 50%;
|
197
219
|
top: 50%;
|
198
220
|
transform: translate(-50%,-50%);
|
199
|
-
width: var(--st-spin-inline);
|
221
|
+
width: var(--st-spin-inline);
|
200
222
|
height: var(--st-spin-inline);
|
201
|
-
background:
|
202
|
-
|
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;
|
203
228
|
z-index: calc(var(--st-overlay-z) + 1);
|
204
229
|
}}
|
205
|
-
|
206
|
-
/* Optional: allow 'esc' feel without blocking clicks — achieved via pointer-events:none above. */
|
207
230
|
</style>
|
208
231
|
""", unsafe_allow_html=True)
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
@@ -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
|
-
|
296
|
-
|
297
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
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
|
-
|
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.
|
131
|
-
day_counter=ql.
|
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.
|
132
|
+
bdc=ql.ModifiedFollowing,
|
136
133
|
end_of_month=False, # Irrelevant when scheduling by days
|
137
134
|
),
|
138
135
|
}
|
@@ -8,9 +8,10 @@ mainsequence/cli/config.py,sha256=UAi0kfxro2oJhzaRdd2D3F72eGpqkYaWjSsCzBrb2LY,27
|
|
8
8
|
mainsequence/cli/ssh_utils.py,sha256=cqodXCJd1EK-PHfG8K_poeg004K6cyqRz6ahpYnbEdk,6127
|
9
9
|
mainsequence/client/__init__.py,sha256=dkiXwFedl9yrmKHprzL_B9BfHRWHRj3d9f_o4-aRH7U,867
|
10
10
|
mainsequence/client/base.py,sha256=yqSu9lUMPDr7ewU1DWJm6jBqXrNUUVGqsihhFcZPs2s,15025
|
11
|
+
mainsequence/client/exceptions.py,sha256=pon9585d5RyMfCVy6dyfpDJ9tCH2ugQ4ptq-eqAl2rY,295
|
11
12
|
mainsequence/client/models_helpers.py,sha256=aXANVSt8qMhI4Diwe_1k4-P6NnJANYvCAfDWQhqGQX8,4095
|
12
13
|
mainsequence/client/models_report_studio.py,sha256=mKu7k0STyUZMTzTK98w46t2b1jv8hVhDpmCzI4EeSnQ,13971
|
13
|
-
mainsequence/client/models_tdag.py,sha256=
|
14
|
+
mainsequence/client/models_tdag.py,sha256=bzHnu0_dFY2-DZ8CeS9Um8cc8aaXLldmlBL2abOgxXw,95529
|
14
15
|
mainsequence/client/models_vam.py,sha256=4dqJkGyVboabo844LjUWO1sTxt40OshzrRoTfSTo-lU,72891
|
15
16
|
mainsequence/client/utils.py,sha256=4aLctRMmQdFtLzaI9b28ifP141pO-VAmiQ7wTdHpqGg,11665
|
16
17
|
mainsequence/client/data_sources_interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -18,7 +19,7 @@ mainsequence/client/data_sources_interfaces/duckdb.py,sha256=cOvq_241eU6jPUw3DqL
|
|
18
19
|
mainsequence/client/data_sources_interfaces/timescale.py,sha256=UkbFnYKv50-qPJDdYXgYOh_e3ildpJyzjC6oD7q6uUk,20318
|
19
20
|
mainsequence/dashboards/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
21
|
mainsequence/dashboards/streamlit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
mainsequence/dashboards/streamlit/scaffold.py,sha256=
|
22
|
+
mainsequence/dashboards/streamlit/scaffold.py,sha256=ww-ON3-ZPSPBPPJPISAOXl0000dI93s3_258uJLhjGA,7535
|
22
23
|
mainsequence/dashboards/streamlit/assets/config.toml,sha256=GCsLTasaF9kJlRRY1SnXLY-X5PyzLkGG4LX48gefkcc,314
|
23
24
|
mainsequence/dashboards/streamlit/assets/favicon.png,sha256=-CHfKx6jfMlXb8TnFQh2BNIBiKeVFfxK7g9K9BaqX6I,347
|
24
25
|
mainsequence/dashboards/streamlit/assets/image_1_base64.txt,sha256=Smm9HT5A6FhC8FUvj9HYEREwUostku8igxiJsS5CT4Q,315018
|
@@ -28,7 +29,7 @@ mainsequence/dashboards/streamlit/assets/image_4_base64.txt,sha256=4KiKYChYUNLB-
|
|
28
29
|
mainsequence/dashboards/streamlit/assets/image_5_base64.txt,sha256=E9Xl4dSMhc_2z-ipHh7BAXsd6Gz2si8dP5NjjhQhyVc,592742
|
29
30
|
mainsequence/dashboards/streamlit/assets/logo.png,sha256=20KBis07H579XnXi0vMNzcjFyaAfW0CmI_dOtj-UYc4,34970
|
30
31
|
mainsequence/dashboards/streamlit/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
-
mainsequence/dashboards/streamlit/core/theme.py,sha256=
|
32
|
+
mainsequence/dashboards/streamlit/core/theme.py,sha256=CBmrv7mu7V7lDzml8inMP41hYfaUpk7mC7tbpa9ug4s,8087
|
32
33
|
mainsequence/dashboards/streamlit/pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
34
|
mainsequence/instrumentation/__init__.py,sha256=qfHOseVGOW_WdoTgw7TWZYpF1REwf14eF9awxWcLtfI,154
|
34
35
|
mainsequence/instrumentation/utils.py,sha256=XgVg9FijubSy5qMNw0yCkl5TMD2n56TnT4Q58WfZXbw,3354
|
@@ -37,10 +38,10 @@ mainsequence/instruments/instruments.default.toml,sha256=RnCWtS7YJ4t_JqTHJWUdvJh
|
|
37
38
|
mainsequence/instruments/settings.py,sha256=QbgNar9gEkaFrpMUM79T7SWkoBcvFVMAKVL8CvSlB4g,279
|
38
39
|
mainsequence/instruments/utils.py,sha256=eiqaTfcq4iulSdEwgi1sMq8xAKK8ONl22a-FNItcbYk,698
|
39
40
|
mainsequence/instruments/data_interface/__init__.py,sha256=aQNXx0csDadrwwFqDlYKRwnqIk1OREsYF7ybjm29Ipo,556
|
40
|
-
mainsequence/instruments/data_interface/data_interface.py,sha256=
|
41
|
+
mainsequence/instruments/data_interface/data_interface.py,sha256=XDTk7BUApt_H5sGhhtQKycrBLT3iPoF1EUarfM22lEg,16014
|
41
42
|
mainsequence/instruments/instruments/__init__.py,sha256=Xe1hCMsOGc3IyyfA90Xuv7pO9t8TctpM7fo-6hML9Cg,154
|
42
43
|
mainsequence/instruments/instruments/base_instrument.py,sha256=XQeZxDSGhZ_ZNut3867I_q3UvDvp9VZDW4VRzyyFe-A,3185
|
43
|
-
mainsequence/instruments/instruments/bond.py,sha256=
|
44
|
+
mainsequence/instruments/instruments/bond.py,sha256=DNFm9zAFdiLoEjZaEoJJUMuPYHzZ3krVFxZ0VBhRmoU,17925
|
44
45
|
mainsequence/instruments/instruments/european_option.py,sha256=amyl52DsgOSMDiMZn4CbmWA4QGfVn38ojVVMhdOLKXM,2760
|
45
46
|
mainsequence/instruments/instruments/interest_rate_swap.py,sha256=GJLi4yiIZNwjASLiOnEGw2owCh-v4NDQdcONNWS-wGs,7893
|
46
47
|
mainsequence/instruments/instruments/json_codec.py,sha256=94sOEL2N7nPMUsleB3GD3FzwYy5sVYsgthvQ2g8u9gE,22070
|
@@ -52,7 +53,7 @@ mainsequence/instruments/pricing_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
52
53
|
mainsequence/instruments/pricing_models/black_scholes.py,sha256=bs7fsgiDKRuBZo7fvemVdmUHN-NjC2v6XUjhPaLNC-4,1588
|
53
54
|
mainsequence/instruments/pricing_models/bond_pricer.py,sha256=JXBOsDonlLwYG3GyHGD7rV1cddCPot8ZgE1Th-tHiqY,6566
|
54
55
|
mainsequence/instruments/pricing_models/fx_option_pricer.py,sha256=cMRejaIo3ZEhKCS960C7lg8ejh3b9RqIfL3Hhj4LB0A,3292
|
55
|
-
mainsequence/instruments/pricing_models/indices.py,sha256=
|
56
|
+
mainsequence/instruments/pricing_models/indices.py,sha256=yptDA8joZ5wD28Xljp7jcE77MCEMK3EF1ScRCBanNEs,13552
|
56
57
|
mainsequence/instruments/pricing_models/knockout_fx_pricer.py,sha256=FrO1uRn63zktkm6LwfLKZf0vPn5x0NOCBIPfSPEIO9c,6772
|
57
58
|
mainsequence/instruments/pricing_models/swap_pricer.py,sha256=vQXL1pgWPS9EUu8xXDyrA6QZm-t7wH0Hx3GU3yl1HHw,17994
|
58
59
|
mainsequence/reportbuilder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -109,9 +110,9 @@ mainsequence/virtualfundbuilder/resource_factory/app_factory.py,sha256=XSZo9ImdT
|
|
109
110
|
mainsequence/virtualfundbuilder/resource_factory/base_factory.py,sha256=jPXdK2WCaNw3r6kP3sZGIL7M4ygfIMs8ek3Yq4QYQZg,9434
|
110
111
|
mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py,sha256=ysEeJrlbxrMPA7wFw7KDtuCTzXYkdfYZuxUFpPPY7vE,3732
|
111
112
|
mainsequence/virtualfundbuilder/resource_factory/signal_factory.py,sha256=ywa7vxxLlQopuRwwRKyj866ftgaj8uKVoiPQ9YJ2IIo,7198
|
112
|
-
mainsequence-3.0.
|
113
|
-
mainsequence-3.0.
|
114
|
-
mainsequence-3.0.
|
115
|
-
mainsequence-3.0.
|
116
|
-
mainsequence-3.0.
|
117
|
-
mainsequence-3.0.
|
113
|
+
mainsequence-3.0.2.dist-info/licenses/LICENSE,sha256=fXoCKgEuZXmP84_QDXpvO18QHze_6AAhd-zvZBUjJxQ,4524
|
114
|
+
mainsequence-3.0.2.dist-info/METADATA,sha256=Sjbad1ij3ayfAZzZkzzr0-se9kqL6vQU9X5XHWTKC9Y,8028
|
115
|
+
mainsequence-3.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
116
|
+
mainsequence-3.0.2.dist-info/entry_points.txt,sha256=2J8TprrUndh7AttNTlXAaxgGtkXFUAHgXs-M7DCj5MU,58
|
117
|
+
mainsequence-3.0.2.dist-info/top_level.txt,sha256=uSLD9rXMDMN0cc1x0p808bwyQMoRmYY2pdQZEWLajX8,13
|
118
|
+
mainsequence-3.0.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|