Skip to content

Commit 4c56c54

Browse files
committed
fix: resolve all ruff violations in data/ and models/ modules
- Replace bare except with except (ValueError, KeyError) in fetcher.py - Add `from None` to re-raised ImportError in alpaca_ws.py and polygon.py to satisfy B904 (explicit exception chaining) - Remove unused StockBarsRequest top-level import in alpaca.py adapter (already imported lazily inside the method that uses it) - Remove unused IBKRSettings import in registry.py - Rename ambiguous loop variable `l` to `loss` in incremental_features.py - Apply ruff auto-fix formatting to newly tracked data/ and models/ files
1 parent 2b76f39 commit 4c56c54

46 files changed

Lines changed: 1639 additions & 1607 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/quant_pod/crews/trading_crew.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,9 +412,11 @@ def prepare_inputs(self, inputs: dict[str, Any]) -> dict[str, Any]:
412412
f"{envelope.asset_class}/{envelope.instrument_type}:{envelope.task_intent}"
413413
)
414414

415-
# Set defaults
415+
# Set defaults — always stringify: CrewAI template interpolation rejects date objects
416416
if "current_date" not in inputs or inputs["current_date"] is None:
417-
inputs["current_date"] = date.today()
417+
inputs["current_date"] = str(date.today())
418+
else:
419+
inputs["current_date"] = str(inputs["current_date"])
418420

419421
if "regime" not in inputs or inputs["regime"] is None:
420422
inputs["regime"] = {
@@ -803,7 +805,7 @@ def run_trading_analysis(
803805
# normalization, envelope construction, and default-setting.
804806
inputs = {
805807
"symbol": symbol,
806-
"current_date": current_date or date.today(),
808+
"current_date": str(current_date or date.today()),
807809
"regime": regime or {"trend": "unknown", "volatility": "normal", "confidence": 0.5},
808810
"portfolio": portfolio or {},
809811
"historical_context": historical_context,
@@ -830,7 +832,7 @@ def run_analysis_only(
830832
crew = TradingCrew()
831833
inputs = {
832834
"symbol": symbol,
833-
"current_date": current_date or date.today(),
835+
"current_date": str(current_date or date.today()),
834836
"regime": regime or {"trend": "unknown", "volatility": "normal", "confidence": 0.5},
835837
"portfolio": portfolio or {},
836838
"historical_context": historical_context,

packages/quant_pod/data/adapters/alpaca_ws.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,13 @@
4040
import asyncio
4141
import json
4242
import os
43-
from datetime import datetime, timezone
44-
from typing import List, Optional
43+
from datetime import UTC, datetime
4544

4645
from loguru import logger
4746

4847
from quant_pod.data.market_data_bus import MarketDataBus
4948
from quant_pod.execution.tick_executor import Tick
5049

51-
5250
# Alpaca streaming endpoints
5351
_FEED_URLS = {
5452
"iex": "wss://stream.data.alpaca.markets/v2/iex",
@@ -74,9 +72,9 @@ class AlpacaWebSocketBus(MarketDataBus):
7472

7573
def __init__(
7674
self,
77-
symbols: List[str],
78-
api_key: Optional[str] = None,
79-
api_secret: Optional[str] = None,
75+
symbols: list[str],
76+
api_key: str | None = None,
77+
api_secret: str | None = None,
8078
feed: str = "iex",
8179
):
8280
super().__init__(symbols)
@@ -133,7 +131,7 @@ async def _connect_and_stream(self, tick_queue: asyncio.Queue) -> None:
133131
raise ImportError(
134132
"websockets package required for AlpacaWebSocketBus. "
135133
"Install with: pip install 'websockets>=12'"
136-
)
134+
) from None
137135

138136
async with websockets.connect(
139137
self._url,
@@ -181,7 +179,7 @@ def _check_auth(self, response: list) -> None:
181179
return
182180
# No explicit error and no explicit success — assume ok (some versions omit)
183181

184-
def _parse_message(self, msg: dict) -> Optional[Tick]:
182+
def _parse_message(self, msg: dict) -> Tick | None:
185183
"""Convert an Alpaca message dict to a Tick, or None if not a trade."""
186184
msg_type = msg.get("T")
187185

@@ -201,7 +199,7 @@ def _parse_message(self, msg: dict) -> Optional[Tick]:
201199
try:
202200
ts = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
203201
except (ValueError, AttributeError):
204-
ts = datetime.now(timezone.utc)
202+
ts = datetime.now(UTC)
205203

206204
# Enrich with latest quote if available
207205
quote = self._latest_quotes.get(sym, {})

packages/quant_pod/data/market_data_bus.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,12 @@
4949

5050
import asyncio
5151
from abc import ABC, abstractmethod
52-
from typing import Callable, List, Optional
52+
from collections.abc import Callable
5353

5454
from loguru import logger
5555

5656
from quant_pod.execution.tick_executor import Tick
5757

58-
5958
# ---------------------------------------------------------------------------
6059
# Abstract interface
6160
# ---------------------------------------------------------------------------
@@ -69,7 +68,7 @@ class MarketDataBus(ABC):
6968
the tick_queue. A None sentinel on the queue signals clean shutdown.
7069
"""
7170

72-
def __init__(self, symbols: List[str]):
71+
def __init__(self, symbols: list[str]):
7372
self.symbols = [s.upper() for s in symbols]
7473
self._running = False
7574

@@ -113,7 +112,7 @@ class RestPollingBus(MarketDataBus):
113112

114113
def __init__(
115114
self,
116-
symbols: List[str],
115+
symbols: list[str],
117116
quote_fn: Callable[[str], dict],
118117
poll_interval_seconds: float = 30.0,
119118
error_sleep_seconds: float = 5.0,

packages/quant_pod/db.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,23 @@
2626
from __future__ import annotations
2727

2828
import os
29+
import re
30+
import time
2931
from pathlib import Path
3032
from threading import Lock
3133

3234
import duckdb
3335
from loguru import logger
3436

37+
# How long (seconds) to keep retrying after detecting a stale lock.
38+
# A stale lock means the previous owner process is dead; the OS will release
39+
# the file lock shortly. Retry window must exceed typical OS cleanup latency.
40+
_STALE_LOCK_RETRY_SECS = 10
41+
_STALE_LOCK_POLL_INTERVAL = 0.5
42+
43+
# Pattern DuckDB embeds in its lock error: "... (PID 12345) ..."
44+
_LOCK_PID_RE = re.compile(r"\(PID\s+(\d+)\)")
45+
3546
# ---------------------------------------------------------------------------
3647
# Connection management
3748
# ---------------------------------------------------------------------------
@@ -40,6 +51,70 @@
4051
_conn_lock = Lock()
4152

4253

54+
def _is_process_alive(pid: int) -> bool:
55+
"""Return True if a process with this PID is running on this machine."""
56+
try:
57+
os.kill(pid, 0)
58+
return True
59+
except ProcessLookupError:
60+
return False # PID does not exist
61+
except PermissionError:
62+
return True # PID exists but owned by another user — still alive
63+
64+
65+
def _connect_with_lock_guard(path: str) -> duckdb.DuckDBPyConnection:
66+
"""
67+
Open a DuckDB connection, handling lock conflicts intelligently.
68+
69+
Two cases:
70+
1. Stale lock (owning process is dead) — the OS will release the file
71+
lock shortly after process exit. Retry for up to
72+
_STALE_LOCK_RETRY_SECS before giving up.
73+
2. Live duplicate — another server process owns the lock. Fail
74+
immediately with an actionable error that includes the PID and
75+
the exact kill command.
76+
"""
77+
deadline = time.monotonic() + _STALE_LOCK_RETRY_SECS
78+
last_exc: Exception | None = None
79+
80+
while True:
81+
try:
82+
return duckdb.connect(path)
83+
except duckdb.IOException as exc:
84+
last_exc = exc
85+
msg = str(exc)
86+
if "Conflicting lock" not in msg:
87+
raise # unrelated I/O error, don't mask it
88+
89+
match = _LOCK_PID_RE.search(msg)
90+
if not match:
91+
raise # can't determine owner, surface the original error
92+
93+
owner_pid = int(match.group(1))
94+
95+
if _is_process_alive(owner_pid):
96+
raise RuntimeError(
97+
f"QuantPod DB is locked by a running process (PID {owner_pid}).\n"
98+
f" → Kill it: kill {owner_pid}\n"
99+
f" → Or check: ps -p {owner_pid}\n"
100+
f" DB path: {path}"
101+
) from exc
102+
103+
# Stale lock — process is dead. Retry until the OS cleans up.
104+
if time.monotonic() >= deadline:
105+
raise RuntimeError(
106+
f"Stale lock on {path} (dead PID {owner_pid}) did not clear "
107+
f"after {_STALE_LOCK_RETRY_SECS}s. "
108+
f"Try: rm -f '{path}.wal' and restart."
109+
) from last_exc
110+
111+
logger.warning(
112+
f"[DB] Stale lock detected (dead PID {owner_pid}), "
113+
f"retrying in {_STALE_LOCK_POLL_INTERVAL}s..."
114+
)
115+
time.sleep(_STALE_LOCK_POLL_INTERVAL)
116+
117+
43118
def open_db(path: str = "") -> duckdb.DuckDBPyConnection:
44119
"""
45120
Open (or return a cached) DuckDB connection.
@@ -60,7 +135,7 @@ def open_db(path: str = "") -> duckdb.DuckDBPyConnection:
60135

61136
with _conn_lock:
62137
if _conn is None:
63-
_conn = duckdb.connect(path)
138+
_conn = _connect_with_lock_guard(path)
64139
logger.info(f"[DB] Opened consolidated database at {path}")
65140
return _conn
66141

packages/quant_pod/mcp/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2346,7 +2346,7 @@ def _minimal_crew_inputs(symbol: str, regime: dict[str, Any]) -> dict[str, Any]:
23462346

23472347
return {
23482348
"symbol": symbol,
2349-
"current_date": date.today(),
2349+
"current_date": str(date.today()),
23502350
"regime": regime,
23512351
"regime_str": (
23522352
f"Trend: {regime.get('trend', 'unknown')}, "

packages/quantcore/data/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Data module for fetching, storing, and processing market data."""
22

33
from quantcore.data.fetcher import AlphaVantageClient
4-
from quantcore.data.storage import DataStore
5-
from quantcore.data.resampler import OHLCVResampler
64
from quantcore.data.preprocessor import DataPreprocessor
7-
from quantcore.data.provider import Bar, Quote, SymbolInfo, DataProvider, get_provider, set_provider
5+
from quantcore.data.provider import Bar, DataProvider, Quote, SymbolInfo, get_provider, set_provider
6+
from quantcore.data.resampler import OHLCVResampler
7+
from quantcore.data.storage import DataStore
88
from quantcore.data.validator import DataValidator
99

1010
__all__ = [

packages/quantcore/data/adapters/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
uv pip install -e ".[ibkr]"
1414
"""
1515

16-
from quantcore.data.adapters.alphavantage import AlphaVantageAdapter
1716
from quantcore.data.adapters.alpaca import AlpacaAdapter
18-
from quantcore.data.adapters.polygon_adapter import PolygonAdapter
17+
from quantcore.data.adapters.alphavantage import AlphaVantageAdapter
1918
from quantcore.data.adapters.ibkr import IBKRDataAdapter
19+
from quantcore.data.adapters.polygon_adapter import PolygonAdapter
2020

2121
__all__ = [
2222
"AlphaVantageAdapter",

packages/quantcore/data/adapters/alpaca.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121

2222
from __future__ import annotations
2323

24-
from datetime import datetime, timezone
25-
from typing import List, Optional
24+
from datetime import UTC, datetime
2625

2726
import pandas as pd
2827
from loguru import logger
@@ -34,7 +33,6 @@
3433

3534
try:
3635
from alpaca.data.historical import StockHistoricalDataClient
37-
from alpaca.data.requests import StockBarsRequest
3836
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
3937
_ALPACA_AVAILABLE = True
4038
except ImportError:
@@ -61,7 +59,7 @@ def _require_alpaca() -> None:
6159
)
6260

6361

64-
def _to_alpaca_tf(timeframe: Timeframe) -> "TimeFrame":
62+
def _to_alpaca_tf(timeframe: Timeframe) -> TimeFrame:
6563
amount, unit_name = _ALPACA_NATIVE[timeframe]
6664
unit = getattr(TimeFrameUnit, unit_name)
6765
return TimeFrame(amount, unit)
@@ -110,8 +108,8 @@ class AlpacaAdapter(AssetClassAdapter):
110108

111109
def __init__(
112110
self,
113-
api_key: Optional[str] = None,
114-
secret_key: Optional[str] = None,
111+
api_key: str | None = None,
112+
secret_key: str | None = None,
115113
paper: bool = True,
116114
) -> None:
117115
_require_alpaca()
@@ -139,8 +137,8 @@ def fetch_ohlcv(
139137
self,
140138
symbol: str,
141139
timeframe: Timeframe,
142-
start_date: Optional[datetime] = None,
143-
end_date: Optional[datetime] = None,
140+
start_date: datetime | None = None,
141+
end_date: datetime | None = None,
144142
) -> pd.DataFrame:
145143
"""Fetch OHLCV bars from Alpaca.
146144
@@ -171,7 +169,7 @@ def fetch_ohlcv(
171169

172170
return df.sort_index()
173171

174-
def get_available_symbols(self) -> List[str]:
172+
def get_available_symbols(self) -> list[str]:
175173
# Alpaca supports thousands of US equities; returning the full list
176174
# is expensive. Callers should query the assets endpoint directly
177175
# if they need a complete universe.
@@ -183,8 +181,8 @@ def _fetch(
183181
self,
184182
symbol: str,
185183
timeframe: Timeframe,
186-
start_date: Optional[datetime],
187-
end_date: Optional[datetime],
184+
start_date: datetime | None,
185+
end_date: datetime | None,
188186
) -> pd.DataFrame:
189187
if timeframe == Timeframe.H4:
190188
df_1h = self._fetch_native(symbol, Timeframe.H1, start_date, end_date)
@@ -200,16 +198,16 @@ def _fetch_native(
200198
self,
201199
symbol: str,
202200
timeframe: Timeframe,
203-
start_date: Optional[datetime],
204-
end_date: Optional[datetime],
201+
start_date: datetime | None,
202+
end_date: datetime | None,
205203
) -> pd.DataFrame:
206204
from alpaca.data.requests import StockBarsRequest
207205

208206
# Alpaca requires timezone-aware datetimes
209-
def _as_utc(dt: Optional[datetime]) -> Optional[datetime]:
207+
def _as_utc(dt: datetime | None) -> datetime | None:
210208
if dt is None:
211209
return None
212-
return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
210+
return dt if dt.tzinfo else dt.replace(tzinfo=UTC)
213211

214212
request = StockBarsRequest(
215213
symbol_or_symbols=symbol,

0 commit comments

Comments
 (0)