Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "pip install -e '.[api,mcp,dev]' --quiet 2>/dev/null || pip install -e '.[dev]' --quiet 2>/dev/null || true"
}
]
}
]
}
}
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ KALSHI_PRIVATE_KEY_PATH=kalshi_private_key.pem
MANIFOLD_API_KEY=manifold_your_api_key_here

# FastAPI settings (for the local web app)
SECRET_KEY=change-this-secret-key-in-production
# SECRET_KEY is auto-generated in dev mode. For production, generate a strong key:
# python -c "import secrets; print(secrets.token_urlsafe(64))"
# SECRET_KEY=your-secure-random-string-here
DATABASE_URL=sqlite:///./data/prediction_analyzer.db
# ENVIRONMENT=production # Uncomment to enforce SECRET_KEY requirement
13 changes: 13 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[flake8]
max-line-length = 100
extend-ignore = E203, W503
exclude =
.git,
__pycache__,
build,
dist,
*.egg-info,
venv,
env
per-file-ignores =
__init__.py:F401
73 changes: 73 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-

- name: Install dependencies
run: |
if [[ "${{ matrix.python-version }}" == "3.9" ]]; then
pip install -e ".[api,dev]"
else
pip install -e ".[api,mcp,dev]"
fi

- name: Run tests
run: |
if [[ "${{ matrix.python-version }}" == "3.9" ]]; then
pytest --cov=prediction_analyzer --cov-report=xml -q --ignore=tests/mcp
else
pytest --cov=prediction_analyzer --cov=prediction_mcp --cov-report=xml -q
fi

- name: Upload coverage
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml

lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Check formatting (black)
run: black --check prediction_analyzer prediction_mcp tests

- name: Lint (flake8)
run: flake8 prediction_analyzer prediction_mcp
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ chart_*.png
pro_chart_*.html
enhanced_chart_*.html
global_dashboard.html
charts_output/

# Type checking
.mypy_cache/

# SQLite database
*.db
Expand Down
84 changes: 76 additions & 8 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class MarketProvider(ABC):
|----------|------|-----------|------------|
| Limitless | `X-API-Key` header | Page-based (`page` param) | Native (API provides PnL) |
| Polymarket | None (public API, wallet as query param) | Timestamp window narrowing | FIFO calculator |
| Kalshi | RSA-PSS per-request signing | Cursor-based (`cursor` param) | Position endpoint + distribution |
| Kalshi | RSA-PSS per-request signing (key cleared from memory after use) | Cursor-based (`cursor` param) | Position endpoint + distribution |
| Manifold | `Authorization: Key ...` header | Cursor-based (`before` param) | FIFO calculator |

### FIFO PnL Calculator (`providers/pnl_calculator.py`)
Expand All @@ -331,19 +331,24 @@ Responsible for loading and normalizing trade data from multiple sources:
- **Supported formats**: JSON, CSV, XLSX
- **Auto-detection**: Uses ProviderRegistry to detect file format from field signatures
- **Timestamp parsing**: Handles Unix epochs (seconds/milliseconds), RFC 3339, ISO 8601
- **Unit conversion**: Converts API micro-units (6 decimals) to standard units
- **Unit conversion**: Converts API micro-units using `USDC_DECIMALS` (1,000,000) constant
- **Field mapping**: Maps various API field names to internal format
- **NaN/Infinity sanitization**: `sanitize_numeric()` replaces NaN → 0.0, Inf → ±`INF_CAP`

Key functions:
- `load_trades(file_path)`: Main entry point -- auto-detects provider format
- `save_trades(trades, file_path)`: Save trades to JSON
- `_parse_timestamp(value)`: Robust timestamp parsing
- `sanitize_numeric(value)`: Guards against NaN/Infinity for JSON serialization

Key constants:
- `INF_CAP = 999999.99` — shared ceiling for infinite values across the codebase

### PnL Calculator (`pnl.py`)

Calculates profit/loss metrics:

- `calculate_pnl(trades)`: Returns DataFrame with cumulative PnL
- `calculate_pnl(trades)`: Returns DataFrame with cumulative PnL (uses `Decimal` accumulation to avoid float drift)
- `calculate_global_pnl_summary(trades)`: Aggregate statistics with currency separation -- top-level totals use real-money currencies (USD/USDC) only; play-money (MANA) reported separately under `by_currency`; also includes `by_source` breakdown
- `calculate_market_pnl_summary(trades)`: Per-market statistics
- `calculate_market_pnl(trades)`: Breakdown by market
Expand All @@ -356,6 +361,11 @@ Metrics calculated:
- Total invested/returned
- Per-currency and per-source breakdowns

**Numeric precision notes:**
- Cumulative PnL is computed using `decimal.Decimal` accumulation, then stored back as `float`.
- Infinite values (e.g. profit factor with zero losses) are capped at `INF_CAP` (999999.99), defined in `trade_loader.py` and shared across the codebase.
- DB monetary columns use `Numeric(18,8)` to reduce rounding in storage.

### Filters (`filters.py` + `trade_filter.py`)

Advanced filtering capabilities:
Expand Down Expand Up @@ -404,12 +414,14 @@ Four chart types with different use cases:

### MCP Server (`prediction_mcp/`)

Model Context Protocol server providing 18 tools across 7 modules:
Model Context Protocol server implementing all three MCP primitives:

- **Transport**: stdio (Claude Code) or HTTP/SSE (web agents)
- **State**: In-memory session with optional SQLite persistence
- **Multi-source**: Session tracks multiple provider sources simultaneously
- **Tools**: data (4), analysis (5), filter (1), chart (2), export (1), portfolio (4), tax (1)
- **Tools**: 18 tools across 7 modules — data (4), analysis (5), filter (1), chart (2), export (1), portfolio (4), tax (1)
- **Resources**: Dynamic resources exposing session state — `prediction://trades/summary`, `prediction://trades/markets`, `prediction://trades/filters`
- **Prompts**: 3 prompt templates — `analyze_portfolio` (with risk/performance/tax focus), `compare_periods`, `daily_report`

Key features:
- `fetch_trades` tool accepts `provider` parameter with auto-detection
Expand All @@ -421,11 +433,15 @@ Key features:

REST API with JWT authentication:

- Trade upload with auto-detection of provider format
- Trade upload with auto-detection of provider format (10 MB upload limit)
- Source-based filtering (`?source=polymarket`)
- `/trades/providers` endpoint listing available providers
- `/trades/providers` endpoint listing available providers (requires authentication)
- CSV/JSON export with source and currency fields
- SQLAlchemy models include `source` and `currency` columns
- SQLAlchemy models use `Numeric(18,8)` for monetary columns (price, shares, cost, pnl)
- Security headers middleware (X-Frame-Options, X-Content-Type-Options, HSTS, etc.)
- Per-IP rate limiting with key eviction (bounded memory; single-process only)
- SECRET_KEY auto-generated in dev mode; must be explicitly set for production
- Minimum password length: 8 characters

## User Interfaces

Expand Down Expand Up @@ -625,6 +641,58 @@ pytest # Run all tests
pytest --cov=prediction_analyzer # With coverage
```

## Security Architecture

### Web API Security Layers

```
Request → Rate Limiter → Security Headers → CORS → Auth (JWT) → Route Handler
```

1. **Rate Limiting** (per-IP, in-memory sliding window)
- Auth endpoints: 5 req/60s
- General endpoints: 60 req/60s
- Key eviction at 10,000 keys to bound memory
- **Limitation**: Single-process only. For multi-worker deployments, replace with Redis-backed solution.

2. **Security Headers** (middleware on all responses)
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `X-XSS-Protection: 1; mode=block`
- `Permissions-Policy: geolocation=(), camera=(), microphone=()`
- `Strict-Transport-Security` (HTTPS only)

3. **CORS**
- Explicit origins list with credentials; wildcard without credentials
- Methods restricted to `GET, POST, PUT, PATCH, DELETE, OPTIONS`
- Headers restricted to `Authorization, Content-Type, Accept`

4. **Authentication**
- JWT tokens with HS256 (symmetric), includes `exp`, `iat`, `iss`, `aud` claims
- Passwords hashed with Argon2 (minimum 8 characters)
- SECRET_KEY: auto-generated random key in dev; must be set via env var in production

5. **Upload Protection**
- 10 MB file size limit enforced before processing
- SHA-256 dedup prevents duplicate uploads
- Temporary files cleaned up in `finally` block

### Provider Credential Security

- API keys are never logged, printed, or serialized
- Kalshi RSA private key cleared from memory (`self._private_key = None`) after each `fetch_trades` call
- `.env` and `*.pem` files excluded from version control via `.gitignore`
- Polymarket wallet address is not logged (removed in security audit)

### Numeric Precision Invariants

- **DB storage**: `Numeric(18,8)` for all monetary columns (price, shares, cost, pnl)
- **Cumulative PnL**: Computed via `decimal.Decimal` accumulation, not float `cumsum()`
- **Infinity cap**: Unified to `INF_CAP = 999999.99` (defined in `trade_loader.py`)
- **USDC conversion**: Uses named constant `USDC_DECIMALS = 1_000_000`
- **NaN/Infinity sanitization**: Applied at serialization boundaries (JSON export, MCP responses)

## Design Principles

1. **Modularity**: Each component has a single responsibility
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-03-10

### Added
- Multi-provider support: Limitless Exchange, Polymarket, Kalshi, Manifold Markets
- Provider auto-detection from API key prefix and file field signatures
- FIFO PnL calculator for providers without native PnL (Polymarket, Manifold)
- MCP server with 18 tools across 7 modules (stdio + SSE transports)
- FastAPI web application with JWT authentication
- SQLite session persistence for the MCP server
- Interactive CLI menu system for novice users
- Tkinter desktop GUI with provider selection
- Four chart types: simple (matplotlib), pro (Plotly), enhanced, global dashboard
- Advanced trading metrics: Sharpe, Sortino, drawdown, profit factor, streaks
- Portfolio tools: open positions, concentration risk, drawdown analysis, period comparison
- Tax reporting with FIFO/LIFO/average cost basis methods
- CSV, XLSX, and JSON export
- LLM-friendly error handling with recovery hints
- Input validation with case normalization for LLM agents
- NaN/Infinity sanitization across all serialization boundaries

### Security
- Security headers middleware (X-Frame-Options, X-Content-Type-Options, HSTS, etc.)
- Per-IP rate limiting with key eviction (5 req/min auth, 60 req/min general)
- 10 MB file upload limit with SHA-256 deduplication
- Argon2 password hashing (minimum 8 characters)
- SECRET_KEY auto-generated in dev, required in production
- Kalshi RSA private key cleared from memory after use
- `Numeric(18,8)` for all DB monetary columns (replacing Float)
- `decimal.Decimal` accumulation for cumulative PnL
- CORS with restricted methods/headers
- All endpoints authenticated (including `/trades/providers`)
- API keys never logged; only env var names in error messages
Loading
Loading