mainsequence 2.0.2a0__py3-none-any.whl → 2.0.4__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/cli/api.py +31 -0
- mainsequence/cli/cli.py +28 -4
- mainsequence/cli/config.py +4 -0
- mainsequence/client/__init__.py +1 -1
- mainsequence/client/base.py +1 -0
- mainsequence/client/models_tdag.py +130 -2
- mainsequence/instruments/__init__.py +2 -1
- mainsequence/instruments/data_interface/__init__.py +17 -5
- mainsequence/instruments/data_interface/data_interface.py +9 -1
- mainsequence/instruments/instruments/interest_rate_swap.py +2 -0
- mainsequence/instruments/instruments.default.toml +17 -0
- mainsequence/instruments/pricing_models/indices.py +26 -18
- mainsequence/instruments/settings.py +25 -26
- {mainsequence-2.0.2a0.dist-info → mainsequence-2.0.4.dist-info}/METADATA +1 -1
- {mainsequence-2.0.2a0.dist-info → mainsequence-2.0.4.dist-info}/RECORD +19 -18
- {mainsequence-2.0.2a0.dist-info → mainsequence-2.0.4.dist-info}/WHEEL +0 -0
- {mainsequence-2.0.2a0.dist-info → mainsequence-2.0.4.dist-info}/entry_points.txt +0 -0
- {mainsequence-2.0.2a0.dist-info → mainsequence-2.0.4.dist-info}/licenses/LICENSE +0 -0
- {mainsequence-2.0.2a0.dist-info → mainsequence-2.0.4.dist-info}/top_level.txt +0 -0
mainsequence/cli/api.py
CHANGED
@@ -38,6 +38,9 @@ def _refresh_token() -> str | None:
|
|
38
38
|
return tok.get("refresh")
|
39
39
|
|
40
40
|
def login(email: str, password: str) -> dict:
|
41
|
+
email = (email or "").strip()
|
42
|
+
password = (password or "").rstrip("\r\n")
|
43
|
+
|
41
44
|
url = _full(AUTH_PATHS["obtain"])
|
42
45
|
payload = {"email": email, "password": password} # server expects 'email'
|
43
46
|
r = S.post(url, data=json.dumps(payload))
|
@@ -155,3 +158,31 @@ def add_deploy_key(project_id: int | str, key_title: str, public_key: str) -> No
|
|
155
158
|
{"key_title": key_title, "public_key": public_key})
|
156
159
|
except Exception:
|
157
160
|
pass
|
161
|
+
|
162
|
+
def get_project_token(project_id: int | str) -> str:
|
163
|
+
"""
|
164
|
+
Fetch the project's token using the current access token.
|
165
|
+
If the access token is expired or missing, authed() will refresh once.
|
166
|
+
If refresh also fails, NotLoggedIn is raised so the caller can prompt re-login.
|
167
|
+
"""
|
168
|
+
r = authed("GET", f"/orm/api/pods/projects/{project_id}/get_project_token/")
|
169
|
+
if not r.ok:
|
170
|
+
# authed() already tried refresh on 401;
|
171
|
+
# at this point treat as API error with server message.
|
172
|
+
msg = r.text
|
173
|
+
try:
|
174
|
+
if r.headers.get("content-type","").startswith("application/json"):
|
175
|
+
data = r.json()
|
176
|
+
msg = data.get("detail") or data.get("message") or msg
|
177
|
+
except Exception:
|
178
|
+
pass
|
179
|
+
raise ApiError(f"Project token fetch failed ({r.status_code}). {msg}")
|
180
|
+
|
181
|
+
token = None
|
182
|
+
if r.headers.get("content-type","").startswith("application/json"):
|
183
|
+
data = r.json()
|
184
|
+
token=data["development"][0]
|
185
|
+
token = (r.text or "").strip()
|
186
|
+
if not token:
|
187
|
+
raise ApiError("Project token response did not include a token.")
|
188
|
+
return token
|
mainsequence/cli/cli.py
CHANGED
@@ -26,6 +26,7 @@ from .api import (
|
|
26
26
|
login as api_login,
|
27
27
|
repo_name_from_git_url,
|
28
28
|
safe_slug,
|
29
|
+
get_project_token
|
29
30
|
)
|
30
31
|
from .ssh_utils import (
|
31
32
|
ensure_key_for_repo,
|
@@ -128,7 +129,6 @@ def _render_projects_table(items: list[dict], links: dict, base_dir: str, org_sl
|
|
128
129
|
def login(
|
129
130
|
email: str = typer.Argument(..., help="Email/username (server expects 'email' field)"),
|
130
131
|
password: Optional[str] = typer.Option(None, prompt=True, hide_input=True, help="Password"),
|
131
|
-
export: bool = typer.Option(False, "--export", help='Print `export MAIN_SEQUENCE_USER_TOKEN=...` so you can eval it'),
|
132
132
|
no_status: bool = typer.Option(False, "--no-status", help="Do not print projects table after login")
|
133
133
|
):
|
134
134
|
"""
|
@@ -146,9 +146,7 @@ def login(
|
|
146
146
|
typer.secho(f"Signed in as {res['username']} (Backend: {res['backend']})", fg=typer.colors.GREEN)
|
147
147
|
typer.echo(f"Projects base folder: {base}")
|
148
148
|
|
149
|
-
|
150
|
-
if export and tok:
|
151
|
-
print(f'export MAIN_SEQUENCE_USER_TOKEN="{tok}"')
|
149
|
+
|
152
150
|
|
153
151
|
if not no_status:
|
154
152
|
try:
|
@@ -287,6 +285,7 @@ def project_set_up_locally(
|
|
287
285
|
cfg_obj = cfg.get_config()
|
288
286
|
base = base_dir or cfg_obj["mainsequence_path"]
|
289
287
|
|
288
|
+
|
290
289
|
org_slug = _org_slug_from_profile()
|
291
290
|
|
292
291
|
items = get_projects()
|
@@ -346,8 +345,33 @@ def project_set_up_locally(
|
|
346
345
|
else:
|
347
346
|
if env_text and not env_text.endswith("\n"): env_text += "\n"
|
348
347
|
env_text += f"VFB_PROJECT_PATH={str(target_dir)}\n"
|
348
|
+
|
349
|
+
try:
|
350
|
+
project_token = get_project_token(project_id)
|
351
|
+
except NotLoggedIn:
|
352
|
+
typer.secho("Session expired or refresh failed. Run: mainsequence login <email>", fg=typer.colors.RED)
|
353
|
+
raise typer.Exit(1)
|
354
|
+
except ApiError as e:
|
355
|
+
typer.secho(f"Could not fetch project token: {e}", fg=typer.colors.RED)
|
356
|
+
raise typer.Exit(1)
|
357
|
+
|
358
|
+
lines = env_text.splitlines()
|
359
|
+
if any(line.startswith("MAINSEQUENCE_TOKEN=") for line in lines):
|
360
|
+
lines = [
|
361
|
+
(f"MAINSEQUENCE_TOKEN={project_token}" if line.startswith("MAINSEQUENCE_TOKEN=") else line)
|
362
|
+
for line in lines
|
363
|
+
]
|
364
|
+
env_text = "\n".join(lines)
|
365
|
+
else:
|
366
|
+
if env_text and not env_text.endswith("\n"): env_text += "\n"
|
367
|
+
env_text += f"MAINSEQUENCE_TOKEN={project_token}\n"
|
368
|
+
|
369
|
+
# write final .env with both vars present
|
349
370
|
(target_dir / ".env").write_text(env_text, encoding="utf-8")
|
350
371
|
|
372
|
+
|
373
|
+
|
374
|
+
|
351
375
|
cfg.set_link(project_id, str(target_dir))
|
352
376
|
|
353
377
|
typer.secho(f"Local folder: {target_dir}", fg=typer.colors.GREEN)
|
mainsequence/cli/config.py
CHANGED
@@ -75,4 +75,8 @@ def set_env_access(access: str) -> None:
|
|
75
75
|
def backend_url() -> str:
|
76
76
|
cfg = get_config()
|
77
77
|
url = (cfg.get("backend_url") or DEFAULTS["backend_url"]).rstrip("/")
|
78
|
+
|
79
|
+
if os.environ.get("MAIN_SEQUENCE_BACKEND_URL") is not None:
|
80
|
+
url=os.environ.get("MAIN_SEQUENCE_BACKEND_URL")
|
81
|
+
|
78
82
|
return url
|
mainsequence/client/__init__.py
CHANGED
@@ -4,7 +4,7 @@ from .models_tdag import (request_to_datetime, LocalTimeSeriesDoesNotExist, Dyna
|
|
4
4
|
SourceTableConfigurationDoesNotExist, LocalTimeSerieUpdateDetails,
|
5
5
|
JSON_COMPRESSED_PREFIX, Scheduler, SchedulerDoesNotExist, LocalTimeSerie,
|
6
6
|
DynamicTableMetaData, DynamicTableDataSource,DUCK_DB,
|
7
|
-
ColumnMetaData,Artifact,TableMetaData ,DataFrequency,SourceTableConfiguration,
|
7
|
+
ColumnMetaData,Artifact,TableMetaData ,DataFrequency,SourceTableConfiguration,Constant,
|
8
8
|
Project, UniqueIdentifierRangeMap, LocalTimeSeriesHistoricalUpdate,
|
9
9
|
UpdateStatistics, DataSource, PodDataSource, SessionDataSource)
|
10
10
|
|
mainsequence/client/base.py
CHANGED
@@ -18,9 +18,10 @@ from typing import Union
|
|
18
18
|
import time
|
19
19
|
import os
|
20
20
|
from mainsequence.logconf import logger
|
21
|
+
from threading import RLock
|
21
22
|
|
22
|
-
from pydantic import BaseModel, Field, field_validator
|
23
|
-
from typing import Optional, List, Dict, Any, TypedDict, Tuple
|
23
|
+
from pydantic import BaseModel, Field, field_validator,computed_field
|
24
|
+
from typing import Optional, List, Dict, Any, TypedDict, Tuple, ClassVar
|
24
25
|
from .data_sources_interfaces import timescale as TimeScaleInterface
|
25
26
|
from functools import wraps
|
26
27
|
import math
|
@@ -29,6 +30,9 @@ import base64
|
|
29
30
|
import numpy as np
|
30
31
|
import concurrent.futures
|
31
32
|
|
33
|
+
from cachetools import TTLCache, cachedmethod
|
34
|
+
from operator import attrgetter
|
35
|
+
|
32
36
|
_default_data_source = None # Module-level cache
|
33
37
|
|
34
38
|
JSON_COMPRESSED_PREFIX = ["json_compressed", "jcomp_"]
|
@@ -2272,5 +2276,129 @@ class PodDataSource:
|
|
2272
2276
|
return f"{self.data_source.related_resource}"
|
2273
2277
|
|
2274
2278
|
|
2279
|
+
|
2280
|
+
def _norm_value(v: Any) -> Any:
|
2281
|
+
"""Normalize values into hashable, deterministic forms for the cache key."""
|
2282
|
+
# Project objects → their integer IDs (project scoped vs global)
|
2283
|
+
if Project and isinstance(v, Project):
|
2284
|
+
return getattr(v, "id", v)
|
2285
|
+
|
2286
|
+
# Common iterables → sorted tuples to ignore order in queries like name__in
|
2287
|
+
if isinstance(v, (set, list, tuple)):
|
2288
|
+
# Convert nested items too, just in case
|
2289
|
+
return tuple(sorted(_norm_value(x) for x in v))
|
2290
|
+
|
2291
|
+
# Dicts → sorted (k,v) tuples
|
2292
|
+
if isinstance(v, dict):
|
2293
|
+
return tuple(sorted((k, _norm_value(val)) for k, val in v.items()))
|
2294
|
+
|
2295
|
+
return v # primitives pass through
|
2296
|
+
def _norm_kwargs(kwargs: Dict[str, Any]) -> Tuple[Tuple[str, Any], ...]:
|
2297
|
+
"""Stable, hashable key from kwargs (order-insensitive)."""
|
2298
|
+
items = []
|
2299
|
+
for k, v in kwargs.items():
|
2300
|
+
# Special-case a big `name__in` so you don’t produce huge keys.
|
2301
|
+
if k == "name__in" and isinstance(v, (list, tuple, set)):
|
2302
|
+
items.append((k, tuple(sorted(str(x) for x in v))))
|
2303
|
+
else:
|
2304
|
+
items.append((k, _norm_value(v)))
|
2305
|
+
return tuple(sorted(items))
|
2306
|
+
|
2307
|
+
class Constant(BasePydanticModel, BaseObjectOrm):
|
2308
|
+
"""
|
2309
|
+
Simple scoped constant.
|
2310
|
+
- Global when project is None.
|
2311
|
+
- Project-scoped when project is set.
|
2312
|
+
|
2313
|
+
Uniqueness (enforced in DB/service layer):
|
2314
|
+
* Global: (organization_owner, name)
|
2315
|
+
* Per-project: (organization_owner, project, name)
|
2316
|
+
"""
|
2317
|
+
id: Optional[int]
|
2318
|
+
|
2319
|
+
name: str = Field(
|
2320
|
+
...,
|
2321
|
+
max_length=255,
|
2322
|
+
description="UPPER_SNAKE_CASE; optional category via double-underscore, e.g. 'CURVE__US_TREASURIES'."
|
2323
|
+
)
|
2324
|
+
value: Any = Field(
|
2325
|
+
...,
|
2326
|
+
description="Small JSON value (string/number/bool/object/array). Keep it small (e.g., <=10KB).",
|
2327
|
+
)
|
2328
|
+
project: Optional[Union[Project,int]] = Field(
|
2329
|
+
None,
|
2330
|
+
description="Project ID; None ⇒ global."
|
2331
|
+
)
|
2332
|
+
category: Optional[str] = None
|
2333
|
+
|
2334
|
+
# Class-level cache & lock (Pydantic ignores ClassVar)
|
2335
|
+
_filter_cache: ClassVar[TTLCache] = TTLCache(maxsize=512, ttl=600)
|
2336
|
+
_get_cache: ClassVar[TTLCache] = TTLCache(maxsize=1024, ttl=600)
|
2337
|
+
|
2338
|
+
|
2339
|
+
_cache_lock: ClassVar[RLock] = RLock()
|
2340
|
+
|
2341
|
+
model_config = dict(from_attributes=True) # allows .model_validate(from_orm_obj)
|
2342
|
+
|
2343
|
+
|
2344
|
+
@classmethod
|
2345
|
+
@cachedmethod(
|
2346
|
+
lambda cls: cls._filter_cache, # <- resolves to the real TTLCache
|
2347
|
+
lock=lambda cls: cls._cache_lock,
|
2348
|
+
key=lambda cls, **kw: _norm_kwargs(kw)
|
2349
|
+
)
|
2350
|
+
def filter(cls, **kwargs):
|
2351
|
+
# Delegate to your real filter (API/DB) only on cache miss
|
2352
|
+
return super().filter(**kwargs)
|
2353
|
+
|
2354
|
+
@classmethod
|
2355
|
+
@cachedmethod(
|
2356
|
+
lambda cls: cls._get_cache,
|
2357
|
+
lock=lambda cls: cls._cache_lock,
|
2358
|
+
key=lambda cls, **kw: _norm_kwargs(kw),
|
2359
|
+
)
|
2360
|
+
def get(cls, **kwargs):
|
2361
|
+
# e.g. get(name="CURVE__M_BONOS", project=None)
|
2362
|
+
return super().get(**kwargs)
|
2363
|
+
|
2364
|
+
@classmethod
|
2365
|
+
def get_value(cls,name:str,project_id:Optional[int]=None):
|
2366
|
+
return cls.get(name=name,project_id=project_id).value
|
2367
|
+
|
2368
|
+
@classmethod
|
2369
|
+
def invalidate_filter_cache(cls) -> None:
|
2370
|
+
cls._filter_cache.clear()
|
2371
|
+
|
2372
|
+
@classmethod
|
2373
|
+
def create_constants_if_not_exist(cls,constants_to_create: dict):
|
2374
|
+
# crete global constants if not exist in backed
|
2375
|
+
|
2376
|
+
# constants_to_create=dict(
|
2377
|
+
# TIIE_28_UID = "TIIE_28",
|
2378
|
+
# TIIE_91_UID = "TIIE_91",
|
2379
|
+
# TIIE_182_UID = "TIIE_182",
|
2380
|
+
# TIIE_OVERNIGHT_UID = "TIIE_OVERNIGHT",
|
2381
|
+
#
|
2382
|
+
# CETE_28_UID = "CETE_28",
|
2383
|
+
# CETE_91_UID = "CETE_91",
|
2384
|
+
# CETE_182_UID = "CETE_182",
|
2385
|
+
#
|
2386
|
+
# # Curve identifiers
|
2387
|
+
# TIIE_28_ZERO_CURVE = "F_TIIE_28_VALMER",
|
2388
|
+
# M_BONOS_ZERO_CURVE = "M_BONOS_ZERO_OTR",
|
2389
|
+
#
|
2390
|
+
#
|
2391
|
+
# DISCOUNT_CURVES_TABLE = "discount_curves",
|
2392
|
+
# REFERENCE_RATES_FIXING_TABLE = "fixing_rates_1d",
|
2393
|
+
# )
|
2394
|
+
existing_constants = cls.filter(name__in=list(constants_to_create.keys()))
|
2395
|
+
existing_constants_names = [c.name for c in existing_constants]
|
2396
|
+
constants_to_register = {k: v for k, v in constants_to_create.items() if k not in existing_constants_names}
|
2397
|
+
created_constants=[]
|
2398
|
+
for k, v in constants_to_register.items():
|
2399
|
+
new_constant = cls.create(name=k, value=v)
|
2400
|
+
created_constants.append(new_constant)
|
2401
|
+
return created_constants
|
2402
|
+
|
2275
2403
|
SessionDataSource = PodDataSource()
|
2276
2404
|
SessionDataSource.set_remote_db()
|
@@ -1 +1,2 @@
|
|
1
|
-
from .
|
1
|
+
from .constants import *
|
2
|
+
from .instruments import *
|
@@ -1,10 +1,22 @@
|
|
1
1
|
from .data_interface import DateInfo, MockDataInterface, MSInterface
|
2
|
-
from mainsequence.
|
2
|
+
from mainsequence.client import Constant as _C
|
3
|
+
|
4
|
+
import os
|
3
5
|
|
4
6
|
def _make_backend():
|
5
|
-
|
6
|
-
|
7
|
-
return MockDataInterface()
|
7
|
+
backend = os.getenv("MSI_DATA_BACKEND", "mock").lower()
|
8
|
+
return MSInterface() if backend == "mainsequence" else MockDataInterface()
|
8
9
|
|
9
10
|
# export a single, uniform instance
|
10
|
-
data_interface = _make_backend()
|
11
|
+
data_interface = _make_backend()
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
constants_to_create=dict(
|
16
|
+
|
17
|
+
|
18
|
+
DISCOUNT_CURVES_TABLE = "discount_curves",
|
19
|
+
REFERENCE_RATES_FIXING_TABLE = "fixing_rates_1d",
|
20
|
+
)
|
21
|
+
|
22
|
+
_C.create_constants_if_not_exist(constants_to_create)
|
@@ -2,12 +2,21 @@ import datetime
|
|
2
2
|
from typing import Dict, Optional, TypedDict, Any
|
3
3
|
import random
|
4
4
|
from mainsequence.instruments.utils import to_ql_date
|
5
|
+
import mainsequence.client as msc
|
5
6
|
import QuantLib as ql
|
6
7
|
import os
|
7
8
|
import pandas as pd
|
8
9
|
from pathlib import Path
|
9
10
|
|
10
11
|
|
12
|
+
DISCOUNT_CURVES_TABLE=msc.Constant.get_or_none(name="DISCOUNT_CURVES_TABLE")
|
13
|
+
REFERENCE_RATES_FIXING_TABLE = msc.Constant.get_or_none(name="REFERENCE_RATES_FIXING_TABLE")
|
14
|
+
|
15
|
+
assert DISCOUNT_CURVES_TABLE is not None, "DISCOUNT_CURVES_TABLE not found in constants"
|
16
|
+
assert REFERENCE_RATES_FIXING_TABLE is not None, "REFERENCE_RATES_FIXING_TABLE not found in constants"
|
17
|
+
|
18
|
+
|
19
|
+
|
11
20
|
class DateInfo(TypedDict, total=False):
|
12
21
|
"""Defines the date range for a data query."""
|
13
22
|
start_date: Optional[datetime.datetime]
|
@@ -278,7 +287,6 @@ class MSInterface():
|
|
278
287
|
@cachedmethod(cache=attrgetter("_curve_cache"), lock=attrgetter("_curve_cache_lock"))
|
279
288
|
def get_historical_discount_curve(self, curve_name, target_date):
|
280
289
|
from mainsequence.tdag import APIDataNode
|
281
|
-
from mainsequence.instruments.settings import DISCOUNT_CURVES_TABLE
|
282
290
|
data_node = APIDataNode.build_from_identifier(identifier=DISCOUNT_CURVES_TABLE)
|
283
291
|
|
284
292
|
|
@@ -12,6 +12,8 @@ from mainsequence.instruments.pricing_models.swap_pricer import get_swap_cashfl
|
|
12
12
|
from mainsequence.instruments.pricing_models.indices import get_index
|
13
13
|
from mainsequence.instruments.utils import to_ql_date, to_py_date
|
14
14
|
from .base_instrument import InstrumentModel
|
15
|
+
from mainsequence.instruments.pricing_models.indices import build_zero_curve
|
16
|
+
|
15
17
|
|
16
18
|
from .ql_fields import (
|
17
19
|
QuantLibPeriod as QPeriod,
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# instruments.toml — defaults for MainSequence Instruments
|
2
|
+
|
3
|
+
DISCOUNT_CURVES_TABLE = "discount_curves"
|
4
|
+
REFERENCE_RATES_FIXING_TABLE = "fixing_rates_1d"
|
5
|
+
|
6
|
+
TIIE_28_ZERO_CURVE = "F_TIIE_28_VALMER"
|
7
|
+
M_BONOS_ZERO_CURVE = "M_BONOS_ZERO_OTR"
|
8
|
+
|
9
|
+
TIIE_28_UID = "TIIE_28"
|
10
|
+
TIIE_91_UID = "TIIE_91"
|
11
|
+
TIIE_182_UID = "TIIE_182"
|
12
|
+
TIIE_OVERNIGHT_UID = "TIIE_OVERNIGHT"
|
13
|
+
|
14
|
+
CETE_28_UID = "CETE_28"
|
15
|
+
CETE_91_UID = "CETE_91"
|
16
|
+
CETE_182_UID = "CETE_182"
|
17
|
+
|
@@ -1,18 +1,17 @@
|
|
1
1
|
# pricing_models/indices.py
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
"""
|
4
|
-
Index factory for QuantLib (identifier-driven
|
4
|
+
Index factory for QuantLib (identifier-driven).
|
5
5
|
|
6
6
|
Usage
|
7
7
|
-----
|
8
8
|
>>> from datetime import date
|
9
|
-
>>> from mainsequence.instruments.settings import TIIE_28_UID
|
10
9
|
>>> from mainsequence.instruments.pricing_models.indices import get_index
|
11
|
-
>>> idx = get_index(
|
10
|
+
>>> idx = get_index("TIIE_28", target_date=date(2024, 6, 14))
|
12
11
|
|
13
12
|
You can also supply a forwarding curve handle:
|
14
13
|
>>> h = ql.RelinkableYieldTermStructureHandle()
|
15
|
-
>>> idx = get_index(
|
14
|
+
>>> idx = get_index("TIIE_28", target_date=date(2025, 9, 16), forwarding_curve=h)
|
16
15
|
|
17
16
|
Notes
|
18
17
|
-----
|
@@ -30,9 +29,8 @@ import QuantLib as ql
|
|
30
29
|
from functools import lru_cache
|
31
30
|
|
32
31
|
from mainsequence.instruments.data_interface import data_interface
|
33
|
-
from mainsequence.instruments import settings
|
34
32
|
from mainsequence.instruments.utils import to_py_date, to_ql_date
|
35
|
-
|
33
|
+
from mainsequence.client import Constant as _C
|
36
34
|
|
37
35
|
# ----------------------------- Cache (ONLY by identifier + date) ----------------------------- #
|
38
36
|
|
@@ -50,8 +48,8 @@ def clear_index_cache() -> None:
|
|
50
48
|
# No tenor tokens; we store the QuantLib Period directly.
|
51
49
|
|
52
50
|
INDEX_CONFIGS: Dict[str, Dict] = {
|
53
|
-
|
54
|
-
curve_uid=
|
51
|
+
_C.get_value(name="TIIE_28_UID"): dict(
|
52
|
+
curve_uid=_C.get_value(name="TIIE_28_ZERO_CURVE"),
|
55
53
|
calendar=(ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()),
|
56
54
|
day_counter=ql.Actual360(),
|
57
55
|
currency=(ql.MXNCurrency() if hasattr(ql, "MXNCurrency") else ql.USDCurrency()),
|
@@ -60,8 +58,8 @@ INDEX_CONFIGS: Dict[str, Dict] = {
|
|
60
58
|
bdc=ql.ModifiedFollowing,
|
61
59
|
end_of_month=False,
|
62
60
|
),
|
63
|
-
|
64
|
-
curve_uid=
|
61
|
+
_C.get_value(name="TIIE_91_UID"): dict(
|
62
|
+
curve_uid=_C.get_value(name="TIIE_28_ZERO_CURVE"),
|
65
63
|
calendar=(ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()),
|
66
64
|
day_counter=ql.Actual360(),
|
67
65
|
currency=ql.MXNCurrency(),
|
@@ -70,8 +68,8 @@ INDEX_CONFIGS: Dict[str, Dict] = {
|
|
70
68
|
bdc=ql.ModifiedFollowing,
|
71
69
|
end_of_month=False,
|
72
70
|
),
|
73
|
-
|
74
|
-
curve_uid=
|
71
|
+
_C.get_value(name="TIIE_182_UID"): dict(
|
72
|
+
curve_uid=_C.get_value(name="TIIE_28_ZERO_CURVE"),
|
75
73
|
calendar=(ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()),
|
76
74
|
day_counter=ql.Actual360(),
|
77
75
|
currency=ql.MXNCurrency(),
|
@@ -81,8 +79,8 @@ INDEX_CONFIGS: Dict[str, Dict] = {
|
|
81
79
|
end_of_month=False,
|
82
80
|
),
|
83
81
|
# Add more identifiers here as needed.
|
84
|
-
|
85
|
-
curve_uid=
|
82
|
+
_C.get_value(name="TIIE_OVERNIGHT_UID"): dict(
|
83
|
+
curve_uid=_C.get_value(name="TIIE_28_ZERO_CURVE"),
|
86
84
|
calendar=(ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()),
|
87
85
|
day_counter=ql.Actual360(),
|
88
86
|
currency=ql.MXNCurrency(),
|
@@ -91,8 +89,8 @@ INDEX_CONFIGS: Dict[str, Dict] = {
|
|
91
89
|
bdc=ql.ModifiedFollowing,
|
92
90
|
end_of_month=False,
|
93
91
|
),
|
94
|
-
|
95
|
-
curve_uid=
|
92
|
+
_C.get_value(name="CETE_28_UID"): dict(
|
93
|
+
curve_uid=_C.get_value(name="M_BONOS_ZERO_CURVE"),
|
96
94
|
calendar=(ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()),
|
97
95
|
day_counter=ql.Actual360(), # BONOS accrue on Act/360
|
98
96
|
currency=ql.MXNCurrency(),
|
@@ -101,9 +99,19 @@ INDEX_CONFIGS: Dict[str, Dict] = {
|
|
101
99
|
bdc=ql.Following, # “next banking business day” => Following
|
102
100
|
end_of_month=False, # Irrelevant when scheduling by days
|
103
101
|
),
|
102
|
+
_C.get_value(name="CETE_91_UID"): dict(
|
103
|
+
curve_uid=_C.get_value(name="M_BONOS_ZERO_CURVE"),
|
104
|
+
calendar=(ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()),
|
105
|
+
day_counter=ql.Actual360(), # BONOS accrue on Act/360
|
106
|
+
currency=ql.MXNCurrency(),
|
107
|
+
period=ql.Period(91, ql.Days), # Coupons every 28 days
|
108
|
+
settlement_days=1, # T+1 in Mexico since May 27–28, 2024
|
109
|
+
bdc=ql.Following, # “next banking business day” => Following
|
110
|
+
end_of_month=False, # Irrelevant when scheduling by days
|
111
|
+
),
|
104
112
|
|
105
|
-
|
106
|
-
curve_uid=
|
113
|
+
_C.get_value(name="CETE_182_UID"): dict(
|
114
|
+
curve_uid=_C.get_value(name="M_BONOS_ZERO_CURVE"),
|
107
115
|
calendar=(ql.Mexico() if hasattr(ql, "Mexico") else ql.TARGET()),
|
108
116
|
day_counter=ql.Actual360(), # BONOS accrue on Act/360
|
109
117
|
currency=ql.MXNCurrency(),
|
@@ -7,6 +7,8 @@ import sys
|
|
7
7
|
from pathlib import Path
|
8
8
|
from types import SimpleNamespace
|
9
9
|
|
10
|
+
|
11
|
+
HERE = Path(__file__).resolve().parent
|
10
12
|
# ---------------- App identity & app dirs ----------------
|
11
13
|
APP_VENDOR = "mainsequence"
|
12
14
|
APP_NAME = "instruments"
|
@@ -15,6 +17,8 @@ APP_ID = f"{APP_VENDOR}/{APP_NAME}"
|
|
15
17
|
# All environment variables use this prefix now.
|
16
18
|
ENV_PREFIX = "MSI" # e.g., MSI_CONFIG_FILE, MSI_DATA_BACKEND
|
17
19
|
ENV_CONFIG_FILE = f"{ENV_PREFIX}_CONFIG_FILE"
|
20
|
+
ENV_DEFAULT_TOML_FILE = f"{ENV_PREFIX}_DEFAULT_TOML_FILE"
|
21
|
+
|
18
22
|
|
19
23
|
def _user_config_root() -> Path:
|
20
24
|
if sys.platform == "win32":
|
@@ -62,31 +66,22 @@ def _load_file_config() -> dict:
|
|
62
66
|
pass
|
63
67
|
return {}
|
64
68
|
|
65
|
-
# ---------------- default TOML (
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
CETE_182_UID = "CETE_182"
|
82
|
-
|
83
|
-
[data]
|
84
|
-
backend = "mock"
|
85
|
-
|
86
|
-
[files]
|
87
|
-
tiie_zero_csv = ""
|
88
|
-
tiie28_fixings_csv = ""
|
89
|
-
"""
|
69
|
+
# ---------------- default TOML (read from a real file next to this module) -----
|
70
|
+
def _read_default_toml_text() -> str | None:
|
71
|
+
"""
|
72
|
+
Returns the text of the default TOML configuration.
|
73
|
+
Order of precedence:
|
74
|
+
1) Path from MSI_DEFAULT_TOML_FILE (if set)
|
75
|
+
2) instruments.default.toml next to this file
|
76
|
+
Returns None if no default file is found/readable.
|
77
|
+
"""
|
78
|
+
candidate = Path(os.getenv(ENV_DEFAULT_TOML_FILE, HERE / "instruments.default.toml")).expanduser()
|
79
|
+
try:
|
80
|
+
if candidate.exists():
|
81
|
+
return candidate.read_text(encoding="utf-8")
|
82
|
+
except Exception:
|
83
|
+
pass
|
84
|
+
return None
|
90
85
|
|
91
86
|
def _existing_config_path() -> Path | None:
|
92
87
|
env_cfg = os.getenv(ENV_CONFIG_FILE)
|
@@ -108,11 +103,15 @@ def _ensure_default_config_file() -> Path | None:
|
|
108
103
|
"""If no config exists anywhere, create one. Never overwrites existing."""
|
109
104
|
if _existing_config_path() is not None:
|
110
105
|
return None
|
106
|
+
default_text = _read_default_toml_text()
|
107
|
+
if default_text is None:
|
108
|
+
return None
|
109
|
+
|
111
110
|
target = Path(os.getenv(ENV_CONFIG_FILE, APP_ROOT / "config.toml")).expanduser()
|
112
111
|
try:
|
113
112
|
target.parent.mkdir(parents=True, exist_ok=True) # ensure parent dir only
|
114
113
|
if not target.exists():
|
115
|
-
target.write_text(
|
114
|
+
target.write_text(default_text, encoding="utf-8")
|
116
115
|
except Exception:
|
117
116
|
return None
|
118
117
|
return target
|
@@ -2,15 +2,15 @@ mainsequence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
mainsequence/__main__.py,sha256=1_gCQd-MlXQxAubgVdTDgmP2117UQmj5DvTI_PASZis,162
|
3
3
|
mainsequence/logconf.py,sha256=OLkhALon2uIDZSeGOwX3DneapkVqlwDjqJm96TRAhgA,10057
|
4
4
|
mainsequence/cli/__init__.py,sha256=TNxXsTPgb52OhakIda9wTRh91cqoBqgQRx5TxjzQQFU,21
|
5
|
-
mainsequence/cli/api.py,sha256=
|
6
|
-
mainsequence/cli/cli.py,sha256=
|
7
|
-
mainsequence/cli/config.py,sha256=
|
5
|
+
mainsequence/cli/api.py,sha256=mlBkpq_WF_MDn6u5G0MVAbBw2INLVd5Pl7g92bKaTG0,7377
|
6
|
+
mainsequence/cli/cli.py,sha256=EE9oHiCvx6FeuzAYg7Yq178rddddmXiTVBOVILKApnI,17016
|
7
|
+
mainsequence/cli/config.py,sha256=UAi0kfxro2oJhzaRdd2D3F72eGpqkYaWjSsCzBrb2LY,2705
|
8
8
|
mainsequence/cli/ssh_utils.py,sha256=95CmZk2nIoJhNTCmifMEsE9QXJrXY3zvfxLQp3QpdiM,5088
|
9
|
-
mainsequence/client/__init__.py,sha256=
|
10
|
-
mainsequence/client/base.py,sha256=
|
9
|
+
mainsequence/client/__init__.py,sha256=5aDPrdS9GVebgQ52yYFre1H14n8pMrEEo0OVDZH0zvw,878
|
10
|
+
mainsequence/client/base.py,sha256=yY8dar55QXKkN-g0ohw1jQEboZx5ZnNsnE01xwji93Q,15036
|
11
11
|
mainsequence/client/models_helpers.py,sha256=QXu-EZK54ryszVbjREwb7qRrS2WPvn_voboptPTR6kM,4100
|
12
12
|
mainsequence/client/models_report_studio.py,sha256=mKu7k0STyUZMTzTK98w46t2b1jv8hVhDpmCzI4EeSnQ,13971
|
13
|
-
mainsequence/client/models_tdag.py,sha256=
|
13
|
+
mainsequence/client/models_tdag.py,sha256=n8IK3tjiNqAlYSae_iVAfY45M0mts9EgWEjnaRger2M,94938
|
14
14
|
mainsequence/client/models_vam.py,sha256=E78QsdDNszpgweVvGUY5QF3DDOtSjJNZeVXvast2BjU,72891
|
15
15
|
mainsequence/client/utils.py,sha256=4aLctRMmQdFtLzaI9b28ifP141pO-VAmiQ7wTdHpqGg,11665
|
16
16
|
mainsequence/client/data_sources_interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -27,16 +27,17 @@ mainsequence/dashboards/streamlit/core/theme.py,sha256=Gc2lIUDugogTSGgBLn6nL3PDJ
|
|
27
27
|
mainsequence/dashboards/streamlit/pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
28
|
mainsequence/instrumentation/__init__.py,sha256=qfHOseVGOW_WdoTgw7TWZYpF1REwf14eF9awxWcLtfI,154
|
29
29
|
mainsequence/instrumentation/utils.py,sha256=XgVg9FijubSy5qMNw0yCkl5TMD2n56TnT4Q58WfZXbw,3354
|
30
|
-
mainsequence/instruments/__init__.py,sha256=
|
31
|
-
mainsequence/instruments/
|
30
|
+
mainsequence/instruments/__init__.py,sha256=RZzyEw7P9mcNKhTd8G1a59xdDBEgUgtSLCuac49svtA,52
|
31
|
+
mainsequence/instruments/instruments.default.toml,sha256=RnCWtS7YJ4t_JqTHJWUdvJhEf-SkADsnQzAyhJZTcXA,452
|
32
|
+
mainsequence/instruments/settings.py,sha256=Jtwg9rO42uJ7DOL6HkW8RPgkDxE-G2YDuw0QcU8itrA,5857
|
32
33
|
mainsequence/instruments/utils.py,sha256=eiqaTfcq4iulSdEwgi1sMq8xAKK8ONl22a-FNItcbYk,698
|
33
|
-
mainsequence/instruments/data_interface/__init__.py,sha256=
|
34
|
-
mainsequence/instruments/data_interface/data_interface.py,sha256=
|
34
|
+
mainsequence/instruments/data_interface/__init__.py,sha256=Mz-Xx-qLNwI-iIJGL2ik-veUZuraU4aaDyUKhcOXsLg,548
|
35
|
+
mainsequence/instruments/data_interface/data_interface.py,sha256=Xrvm8GQgALSTxt4vWK888J49X7pXisiitB2DTu9KnCg,15229
|
35
36
|
mainsequence/instruments/instruments/__init__.py,sha256=Xe1hCMsOGc3IyyfA90Xuv7pO9t8TctpM7fo-6hML9Cg,154
|
36
37
|
mainsequence/instruments/instruments/base_instrument.py,sha256=XQeZxDSGhZ_ZNut3867I_q3UvDvp9VZDW4VRzyyFe-A,3185
|
37
38
|
mainsequence/instruments/instruments/bond.py,sha256=tm1Xr-OX2kbAzEZin5qbFBPhWyrFNUjLf7aWVQ6FTzg,17432
|
38
39
|
mainsequence/instruments/instruments/european_option.py,sha256=amyl52DsgOSMDiMZn4CbmWA4QGfVn38ojVVMhdOLKXM,2760
|
39
|
-
mainsequence/instruments/instruments/interest_rate_swap.py,sha256=
|
40
|
+
mainsequence/instruments/instruments/interest_rate_swap.py,sha256=GJLi4yiIZNwjASLiOnEGw2owCh-v4NDQdcONNWS-wGs,7893
|
40
41
|
mainsequence/instruments/instruments/json_codec.py,sha256=94sOEL2N7nPMUsleB3GD3FzwYy5sVYsgthvQ2g8u9gE,22070
|
41
42
|
mainsequence/instruments/instruments/knockout_fx_option.py,sha256=JuRdBOOjrlLazH_Q98jTfTv7tA1nvG4QZHDkTgGlAnw,6115
|
42
43
|
mainsequence/instruments/instruments/position.py,sha256=i8aYyPET1gipw7rgsJtZ4FZZ5EtFccu6DGI6yB_vKp8,19351
|
@@ -46,7 +47,7 @@ mainsequence/instruments/pricing_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
46
47
|
mainsequence/instruments/pricing_models/black_scholes.py,sha256=bs7fsgiDKRuBZo7fvemVdmUHN-NjC2v6XUjhPaLNC-4,1588
|
47
48
|
mainsequence/instruments/pricing_models/bond_pricer.py,sha256=JXBOsDonlLwYG3GyHGD7rV1cddCPot8ZgE1Th-tHiqY,6566
|
48
49
|
mainsequence/instruments/pricing_models/fx_option_pricer.py,sha256=cMRejaIo3ZEhKCS960C7lg8ejh3b9RqIfL3Hhj4LB0A,3292
|
49
|
-
mainsequence/instruments/pricing_models/indices.py,sha256=
|
50
|
+
mainsequence/instruments/pricing_models/indices.py,sha256=20FgSKeWu7L2bpAZ1zklUnJ6SOi1BJk7yt1WfKPfDLg,12923
|
50
51
|
mainsequence/instruments/pricing_models/knockout_fx_pricer.py,sha256=FrO1uRn63zktkm6LwfLKZf0vPn5x0NOCBIPfSPEIO9c,6772
|
51
52
|
mainsequence/instruments/pricing_models/swap_pricer.py,sha256=vQXL1pgWPS9EUu8xXDyrA6QZm-t7wH0Hx3GU3yl1HHw,17994
|
52
53
|
mainsequence/reportbuilder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -103,9 +104,9 @@ mainsequence/virtualfundbuilder/resource_factory/app_factory.py,sha256=XSZo9ImdT
|
|
103
104
|
mainsequence/virtualfundbuilder/resource_factory/base_factory.py,sha256=jPXdK2WCaNw3r6kP3sZGIL7M4ygfIMs8ek3Yq4QYQZg,9434
|
104
105
|
mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py,sha256=ysEeJrlbxrMPA7wFw7KDtuCTzXYkdfYZuxUFpPPY7vE,3732
|
105
106
|
mainsequence/virtualfundbuilder/resource_factory/signal_factory.py,sha256=ywa7vxxLlQopuRwwRKyj866ftgaj8uKVoiPQ9YJ2IIo,7198
|
106
|
-
mainsequence-2.0.
|
107
|
-
mainsequence-2.0.
|
108
|
-
mainsequence-2.0.
|
109
|
-
mainsequence-2.0.
|
110
|
-
mainsequence-2.0.
|
111
|
-
mainsequence-2.0.
|
107
|
+
mainsequence-2.0.4.dist-info/licenses/LICENSE,sha256=fXoCKgEuZXmP84_QDXpvO18QHze_6AAhd-zvZBUjJxQ,4524
|
108
|
+
mainsequence-2.0.4.dist-info/METADATA,sha256=E0-idLtngCwc20lVgtUq3DA_oYC1iMlyGH36WZ2Q8kM,8028
|
109
|
+
mainsequence-2.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
110
|
+
mainsequence-2.0.4.dist-info/entry_points.txt,sha256=2J8TprrUndh7AttNTlXAaxgGtkXFUAHgXs-M7DCj5MU,58
|
111
|
+
mainsequence-2.0.4.dist-info/top_level.txt,sha256=uSLD9rXMDMN0cc1x0p808bwyQMoRmYY2pdQZEWLajX8,13
|
112
|
+
mainsequence-2.0.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|