Metadata-Version: 2.4
Name: playtrader-sdk
Version: 0.1.0
Summary: Typed PocketBase client and CLI for Playtrader
Requires-Python: >=3.12
Requires-Dist: loguru>=0.7.2
Requires-Dist: pydantic>=2.9.0
Requires-Dist: requests>=2.31.0
Requires-Dist: typer>=0.12.3
Provides-Extra: dev
Requires-Dist: pytest>=8.2; extra == 'dev'
Description-Content-Type: text/markdown

# Playtrader SDK

Typed Python client for the Playtrader PocketBase APIs. Install it from PyPI
(``pip install playtrader-sdk``) or straight from GitHub
(``pip install git+https://github.com/hampusadamsson/playtrader-sdk.git``) and
you immediately gain:

- A synchronous `PlaytraderClient` that wraps the `asset`, `history`,
  `portfolio`, `holding`, `transaction`, and `/api/trade` endpoints
- Fully-typed response models (Pydantic) that keep agents honest when handling
  live data
- Enum-based helpers (`IdentifierType`) for juggling tickers, orderbook ids, and
  PocketBase `Symbol` values

> **Demo Environment** – Everything in this README uses the hosted sandbox at
> `https://playtrader.hampusadamsson.com`.

## Quick Start

```python
from playtrader import PlaytraderClient
from playtrader.client import IdentifierType

client = PlaytraderClient()

# List assets
assets = client.list_assets(limit=5)
for asset in assets:
    print(asset.ticker, asset.ask_price)

# Fetch history for a ticker (auto-converted from IdentifierType)
hist = client.get_history("ABB", identifier_type=IdentifierType.TICKER, limit=10)
print(hist[0].updt, hist[0].close_price)

# Authenticate and inspect holdings
# Create an account at: https://playtrader.hampusadamsson.com/register

client.auth("username", "password")
portfolio = client.get_portfolio("4b055zbbu920354", expand=["holdings"])

print(portfolio.name, portfolio.funds)

# Execute a trade (be sure you're OK with live demo trades!)
# client.buy("4b055zbbu920354", "ABB", 1)
```

## Usage Guide

1. **Install**

   ```bash
   pip install playtrader-sdk
   ```

2. **Initialize the client**

   ```python
   from playtrader_sdk import PlaytraderClient
   client = PlaytraderClient()
   ```

3. **List tradable assets**

   ```python
   assets = client.list_assets(limit=10)
   for asset in assets:
       print(asset.ticker, asset.ask_price)
   ```

4. **Authenticate for portfolio endpoints**

   ```python
   client.auth("username", "password")
   ```

   Once authenticated you can read private data:

   ```python
   holdings = client.get_holdings("4b055zbbu920354")
   funds = client.get_available_funds("4b055zbbu920354")
   ```

5. **Trade carefully in the demo sandbox**

   ```python
   client.buy("4b055zbbu920354", "ABB", 1)
   client.sell("4b055zbbu920354", "ABB", 1)
   ```

   These calls mutate the shared PocketBase instance—stick to tiny sizes.

### Common Tasks

| Task | Snippet |
|------|---------|
| Convert tickers ↔ symbols | `client.transform_symbols_to_tickers([...])` |
| Fetch last 10 candles for a ticker | `client.get_history("ABB", identifier_type=IdentifierType.TICKER, limit=10)` |
| Snapshot current ask prices | `client.get_prices(["ABB", "ERIC"])` |
| Summarize a portfolio | `client.get_portfolio("4b055zbbu920354", expand=["holdings"])` |
| Update portfolio description | `client.set_portfolio_description(portfolio_id, "New plan")` |

## Feature Matrix

| Capability | Methods | Notes |
|------------|---------|-------|
| Asset discovery | `list_assets`, `get_asset`, `get_symbols` | Returns `Asset` models with metadata. |
| Symbol/ticker conversion | `transform_symbols_to_tickers`, `transform_tickers_to_symbols`, `resolve_symbol` | Uses cached metadata, supports explicit `IdentifierType`. |
| Historical candles | `get_history` | Works with tickers, orderbook ids, or explicit Symbol id. |
| Portfolio insights | `get_portfolio`, `get_holdings`, `get_portfolio_history`, `get_transactions`, `get_available_funds` | Requires authentication; returns typed models. |
| Trading | `buy`, `sell` | Wraps `/api/trade`, returns `TradeResponse`. |
| Price snapshots | `get_prices` | Input identifiers normalized to tickers; output keys always tickers. |
| Basket view | `get_omxs30` | Builds a merged dataframe-like payload (`Omxs30Row`). |

## Schema Types

Every response is a Pydantic model living in `playtrader/schemas/`:

| Model | File | Key Fields |
|-------|------|------------|
| `Asset` | `schemas/asset.py` | `ticker`, `orderbook_id`, `ask_price`, `bid_price`, `sector`, `updt`. |
| `HistoryRecord` | `schemas/history.py` | `symbol`, `updt`, `close_price`. |
| `Holding` / `HoldingExpand` | `schemas/holding.py` | `quantity`, `average_purchase_price`, `expand.asset`. |
| `Portfolio` / `PortfolioExpand` | `schemas/portfolio.py` | `funds`, `name`, `holding_ids`, `expand.holdings`. |
| `PortfolioHistoryEntry` | `schemas/portfolio_history.py` | `date`, `value`. |
| `Transaction` / `TransactionExpand` | `schemas/transaction.py` | `action`, `price`, `quantity`, `expand.asset`. |
| `TradeResponse` | `schemas/trade.py` | `status`, `message`, `trade_id`. |
| `Omxs30Row` | `schemas/omxs30.py` | `timestamp`, `prices` (dict of `symbol -> latest value`). |

These models are imported in `playtrader/__init__.py` so that agents can
`from playtrader.schemas import Asset, Portfolio, ...` if they only have access
to the README and the ability to `pip install playtrader-sdk`.

## Identifier Management

The client exposes an `IdentifierType` enum with three values:

- `IdentifierType.TICKER`
- `IdentifierType.SYMBOL` (PocketBase `Symbol`/Orderbook id)
- `IdentifierType.ORDERBOOK`

Most helper methods accept an optional `identifier_type`. If omitted the client
tries `TICKER` first and falls back to `SYMBOL`, mirroring the legacy code in
the repository example.

## Testing Against the Demo API

```bash
uv run --extra dev python -m pytest
```

- All tests talk to the live demo API and use the credentials.
- Real trading tests are skipped unless `PLAYTRADER_ALLOW_TRADE=1` is set.

### Manual Smoke Test

If you just need to double-check authentication, run:

```bash
uv run python - <<'PY'
from playtrader_sdk import PlaytraderClient
client = PlaytraderClient()
token = client.auth("username", "password")
print("Token chars:", len(token))
PY
```

Expect a token length > 10. Any `AuthenticationError` typically means the demo
credentials rotated or the service is temporarily unavailable.

## Release Management / Wheel Builds

Wheel builds use Hatch via `uv`:

```bash
bash scripts/release.sh
# -> dist/playtrader_sdk-<version>-py3-none-any.whl
```

The script wipes `dist/` and runs `uv build --wheel`, so it’s safe to use in CI
as-is. Upload artifacts with `twine upload dist/*.whl` (or copy them to your
internal registry).

## Helpful References

- `playtrader/client.py`: Detailed docstrings for every public method, with
  examples showing authentication, history lookups, identifier usage, and trades
- `tests/test_playtrader.py`: Live integration tests you can mimic for agents
- [PocketBase records API docs](https://pocketbase.io/docs/api-records/) for raw
  REST details

If your agent only has this README and can run `pip install playtrader-sdk`, it
already has everything needed to authenticate, list assets, fetch history, and
trade against the demo environment. Just import `PlaytraderClient` and follow
the snippets above.

## CLI Usage

`pip install playtrader-sdk` now ships a `playtrader` executable for zero-code
exploration. It wraps the same client and respects the demo credentials above.

```bash
# Authenticate once (token cached in ~/.config/playtrader/token.json)
playtrader auth --email username --password pwd 

# List assets (table or JSON)
playtrader assets list --limit 5
playtrader assets list --limit 2 --json

# Inspect private data (requires auth token)
playtrader portfolio 4b055zbbu920354
playtrader holdings 4b055zbbu920354 --json

# Fetch candles / prices / OMXS30 snapshots
playtrader history ABB --identifier-type ticker --limit 5
playtrader prices ABB ERIC
playtrader omxs30 ABB ERIC --identifier-type ticker --json

# Execute demo trades (mutates shared sandbox!)
playtrader trade buy 4b055zbbu920354 ABB 1
```

Flags common to every command:

- `--base-url`, `--per-page`, `--timeout` to match custom deployments
- `--use-token/--no-token` to control whether cached JWTs are loaded
- `--json/--no-json` on data commands for machine-friendly output

Set `PLAYTRADER_CONFIG_DIR=/tmp/playtrader` to override the token cache path.

---

# Testing the SDK in a Notebook

uvx --with jupyter --with pandas --with ./dist/playtrader_sdk-0.1.0-py3-none-any.whl jupyter notebook --NotebookApp.token='123'
