Skip to content

Commit 1930b85

Browse files
committed
fix: manual upgrade to python 3.12
1 parent faddaf4 commit 1930b85

File tree

4 files changed

+117
-130
lines changed

4 files changed

+117
-130
lines changed

pyproject.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ packages = ["src/dacos"]
3434
strict = true
3535
disallow_any_unimported = true
3636
disallow_any_decorated = false
37-
disallow_any_explicit = true
37+
disallow_any_explicit = false
3838
disallow_any_generics = true
3939
disallow_subclassing_any = true
4040
disallow_untyped_calls = true
@@ -45,7 +45,17 @@ warn_redundant_casts = true
4545
warn_unused_ignores = true
4646
warn_return_any = true
4747
no_implicit_optional = true
48-
warn_unreachable = true
48+
disallow_untyped_decorators = false
49+
warn_unreachable = false
50+
51+
[[tool.mypy.overrides]]
52+
module = [
53+
"numba.*",
54+
"scipy.*",
55+
"statsmodels.*",
56+
"llvmlite.*"
57+
]
58+
ignore_missing_imports = true
4959

5060
[tool.ruff]
5161
line-length = 120

src/dacos/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
__version__ = "0.1.1"
1414

15+
1516
# ============================================================================
1617
# 1. SUBMODULE EXPORTS
1718
# ============================================================================
@@ -31,6 +32,7 @@
3132
# 3. BUILDER & CORE PIPELINE
3233
# ============================================================================
3334
from .builder import execute_etl_pipeline
35+
from .config import StatArbConfig, TSMConfig
3436
from .core import (
3537
compute_pca_safe,
3638
ingest_silver_data,
@@ -95,6 +97,8 @@
9597
"paradigms",
9698
"protocols",
9799
"utils",
100+
"StatArbConfig",
101+
"TSMConfig",
98102
# Public API Endpoints
99103
"run_stat_arb_research",
100104
"run_tsm_research",

src/dacos/api.py

Lines changed: 94 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,190 +1,151 @@
11
"""
2-
This module exposes the main 4 endpoints for the Dacos library.
3-
No mathematical business logic is allowed here.
2+
DACOS PUBLIC API (THE CONDUCTOR)
3+
Location: src/dacos/api.py
4+
Paradigm: Separation of Concerns, Railway Oriented Programming (ROP), 100% Monadic Boundary.
45
"""
56

67
from __future__ import annotations
78

89
import logging
9-
from typing import Any
10+
from typing import Any, cast
1011

1112
import numpy as np
1213
import polars as pl
1314

1415
from dacos.config import StatArbConfig, TSMConfig
15-
16-
# Import Engines
17-
from dacos.paradigms import compute_pairs_zscore
18-
19-
# Import Tactics
20-
from dacos.paradigms.stat_arb.tactics import apply_mean_reversion_tactics_strict
21-
from dacos.paradigms.tsm.engine import compute_tsm_indicators
22-
from dacos.paradigms.tsm.tactics import apply_momentum_tactics_strict
16+
from dacos.paradigms import (
17+
apply_mean_reversion_tactics_strict,
18+
apply_momentum_tactics_strict,
19+
compute_pairs_zscore,
20+
compute_tsm_indicators,
21+
)
2322
from dacos.protocols import DataFrame
2423
from dacos.utils import Err, Ok, Result
2524

26-
# Asumsi fungsi ingestion tersedia (sesuaikan dengan struktur file Anda)
27-
# from dacos.core.data import ingest_silver_data
28-
29-
# ============================================================================
30-
# EXECUTIVE LOGGER CONFIGURATION
31-
# ============================================================================
3225
logger = logging.getLogger(__name__)
3326

34-
3527
# ============================================================================
36-
# PILAR 2: THE VECTORIZED RAILWAY (RESEARCH MODE)
28+
# PILAR 1: THE VECTORIZED RAILWAY (RESEARCH MODE)
3729
# ============================================================================
3830

39-
4031
def run_stat_arb_research(
41-
aligned_data: DataFrame, # Data yang sudah melalui ingest_silver_data & sejajar
32+
aligned_data: DataFrame,
4233
target_symbol: str,
4334
anchor_symbol: str,
4435
hedge_ratio_beta: float,
4536
config: StatArbConfig | None = None,
4637
) -> Result[DataFrame, ValueError]:
47-
"""
48-
(Mode 1) Executes a full vectorized backtest pipeline for Statistical Arbitrage.
49-
Args:
50-
aligned_data: Polars DataFrame containing aligned 'target' and 'anchor' price columns.
51-
target_symbol: The coin being traded (Y).
52-
anchor_symbol: The hedge coin (X).
53-
hedge_ratio_beta: The cointegration multiplier binding X to Y.
54-
config: Immutable StatArb configuration. Defaults to StatArbConfig().
55-
56-
Returns:
57-
Ok(DataFrame) matching STAT_ARB_SIGNAL_SCHEMA, or Err(ValueError).
58-
"""
5938
config = config or StatArbConfig()
39+
prefix = f"[STAT-ARB | RESEARCH | {target_symbol}]"
6040

61-
try:
62-
logger.info(f"START [StatArb Research]: Evaluating {target_symbol} vs {anchor_symbol}")
41+
if not isinstance(aligned_data, pl.DataFrame) or aligned_data.is_empty():
42+
return Err(ValueError(f"{prefix} API Rejected: Empty or invalid DataFrame provided."))
6343

64-
# 1. Stasiun Mesin (Engines)
65-
logger.info("--> Entering Engine Station: Computing Z-Scores...")
66-
engine_res = compute_pairs_zscore(
44+
try:
45+
engine_step = compute_pairs_zscore(
6746
aligned_data=aligned_data,
6847
target_column=target_symbol,
6948
anchor_column=anchor_symbol,
7049
hedge_ratio_beta=hedge_ratio_beta,
71-
z_score_rolling_window=config.z_window,
50+
z_score_rolling_window=config.z_window
7251
)
73-
if engine_res.is_err():
74-
logger.error(f"Engine Failure: {engine_res.unwrap_err()}")
75-
return engine_res
52+
if engine_step.is_err():
53+
return Err(engine_step.unwrap_err()) # Propagate explicitly
7654

77-
# 2. Stasiun Taktik & Bea Cukai (Tactics & Schema Enforcement)
78-
logger.info("--> Entering Tactics Station: Translating continuous metrics to discrete signals...")
79-
tactics_res = apply_mean_reversion_tactics_strict(data=engine_res.unwrap(), symbol=target_symbol, config=config)
80-
if tactics_res.is_err():
81-
logger.error(f"Tactics Failure: {tactics_res.unwrap_err()}")
82-
return tactics_res
55+
tactics_step = apply_mean_reversion_tactics_strict(
56+
data=engine_step.unwrap(),
57+
symbol=target_symbol,
58+
config=config
59+
)
60+
if tactics_step.is_err():
61+
return Err(tactics_step.unwrap_err())
8362

84-
logger.info(f"SUCCESS [StatArb Research]: Pipeline completed for {target_symbol}.")
85-
return Ok(tactics_res.unwrap())
63+
# FIX MYPY: Beri tahu MyPy secara eksplisit bahwa hasilnya adalah DataFrame
64+
final_df = cast(pl.DataFrame, tactics_step.unwrap())
65+
return Ok(final_df)
8666

8767
except Exception as e:
88-
logger.error(f"PANIC [StatArb Research]: Unhandled exception breached the pipeline: {e}")
89-
return Err(ValueError(f"StatArb Pipeline Panic: {e}"))
68+
logger.exception(f"{prefix} UNHANDLED PANIC.")
69+
return Err(ValueError(f"StatArb Pipeline Panic: {str(e)}"))
9070

9171

9272
def run_tsm_research(
93-
silver_data: DataFrame, # Data yang sudah melalui ingest_silver_data
73+
silver_data: DataFrame,
9474
target_symbol: str,
9575
config: TSMConfig | None = None,
9676
) -> Result[DataFrame, ValueError]:
97-
"""
98-
(Mode 1) Executes a full vectorized backtest pipeline for Time Series Momentum (CTA).
99-
100-
Args:
101-
silver_data: Polars DataFrame containing standard OHLCV columns.
102-
target_symbol: The asset being evaluated.
103-
config: Immutable TSM configuration. Defaults to TSMConfig().
104-
105-
Returns:
106-
Ok(DataFrame) matching TSM_SIGNAL_SCHEMA, or Err(ValueError).
107-
"""
10877
config = config or TSMConfig()
78+
prefix = f"[TSM | RESEARCH | {target_symbol}]"
10979

110-
try:
111-
logger.info(f"START [TSM Research]: Evaluating {target_symbol}")
80+
if not isinstance(silver_data, pl.DataFrame) or silver_data.is_empty():
81+
return Err(ValueError(f"{prefix} API Rejected: Empty or invalid DataFrame provided."))
11282

113-
# 1. Stasiun Mesin (Engines)
114-
logger.info("--> Entering Engine Station: Computing Donchian Channels & ATR...")
115-
engine_res = compute_tsm_indicators(
116-
data=silver_data, atr_window=config.atr_window, donchian_window=config.donchian_window
83+
try:
84+
engine_step = compute_tsm_indicators(
85+
data=silver_data,
86+
atr_window=config.atr_window,
87+
donchian_window=config.donchian_window
11788
)
118-
if engine_res.is_err():
119-
logger.error(f"Engine Failure: {engine_res.unwrap_err()}")
120-
return engine_res
89+
if engine_step.is_err():
90+
return Err(engine_step.unwrap_err())
12191

122-
# 2. Stasiun Taktik & Bea Cukai (Tactics & Schema Enforcement)
123-
logger.info("--> Entering Tactics Station: Applying Breakout & Risk Parity sizing...")
124-
tactics_res = apply_momentum_tactics_strict(
125-
data=engine_res.unwrap(), target_symbol=target_symbol, config=config
92+
tactics_step = apply_momentum_tactics_strict(
93+
data=engine_step.unwrap(),
94+
target_symbol=target_symbol,
95+
config=config
12696
)
127-
if tactics_res.is_err():
128-
logger.error(f"Tactics Failure: {tactics_res.unwrap_err()}")
129-
return tactics_res
97+
if tactics_step.is_err():
98+
return Err(tactics_step.unwrap_err())
13099

131-
logger.info(f"SUCCESS [TSM Research]: Pipeline completed for {target_symbol}.")
132-
return Ok(tactics_res.unwrap())
100+
# FIX MYPY: Beri tahu MyPy secara eksplisit bahwa hasilnya adalah DataFrame
101+
final_df = cast(pl.DataFrame, tactics_step.unwrap())
102+
return Ok(final_df)
133103

134104
except Exception as e:
135-
logger.error(f"PANIC [TSM Research]: Unhandled exception breached the pipeline: {e}")
136-
return Err(ValueError(f"TSM Pipeline Panic: {e}"))
105+
logger.exception(f"{prefix} UNHANDLED PANIC.")
106+
return Err(ValueError(f"TSM Pipeline Panic: {str(e)}"))
137107

138108

139109
# ============================================================================
140110
# PILAR 2: THE LIVE TICK PIPELINE (EXECUTION MODE)
141111
# ============================================================================
142112

143-
144113
def evaluate_stat_arb_live(
145114
live_buffer: DataFrame | dict[str, Any],
146115
target_symbol: str,
147116
anchor_symbol: str,
148117
hedge_ratio_beta: float,
149118
config: StatArbConfig | None = None,
150119
) -> Result[dict[str, Any], ValueError]:
151-
"""
152-
(Mode 2) High-speed evaluation of a single live tick/buffer for Statistical Arbitrage.
153-
Bypasses Ingestion and Cointegration Testing.
154-
155-
Args:
156-
live_buffer: Small DataFrame or Dict containing the recent lookback window.
157-
target_symbol: The coin being traded (Y).
158-
anchor_symbol: The hedge coin (X).
159-
hedge_ratio_beta: The pre-calculated beta from the research phase.
160-
config: Immutable StatArb configuration.
161-
162-
Returns:
163-
Ok(Dict) containing exactly one actionable signal matching STAT_ARB_SIGNAL_SCHEMA.
164-
"""
165-
166120
config = config or StatArbConfig()
167121

168122
try:
169-
# SURGERY: Mesin StatArb Polars mewajibkan DataFrame. Casting dict (20 rows) membutuhkan < 1ms.
170123
df_buffer = pl.DataFrame(live_buffer) if isinstance(live_buffer, dict) else live_buffer
171124

172125
engine_step = compute_pairs_zscore(
173126
aligned_data=df_buffer,
174127
target_column=target_symbol,
175128
anchor_column=anchor_symbol,
176129
hedge_ratio_beta=hedge_ratio_beta,
177-
z_score_rolling_window=config.z_window,
130+
z_score_rolling_window=config.z_window
178131
)
179132
if engine_step.is_err():
180-
return engine_step
133+
return Err(engine_step.unwrap_err())
181134

182-
# Ambil baris terujung (Latest Tick) dan suapkan ke Tactics Mode 2 (Dict)
183135
latest_tick = engine_step.unwrap().row(-1, named=True)
184-
return apply_mean_reversion_tactics_strict(data=latest_tick, symbol=target_symbol, config=config)
136+
tactics_res = apply_mean_reversion_tactics_strict(
137+
data=latest_tick,
138+
symbol=target_symbol,
139+
config=config
140+
)
141+
if tactics_res.is_err():
142+
return Err(tactics_res.unwrap_err())
143+
144+
# FIX MYPY: Casting eksplisit ke Dict
145+
final_dict = cast(dict[str, Any], tactics_res.unwrap())
146+
return Ok(final_dict)
185147

186148
except Exception as e:
187-
logger.error(f"[STAT-ARB | LIVE | {target_symbol}] Panic: {e}")
188149
return Err(ValueError(f"StatArb Live Panic: {str(e)}"))
189150

190151

@@ -193,46 +154,52 @@ def evaluate_tsm_live(
193154
target_symbol: str,
194155
config: TSMConfig | None = None,
195156
) -> Result[dict[str, Any], ValueError]:
196-
"""(Mode 2) High-speed evaluation of a single live tick/buffer for Time Series Momentum."""
197157
config = config or TSMConfig()
198158

199159
try:
200160
engine_step = compute_tsm_indicators(
201-
data=live_buffer, atr_window=config.atr_window, donchian_window=config.donchian_window
161+
data=live_buffer,
162+
atr_window=config.atr_window,
163+
donchian_window=config.donchian_window
202164
)
203165
if engine_step.is_err():
204-
return engine_step
166+
return Err(engine_step.unwrap_err())
205167

206168
engine_data = engine_step.unwrap()
207169

208-
# SURGERY: Rekonstruksi Dict jika data dari Numba (yang tidak membawa timestamp)
209-
if isinstance(live_buffer, dict):
210-
# Ambil elemen terakhir dari array timestamp/closets
211-
ts = (
212-
live_buffer["timestamp"][-1]
213-
if isinstance(live_buffer["timestamp"], list | np.ndarray)
214-
else live_buffer["timestamp"]
215-
)
216-
cl = (
217-
live_buffer["close"][-1]
218-
if isinstance(live_buffer["close"], list | np.ndarray)
219-
else live_buffer["close"]
220-
)
170+
# FIX MYPY: Evaluasi `engine_data` secara eksplisit agar MyPy paham itu Dict atau DataFrame
171+
if isinstance(engine_data, dict):
172+
# Casting aman karena jika masuk sini, live_buffer pasti dict yang punya list/ndarray
173+
ts_list = cast(Any, live_buffer)["timestamp"]
174+
cl_list = cast(Any, live_buffer)["close"]
175+
176+
ts = ts_list[-1] if isinstance(ts_list, list | np.ndarray) else ts_list
177+
cl = cl_list[-1] if isinstance(cl_list, list | np.ndarray) else cl_list
221178

222179
engine_data["timestamp"] = ts
223180
engine_data["close"] = cl
224181
latest_tick = engine_data
225-
226-
else:
182+
elif isinstance(engine_data, pl.DataFrame):
227183
latest_tick = engine_data.row(-1, named=True)
184+
else:
185+
return Err(ValueError("Engine returned invalid data type."))
228186

229-
return apply_momentum_tactics_strict(data=latest_tick, target_symbol=target_symbol, config=config)
187+
tactics_res = apply_momentum_tactics_strict(
188+
data=latest_tick,
189+
target_symbol=target_symbol,
190+
config=config
191+
)
192+
193+
if tactics_res.is_err():
194+
return Err(tactics_res.unwrap_err())
195+
196+
# FIX MYPY: Casting eksplisit ke Dict
197+
final_dict = cast(dict[str, Any], tactics_res.unwrap())
198+
return Ok(final_dict)
230199

231200
except Exception as e:
232-
logger.error(f"[TSM | LIVE | {target_symbol}] Panic: {e}")
233201
return Err(ValueError(f"TSM Live Panic: {str(e)}"))
234202

235-
236203
__all__ = [
237204
"run_stat_arb_research",
238205
"run_tsm_research",

src/dacos/config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,11 @@ class TSMConfig:
4545

4646
donchian_window: int = 20
4747
atr_window: int = 14
48-
target_risk_pct: float = 0.01 # 1% risk sizing
48+
target_risk_pct: float = 0.01
4949
allow_short: bool = True
50+
51+
52+
__all__ = [
53+
"StatArbConfig",
54+
"TSMConfig",
55+
]

0 commit comments

Comments
 (0)