Metadata-Version: 2.4
Name: tradepose-client
Version: 3.4.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.9.0
Requires-Dist: typer>=0.16.0
Provides-Extra: dev
Requires-Dist: click>=8.1.0; 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** - Explicit error handling, idempotent submission recovery, 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 strategy new <name>` creates one Strategy Family with a human-owned
`playbook/strategies/**/<name>.py` Working Source. Folders organize the workspace but
do not participate in source identity. Edit its typed interface and public documentation,
then create one self-contained workspace Experiment before previewing:

```bash
tradepose strategy new <name> --folder mean_reversion --template rsi-reversion
# Edit playbook/strategies/mean_reversion/<name>.py.
tradepose experiment new research --source working:<name>
tradepose strategy show <name> --json
tradepose strategy status <name> --json
tradepose strategy check <name> --json
tradepose experiment check research --json
tradepose experiment preview 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 append-only revisions with shared periods and
ordered Working/Formal Strategy Family Members. Use
`experiment list/show/update/history/diff/restore/archive/clone`
for lifecycle operations and explicit `experiment export/import` envelopes for exchange.
Use `experiment preview --file <path|->` before saving a large definition and
`experiment edit <slug>` for an editor-driven update. Preview resolves exact Source
Revisions, Params, parameter selections, Configs, and request hashes without writing a
Run or contacting the Gateway. With explicit remote-execution authorization,
`experiment run <slug> [--detach]` creates the sole durable Run; `run resume <run-id>`
continues that same evidence root.

Use `tradepose inspect <selector>` with `strategy:<slug>`,
`source:<slug>@<version|head|working>` (or an exact source hash),
`experiment:<slug>[@revision]`, `portfolio:<slug>[@version]`, or `run:<id>` for durable
bidirectional lineage. `strategy refs --all`, `experiment refs`, and `portfolio refs`
return the same stable typed summary; their `activity` commands provide filtered,
cursor-paginated evidence. `run list` can be focused with exactly one of `--strategy`,
`--experiment`, or `--portfolio`. All discovery is SQLite-only and never contacts the
Gateway. Local Portfolio promotion selects exact selection IDs or unambiguous short
prefixes and commits Params/Policy-first
append-only versions to SQLite. Portfolio state has no watched file projection: use
`portfolio export <slug> --output <path>` when a portable snapshot is needed, and
`portfolio evaluate` to atomically create a new-period Experiment plus its typed
Portfolio-version origin without executing it.

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 lifecycle commands. Large result files live under
`results/runs/<run-id>/<task-id>/artifacts/`. Inspect local state with `run list`,
`run show`, and `run path`; `run keep`/`run unkeep` control retention, while `run remove`
removes only safe local evidence and never cancels remote work. Terminal unprotected Runs
expire after seven days; preview cleanup with `state clean` before applying it.
SQLite v10 is authoritative. A v9 workspace is migrated only through the explicit,
backed-up `tradepose state migrate` operation; migration is never implicit.

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), [Known limitations](docs/KNOWN_LIMITATIONS.md),
the [domain glossary](docs/DOMAIN_GLOSSARY.md), and the
[3.0 breaking workflow guide](docs/MIGRATION_3_0.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):
    # Params are registered before this callback; keep using the canonical graph.
    primary = params.primary

    fast = builder.col(primary.fast_sma)
    slow = builder.col(primary.slow_sma)
    atr = builder.col(primary.volatility_atr)
    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=params.opportunity.direction,
        trend=params.opportunity.trend,
        entry=entry,
        exit=exit,
    )

params = SmaParams()
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.

Construct `StrategyParams` directly from its typed Pydantic fields and 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. `StrategyRecipe.define()` registers the Data graph
before the callback; `builder.col()` resolves declarations. Each indicator owns its independent completed-bar shift.
Use `ResampledDataSource` for a typed, instrument-inheriting lower-resolution role such
as `primary` 15m → `trend` 1h; automatic registration 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

Synchronize the Gateway's complete instrument catalog into the current workspace:

```bash
tradepose instruments sync
tradepose instruments sync --if-stale 24h
tradepose instruments status --json
```

The sync writes immutable snapshots below `.tradepose/instruments/` and an
SDK-managed, IDE-indexable `tradepose_generated/` package. Both paths are added to the
managed `.gitignore` block. Generated files are checksum verified; unmanaged files,
local edits, missing snapshots, and manifest drift fail closed unless an explicit
`--repair` or `--force` operation is requested.

After a successful sync, notebooks and ordinary Python applications can use static
autocomplete and pass the canonical string leaf directly to a typed Source:

```python
from tradepose_generated.instruments import instruments

instrument = instruments.PEPPERSTONE.FUTURES.NAS100
assert instrument == "PEPPERSTONE:futures:NAS100"
```

Catalog projection imports never perform network I/O. Strategy Working Sources remain
restricted from workspace-local imports until Catalog Binding and portable lineage
evidence are implemented; keep generated catalog use in notebooks and application code
for this phase.

The legacy synchronous discovery helper remains available:

```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
- ✅ Idempotency recovery for ambiguous submissions and bounded polling/download recovery
- ✅ 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)

# 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: Read the environment explicitly
import os
tester = BatchTester(api_key=os.environ["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
