Skip to content

Commit 42100b5

Browse files
committed
fix: resolve mypy type errors and Trivy CVEs
MyPy (execution critical path): - microstructure_pipeline: align FillEvent kwargs with dataclass fields (filled_qty/avg_fill_price, not quantity/fill_price/commission) - signal_cache: guard _conn None-check before .execute() call - portfolio_state: handle fetchone() returning tuple|None on all three COALESCE query callsites - paper_broker: import PortfolioState explicitly (was forward ref); type params as list[str|int] to accept the int LIMIT arg - paper_trading: use Timeframe.D1 enum (not str "1D"); use start_date= kwarg; assert fill_price/fill_timestamp non-None before PaperPosition pyproject.toml: - torch>=2.6.0: fixes CVE-2025-32434 (CRITICAL), CVE-2024-31580, CVE-2024-31583 - wheel>=0.46.2 in build-system: fixes CVE-2026-24049 - jaraco.context>=6.1.0 via uv constraint: fixes CVE-2026-23949 - types-PyYAML added to dev deps (mypy stubs for risk_gate.py yaml import) Dockerfile: - EXTRA_CA_CERT build-arg for local corporate proxy (Zscaler) builds; no-op in CI — arg is empty so production image is unchanged - uv unpinned from stale 0.4.30 to >=0.5.0 for current dep resolution
1 parent 4c56c54 commit 42100b5

File tree

7 files changed

+40
-18
lines changed

7 files changed

+40
-18
lines changed

Dockerfile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ FROM python:3.11-slim-bookworm
66
LABEL maintainer="QuantStack"
77
LABEL description="QuantStack trading platform"
88

9+
# Optional: inject a corporate CA bundle (e.g. Zscaler) for local builds only.
10+
# Usage: docker build --build-arg EXTRA_CA_CERT="$(cat ~/.zscaler_cert.pem)" .
11+
# In CI this arg is empty — no extra certs are added to the production image.
12+
ARG EXTRA_CA_CERT=""
13+
RUN if [ -n "$EXTRA_CA_CERT" ]; then \
14+
printf '%s\n' "$EXTRA_CA_CERT" >> /usr/local/share/ca-certificates/corporate.crt \
15+
&& update-ca-certificates; \
16+
fi
17+
918
# Install system dependencies
1019
RUN apt-get update && apt-get install -y --no-install-recommends \
1120
gcc \
@@ -15,7 +24,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1524
&& rm -rf /var/lib/apt/lists/*
1625

1726
# Install uv for fast dependency management
18-
RUN pip install --no-cache-dir uv==0.4.30
27+
RUN pip install --no-cache-dir "uv>=0.5.0"
1928

2029
WORKDIR /app
2130

packages/quant_pod/execution/microstructure_pipeline.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,12 +301,11 @@ async def _on_features_callback(self, features) -> None:
301301

302302
# Record fill so FillTracker + downstream risk gate stays consistent
303303
fill = FillEvent(
304+
order_id=result.order_id,
304305
symbol=order.symbol,
305306
side=order.side,
306-
quantity=result.filled_qty,
307-
fill_price=result.avg_fill_price or current_price,
308-
commission=result.commission or 0.0,
309-
order_id=result.order_id,
307+
filled_qty=result.filled_qty,
308+
avg_fill_price=result.avg_fill_price or current_price,
310309
)
311310
self._fill_tracker.update_fill(fill)
312311
self._risk_gate.record_submission()

packages/quant_pod/execution/paper_broker.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from loguru import logger
4040
from pydantic import BaseModel, Field
4141

42-
from quant_pod.execution.portfolio_state import Position, get_portfolio_state
42+
from quant_pod.execution.portfolio_state import PortfolioState, Position, get_portfolio_state
4343

4444
# =============================================================================
4545
# DATA MODELS
@@ -107,7 +107,7 @@ class PaperBroker:
107107
def __init__(
108108
self,
109109
conn: duckdb.DuckDBPyConnection | None = None,
110-
portfolio: PortfolioState | None = None, # noqa: F821
110+
portfolio: PortfolioState | None = None,
111111
# Legacy parameter — ignored when conn is provided
112112
db_path: str | None = None,
113113
):
@@ -336,7 +336,7 @@ def _update_portfolio(self, fill: Fill, req: OrderRequest) -> None:
336336
def get_fills(self, symbol: str | None = None, limit: int = 100) -> list[Fill]:
337337
"""Return recent fills, optionally filtered by symbol."""
338338
query = "SELECT * FROM fills"
339-
params = []
339+
params: list[str | int] = []
340340
if symbol:
341341
query += " WHERE symbol = ?"
342342
params.append(symbol)
@@ -367,15 +367,15 @@ def get_total_commission(self) -> float:
367367
row = self.conn.execute(
368368
"SELECT COALESCE(SUM(commission), 0) FROM fills WHERE rejected = FALSE"
369369
).fetchone()
370-
return float(row[0])
370+
return float(row[0]) if row is not None else 0.0
371371

372372
def get_avg_slippage_bps(self) -> float:
373373
"""Average slippage in basis points across all fills."""
374374
row = self.conn.execute(
375375
"SELECT COALESCE(AVG(slippage_bps), 0) FROM fills "
376376
"WHERE rejected = FALSE AND filled_quantity > 0"
377377
).fetchone()
378-
return float(row[0])
378+
return float(row[0]) if row is not None else 0.0
379379

380380

381381
# Singleton — prefer injecting via TradingContext in new code.

packages/quant_pod/execution/portfolio_state.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ def conn(self) -> duckdb.DuckDBPyConnection:
141141
def _seed_cash(self) -> None:
142142
"""Insert initial cash row if the table is empty (first run)."""
143143
with self._lock:
144-
existing = self._conn.execute("SELECT COUNT(*) FROM cash_balance").fetchone()[0]
144+
row = self._conn.execute("SELECT COUNT(*) FROM cash_balance").fetchone()
145+
existing = row[0] if row is not None else 0
145146
if existing == 0:
146147
self._conn.execute(
147148
"INSERT INTO cash_balance (id, cash) VALUES (1, ?)",
@@ -421,7 +422,7 @@ def get_total_realized_pnl(self) -> float:
421422
row = self.conn.execute(
422423
"SELECT COALESCE(SUM(realized_pnl), 0) FROM closed_trades"
423424
).fetchone()
424-
return float(row[0])
425+
return float(row[0]) if row is not None else 0.0
425426

426427
def get_daily_pnl(self, for_date: date | None = None) -> float:
427428
"""Realized P&L for a specific date (defaults to today)."""
@@ -432,7 +433,7 @@ def get_daily_pnl(self, for_date: date | None = None) -> float:
432433
"WHERE closed_at::DATE = ?",
433434
[str(d)],
434435
).fetchone()
435-
return float(row[0])
436+
return float(row[0]) if row is not None else 0.0
436437

437438
def get_snapshot(self) -> PortfolioSnapshot:
438439
"""

packages/quant_pod/execution/signal_cache.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ def _persist(self, signal: TradeSignal) -> None:
244244
245245
Called with self._lock held — don't acquire it here.
246246
"""
247+
if self._conn is None:
248+
return
247249
try:
248250
self._conn.execute(
249251
"""

packages/quantcore/execution/paper_trading.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import pandas as pd
1919
from loguru import logger
2020

21+
from quantcore.config.timeframes import Timeframe
2122
from quantcore.data.storage import DataStore
2223
from quantcore.data.universe import UniverseManager
2324
from quantcore.execution.broker import BrokerInterface
@@ -243,8 +244,8 @@ def _get_features(self, symbol: str) -> dict | None:
243244
try:
244245
df = self.data_store.load_ohlcv(
245246
symbol=symbol,
246-
timeframe="1D",
247-
start=datetime.now() - timedelta(days=30),
247+
timeframe=Timeframe.D1,
248+
start_date=datetime.now() - timedelta(days=30),
248249
)
249250

250251
if df.empty:
@@ -377,7 +378,10 @@ def _update_position_from_order(self, order: PaperOrder) -> None:
377378
if pos.quantity <= 0:
378379
del self.positions[symbol]
379380
else:
380-
# New position
381+
# New position — fill_price and fill_timestamp are set by the
382+
# simulated fill above (lines 356-357) before we reach here.
383+
assert order.fill_price is not None
384+
assert order.fill_timestamp is not None
381385
self.positions[symbol] = PaperPosition(
382386
symbol=symbol,
383387
direction=order.direction,

pyproject.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build-system]
2-
requires = ["setuptools>=61.0", "wheel"]
2+
requires = ["setuptools>=61.0", "wheel>=0.46.2"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
@@ -49,7 +49,8 @@ dependencies = [
4949
"shap>=0.42.0",
5050
"statsmodels>=0.14.0",
5151
# RL - required because quantcore/__init__.py imports rl submodule
52-
"torch>=2.0.0",
52+
# >=2.6.0: fixes CVE-2025-32434 (CRITICAL), CVE-2024-31580, CVE-2024-31583
53+
"torch>=2.6.0",
5354
"gymnasium>=0.29.0",
5455
"stable-baselines3>=2.2.0",
5556
"shimmy>=1.3.0",
@@ -76,6 +77,7 @@ dev = [
7677
"hypothesis>=6.90.0",
7778
"ruff>=0.1.0",
7879
"mypy>=1.7.0",
80+
"types-PyYAML>=6.0.0",
7981
"mcp>=1.0.0",
8082
]
8183
# Scheduler (Enhancement 4) — optional, required only if using scripts/scheduler.py
@@ -186,6 +188,7 @@ dev = [
186188
"hypothesis>=6.90.0",
187189
"ruff>=0.1.0",
188190
"mypy>=1.7.0",
191+
"types-PyYAML>=6.0.0",
189192
"mcp>=1.0.0",
190193
"pre-commit>=3.5.0",
191194
"coverage>=7.0.0",
@@ -195,6 +198,10 @@ dev = [
195198
# UV configuration for dependency management
196199
# Use native TLS for better SSL compatibility (Zscaler, corporate proxies)
197200
native-tls = true
201+
# Security-driven lower bounds on transitive deps (Trivy CVE fixes)
202+
constraint-dependencies = [
203+
"jaraco.context>=6.1.0", # CVE-2026-23949
204+
]
198205

199206
[tool.pytest.ini_options]
200207
testpaths = ["tests"]

0 commit comments

Comments
 (0)