Metadata-Version: 2.4
Name: tradepose-client
Version: 2.6.2
Summary: Python client SDK for TradePose trading platform
Author-email: TradePose Team <codeotter0201@gmail.com>
License: MIT
Keywords: client,quantitative,sdk,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.13
Requires-Dist: filelock>=3.16.0
Requires-Dist: httpx[http2]>=0.28.1
Requires-Dist: nest-asyncio>=1.6.0
Requires-Dist: polars==1.33.1
Requires-Dist: pyarrow
Requires-Dist: pydantic-settings>=2.7.0
Requires-Dist: pydantic>=2.12.1
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: rich>=13.0.0
Requires-Dist: tradepose-models<3.0.0,>=2.4.1
Requires-Dist: typer>=0.16.0
Provides-Extra: analysis
Requires-Dist: kaleido>=0.2.1; extra == 'analysis'
Requires-Dist: plotly>=6.0.0; extra == 'analysis'
Requires-Dist: scipy>=1.14.0; extra == 'analysis'
Provides-Extra: dev
Requires-Dist: mypy>=1.13.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: respx>=0.21.0; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Provides-Extra: optional
Requires-Dist: orjson>=3.10.0; extra == 'optional'
Requires-Dist: tenacity>=9.0.0; extra == 'optional'
Description-Content-Type: text/markdown

# TradePose Client SDK

Python SDK for TradePose quantitative trading platform. Simple, type-safe, production-ready.

## What is this?

Official Python client for the TradePose trading platform API. Designed for quantitative traders, algo developers, and trading system architects who need:

- 🎯 **Simple synchronous API** - No async/await required, works out of the box
- 📊 **Batch testing** - Multi-strategy, multi-period backtesting with background polling
- 🔒 **Type safety** - Pydantic models, IDE autocomplete, compile-time validation
- 🎨 **Direct typed authoring** - Data, Base opportunity, and Advanced policy are explicit
- 🔄 **Production-ready** - Comprehensive error handling, automatic retries, Jupyter support
- 📋 **CRUD Resources** - Strategy, Portfolio, Account, Binding management via Gateway API

## Installation

```bash
pip install tradepose-client
```

**Requirements:**
- Python 3.13+
- Dependencies: httpx, pydantic, polars, PyYAML, nest-asyncio

## Local authoring workspace and agent skills

Initialize a local Python workspace and install the SDK-distributed Claude
and Codex skills:

```bash
tradepose init . --agents claude,codex
tradepose doctor
tradepose skills check
```

`tradepose draft new <name>` creates one human-owned
`playbook/drafts/<name>.py` source. Edit its typed interface and public documentation,
then create one self-contained workspace Experiment before planning:

```bash
tradepose draft new <name> --template rsi-reversion
# Edit playbook/drafts/<name>.py.
tradepose experiment new research --source draft:<name>
tradepose draft describe <name> --json
tradepose strategy check <name> --json
tradepose experiment check research --json
tradepose experiment plan research --json
```

Use `experiment new --kind ohlcv_indicators` for OHLCV plus declared indicators
without signal execution, or `--kind ohlcv_signals [--blueprint NAME]` for the full
signal/trigger/policy path. Periods accept `--year YYYY` or paired ISO
`--start/--end` bounds.

The SQLite Experiment Catalog contains one current, revisioned Experiment with shared
periods and ordered draft/formal strategy Members. Use `experiment list/show/update/remove`
for CRUD and explicit `experiment export/import` envelopes for exchange. `experiment plan`
stores exact Source Revisions,
resolved Params, Config + Blueprint/Policy candidates, and physical requests in SQLite
v4 without contacting the Gateway. Identical Plans reuse the existing Run. An explicit
remote execution request starts it with `tradepose run start <run-id>` (or `tradepose run start <run-id>
--detach`) and resumes it with `tradepose run resume <run-id>`.

Use `tradepose inspect source:<ref>`, `hash:<callable-source-hash>`, `plan:<id>`,
`run:<id>`, or `task:<id>` for durable bidirectional lineage. Local Portfolio promotion
selects exact candidate IDs and commits Params/Policy-first append-only versions to
SQLite. `playbook/portfolios/<slug>.yaml` is a replaceable human projection regenerated
from that version stream; exact execution payload is never published as a remote
Portfolio.

Run metadata, canonical request bytes, and source snapshots live in
`.tradepose/state.sqlite3`. Use `tradepose state info` to see its schema, path, domain
record counts, and safe create/read/update/delete commands. Large result files live under
`results/runs/<run-id>/<task-id>/artifacts/`. Inspect local state with `run list`,
`run show`, and `run path`; remove an unstarted Run with `run remove`. `--force` removes
local evidence only and never cancels remote work.
Incompatible SQLite schemas are rejected without implicit migration; move the old state
aside and rebuild it explicitly when adopting this breaking release.

After `task download <task-id>` verifies the kind-directed Parquet and its indicator
manifest, local data can be inspected without network access or artifact rewrites:

```bash
tradepose task data describe <task-id> --stats
tradepose task data preview <task-id> --rows 20 --column ts --column primary.atr.value
tradepose task data trace <task-id> --event validated-entry --before 5 --after 10
```

The default `friendly` view restores public indicator names; `--view raw` preserves
physical `ind_v1_<digest>` columns. Preview and trace output is strictly bounded.

Use `tradepose skills install --agents claude,codex` to add missing files.
`tradepose skills sync --agents claude,codex` updates only unmodified generated
files, and `tradepose skills check` reports missing, package drift, and user
conflicts. Manifest integrity—including malformed or mismatched recorded
checksums—or generated-file conflicts use storage exit code `7`.
See [Security boundary](docs/SECURITY.md) and
[Known limitations](docs/KNOWN_LIMITATIONS.md).

## Quick Start

### Batch Testing (Recommended)

Test multiple strategies across multiple periods - no async/await needed:

```python
from tradepose_client import BatchTester
from tradepose_client.batch import Period

# Create tester
tester = BatchTester(api_key="tp_live_xxx")

# Submit batch (non-blocking, returns immediately)
batch = tester.submit_backtest(
    strategies=[strategy1, strategy2, strategy3],
    periods=[
        Period.Q1(2024),  # 2024-01-01 to 2024-03-31
        Period.Q2(2024),  # 2024-04-01 to 2024-06-30
        Period.Q3(2024),  # 2024-07-01 to 2024-09-30
    ]
)

print(f"Submitted {len(batch.task_ids)} tasks")
print(f"Progress: {batch.progress:.1%}")

# Wait for completion (blocking)
batch.wait()

# Access trades (Polars DataFrame)
all_trades_df = batch.trades  # All trades with period column

# Period-specific results
q1 = batch[Period.Q1(2024).to_key()]
print(f"Q1 trades: {len(q1.trades)}")
print(f"Q1 PNL: {q1.trades['pnl'].sum()}")
```

### Period Objects (Type-Safe Dates)

Use `Period` objects for type-safe date validation:

```python
from tradepose_client.batch import Period

# Quarterly testing
periods = [
    Period.Q1(2024),  # Jan-Mar
    Period.Q2(2024),  # Apr-Jun
    Period.Q3(2024),  # Jul-Sep
    Period.Q4(2024),  # Oct-Dec
]

# Full year
full_year = Period.from_year(2024)  # 2024-01-01 to 2024-12-31

# Single month
march = Period.from_month(2024, 3)  # 2024-03-01 to 2024-03-31

# Flexible multi-month ranges
three_months = Period.from_month(2024, 3, n_months=3)  # Mar-May 2024
half_year = Period.from_month(2024, 1, n_months=6)     # Jan-Jun 2024
winter = Period.from_month(2024, 11, n_months=3)       # Nov 2024 - Jan 2025

# Custom range
custom = Period(start="2024-01-15", end="2024-02-15")
```

**Benefits:**
- ✅ Compile-time type checking
- ✅ IDE autocomplete and validation
- ✅ Automatic validation (start < end)
- ✅ Clear error messages

### Strategy Authoring

Authoring separates tunable values from assembly:
`direct typed sources + Opportunity → Definition Builder → Definition → current-wire StrategyConfig`.

```python
from tradepose_client import authoring as tp


@tp.strategy(SmaParams)
def sma(builder: tp.DefinitionBuilder, params: SmaParams):
    # Bind keeps Params reusable and resolves typed indicators to recipe-local handles.
    selected = builder.bind(params)
    primary = selected.primary

    # .col() selects completed-bar server columns for Polars expressions.
    fast = primary.fast_sma.col()
    slow = primary.slow_sma.col()
    atr = primary.volatility_atr.col()
    entry = fast > slow
    exit = fast < slow
    volatility_level = build_volatility_level(
        atr,
        window=primary.volatility_window,
    )

    # Register Data outputs before Base seals and assembles the Definition.
    builder.data.set_volatility_scale(primary.volatility_atr)
    builder.data.set_volatility_level(expr=volatility_level)
    builder.base(
        direction=selected.opportunity.direction,
        trend=selected.opportunity.trend,
        entry=entry,
        exit=exit,
    )

params = SmaParams.create()
definition = sma.define(params)
variants = SmaParams.sweep().expand(params)
policies = PolicySet.sweep(
    params,
    direction="long",
    entry_kind="favorable",
    entry_distances=(0.3,),
    stop_losses=(1.0, 1.5),
    take_profits=(2.0, 3.0),
)
configs = sma.build(params, policies=policies)
```

Call `SmaParams.sweep()` for the strategy author's default search ranges, or replace
its typed keyword-only axes with custom tuples, lists, or ranges.

Create policies only after the Base Params seed. The SDK-owned
`PolicySet.sweep(params, ...)` expands the entry/exit search space into Advanced
Blueprints inside its Config; strategy authors only call it and do not implement it
on their Params class. Conditions, sizing, lot-size behavior, volatility weights, and
metadata remain fixed overrides. Distance policies reference the volatility scale
declared by Base Data and cannot replace it. Direction accepts `"long"`, `"short"`, or
`TradeDirection`. Use
`PolicySet.cases(params, Policy(...), ...)` for correlated candidates. Base `sweep` and
Advanced `policies` cannot be used in the same build.

Direct-source `StrategyParams` exposes a typed keyword-only `create()` with useful
recipe defaults. Import `tradepose_client.authoring as tp` and name each `tp.Source`
by stable strategy role (`primary`,
`context`), not sweepable instrument/frequency values. Sources contain flat indicators
plus pure source-local Data calculations. `builder.bind()` registers the Data graph and
returns selectable indicators. Each indicator owns its independent completed-bar shift.
Use `ResampledDataSource` for a typed, instrument-inheriting lower-resolution role such
as `primary` 15m → `trend` 1h; `bind()` validates and materializes that DAG.
[BUILDER_EXAMPLE.md](BUILDER_EXAMPLE.md) for a complete executable example.

## Core Concepts

### Batch Testing API (Primary Interface)

`BatchTester` is the main way to interact with the platform:

```python
from tradepose_client import BatchTester
from tradepose_client.batch import Period

tester = BatchTester(api_key="tp_live_xxx")

# Submit tasks
batch = tester.submit_backtest(
    strategies=[strategy1, strategy2],
    periods=[Period.Q1(2024), Period.Q2(2024)]
)

# Monitor progress
print(f"Progress: {batch.progress:.1%}")
print(f"Completed: {batch.status_counts['completed']}/{len(batch.task_ids)}")

# Wait for completion
batch.wait()  # Blocks until all tasks complete

# Access trades
all_trades = batch.trades  # All trades across periods

# Period-specific results
q1_result = batch[Period.Q1(2024).to_key()]
print(f"Q1 trades: {len(q1_result.trades)}")
```

**Features:**
- **Synchronous interface** - No async/await required
- **Background polling** - Tasks execute in background, results auto-download
- **Type-safe dates** - Period objects with validation
- **Polars DataFrames** - High-performance data analysis
- **Jupyter-friendly** - Automatic event loop setup

### Instrument Discovery

Query available trading instruments:

```python
from tradepose_client import BatchTester

tester = BatchTester(api_key="tp_live_xxx")

# List all available instruments
instruments = tester.list_instruments()
print(f"Available instruments: {len(instruments)}")

for inst in instruments[:5]:
    print(f"  {inst.symbol} - {inst.exchange} ({inst.freq})")

# Filter by exchange
binance = [i for i in instruments if i.exchange == "BINANCE"]
```

### CRUD Resources (v0.3.0)

Manage trading entities via Gateway API:

```python
from tradepose_client import TradePoseClient

client = TradePoseClient(api_key="tp_live_xxx")

# Strategy management
strategies = client.strategies.list()
strategy = client.strategies.create(name="MyStrategy", config={...})

# Portfolio management
portfolios = client.portfolios.list()
portfolio = client.portfolios.create(
    name="MyPortfolio",
    capital=100000,
    currency="USD"
)

# Account management (MT5, Binance, etc.)
accounts = client.accounts.list()

# Binding (connect Portfolio to Account)
binding = client.bindings.create(
    account_id=account.id,
    portfolio_id=portfolio.id
)
```

### Low-Level API (Advanced Users)

For fine-grained control over HTTP connections, custom retry logic, or manual event loop management, see [Low-Level API Documentation](docs/LOW_LEVEL_API.md).

**Most users should use BatchTester** - it's simpler and handles async complexity automatically.

### Task Polling Pattern

Long-running operations return immediately with a task ID. Results are downloaded automatically in the background:

```python
# Submit returns immediately
batch = tester.submit_backtest(strategies=[strategy], periods=[Period.Q1(2024)])
print(f"Task ID: {batch.task_ids[0]}")  # Submitted

# Background polling starts automatically
# Do other work while tasks run...

# Wait when you need results
batch.wait()  # Blocks until completion

# Results ready
trades = batch.trades
```

## Documentation

- 💡 **[Examples](docs/EXAMPLES.md)** - Real-world usage patterns (start here!)
- 📚 **[API Reference](docs/API_REFERENCE.md)** - Complete API documentation
- 🔧 **[Low-Level API](docs/LOW_LEVEL_API.md)** - Advanced async API (for experts)
- ⚠️ **[Error Handling](docs/ERROR_HANDLING.md)** - Exception types and handling strategies
- ⚙️ **[Configuration](docs/CONFIGURATION.md)** - Environment variables, timeout settings
- 📐 **[Architecture](docs/ARCHITECTURE.md)** - Design decisions and data flow

## Features

### Current (Alpha)

#### Batch Testing API
- ✅ Multi-strategy, multi-period testing
- ✅ Background polling (daemon thread)
- ✅ Auto-download on completion
- ✅ Type-safe Period objects with validation
- ✅ Convenient constructors (Q1, Q2, from_year, from_month)
- ✅ Reactive results (lazy loading)
- ✅ Memory caching
- ✅ Jupyter support (nest_asyncio auto-applied)

#### Builder API
- ✅ Fluent strategy construction
- ✅ Type-safe indicator references
- ✅ 60% less boilerplate
- ✅ TradingContext convenience accessors
- ✅ Automatic field inheritance

#### Low-Level Client API
- ✅ Authentication (API key + JWT)
- ✅ Resource-based organization (6 resources, 21 methods)
- ✅ Async-first with HTTP/2
- ✅ Automatic retry with exponential backoff
- ✅ Comprehensive error handling (18 exception types)
- ✅ Type-safe with Pydantic models

#### CRUD Resources (v0.3.0)
- ✅ Strategy management (create, list, get, update, delete)
- ✅ Portfolio management with capital allocation
- ✅ Account management (MT5, Binance, etc.)
- ✅ Binding management (Account ↔ Portfolio)
- ✅ Instrument discovery (`list_instruments()`)
- ✅ TradingContext convenience accessors

### Roadmap

- ⏳ Webhook support (replace polling)
- ⏳ GraphQL endpoint (reduce requests)
- ⏳ Result streaming (large datasets)

## Configuration

### Environment Variables

```bash
# Authentication (required, at least one)
export TRADEPOSE_API_KEY="tp_live_xxx"
export TRADEPOSE_JWT_TOKEN="eyJ..."

# Server (optional)
export TRADEPOSE_SERVER_URL="https://api.tradepose.com"

# HTTP (optional)
export TRADEPOSE_TIMEOUT="30.0"        # Request timeout (1.0 - 600.0s)
export TRADEPOSE_MAX_RETRIES="3"        # Max retry attempts (0 - 10)

# Task polling (optional)
export TRADEPOSE_POLL_INTERVAL="2.0"    # Poll interval (0.5 - 60.0s)
export TRADEPOSE_POLL_TIMEOUT="300.0"   # Max poll duration (10.0 - 3600.0s)

# Logging (optional)
export TRADEPOSE_DEBUG="false"
export TRADEPOSE_LOG_LEVEL="INFO"       # DEBUG/INFO/WARNING/ERROR/CRITICAL
```

### Configuration Methods

```python
# Method 1: Environment variables (recommended)
tester = BatchTester()  # Auto-loads from TRADEPOSE_API_KEY

# Method 2: Direct parameters
tester = BatchTester(
    api_key="tp_live_xxx",
    poll_interval=2.0,
)

# Method 3: Configuration file (see Configuration Guide)
```

See [Configuration Guide](docs/CONFIGURATION.md) for details.

## Error Handling

All exceptions inherit from `TradePoseError`:

```python
from tradepose_client import (
    BatchTester,
    AuthenticationError,
    RateLimitError,
    TaskTimeoutError,
    ValidationError
)
from tradepose_client.batch import Period

tester = BatchTester(api_key="tp_xxx")

try:
    batch = tester.submit_backtest(
        strategies=[strategy],
        periods=[Period.Q1(2024)]
    )
    batch.wait(timeout=600.0)

except AuthenticationError:
    # Invalid API key
    print("Authentication failed")

except ValidationError as e:
    # Invalid Period or strategy configuration
    print(f"Validation error: {e.errors}")

except RateLimitError as e:
    # Rate limit exceeded
    print(f"Rate limited. Wait {e.retry_after}s")

except TaskTimeoutError as e:
    # Task didn't complete in time
    print(f"Timeout. Task ID: {e.task_id}")
```

See [Error Handling Guide](docs/ERROR_HANDLING.md) for complete reference.

## Period Validation

Period objects automatically validate date ranges:

```python
from tradepose_client.batch import Period

# Valid period
period = Period(start="2024-01-01", end="2024-12-31")  # ✅ OK

# Invalid period (start >= end)
try:
    period = Period(start="2024-12-31", end="2024-01-01")  # ❌ Error
except ValueError as e:
    print(e)  # "Period start (2024-12-31) must be before end (2024-01-01)"

# Invalid date format
try:
    period = Period(start="invalid", end="2024-12-31")  # ❌ Error
except ValueError as e:
    print(e)  # "Cannot parse datetime from type..."
```

## Migration from Tuple-Based Periods

**Before (deprecated):**
```python
# ❌ No longer supported
batch = tester.submit_backtest(
    strategies=[strategy],
    periods=[("2024-01-01", "2024-12-31")]  # Tuple not accepted
)
```

**After (type-safe):**
```python
# ✅ Required: Use Period objects
from tradepose_client.batch import Period

batch = tester.submit_backtest(
    strategies=[strategy],
    periods=[Period(start="2024-01-01", end="2024-12-31")]
)

# ✅ Even better: Use convenience constructors
batch = tester.submit_backtest(
    strategies=[strategy],
    periods=[Period.from_year(2024)]  # Clearer and type-safe
)
```

**This is a Breaking Change in version 0.2.0+**. Update your code to use `Period` objects.

## Development Status

**Alpha** - API is stable but subject to minor changes. Production use at your own risk.

## Python Version Support

Requires Python 3.13+ to leverage:
- Type parameter syntax (`[T]`)
- `Self` type hint
- Performance improvements

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Support

- **Documentation**: [docs/](docs/)
- **Issues**: [GitHub Issues](https://github.com/tradepose/tradepose-gateway/issues)
- **Email**: support@tradepose.com
