> ## Documentation Index
> Fetch the complete documentation index at: https://scalarfield.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Strategies

> Build automated trading agents with isolated position books, target-based order execution, and real-time NAV tracking. Each strategy manages its own cash allocation and cannot see or modify positions belonging to other strategies.

<Info>**Python module:** `from scalarlib import strategy` — available only in strategy agent code</Info>

# Product Overview

## What Is a Strategy?

A strategy is an automated trading agent that connects to one of your brokerage accounts and trades on your behalf according to its logic. Each strategy:

* Has its own **cash allocation** (initial capital you assign at creation)
* Maintains its own **position book** — completely isolated from other strategies
* Tracks its own **NAV** (net asset value) over time for performance reporting
* Runs on a schedule you define, executing Python code that uses the `strategy` module

You can run multiple strategies on the same brokerage account. Each one operates independently — it only sees its own holdings and cannot view or modify positions belonging to other strategies. This isolation is enforced by the platform.

## Capital Model

Every strategy tracks three capital metrics:

| Metric            | Description                                                                                                                                                                                                       |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `initial_capital` | The starting NAV of the strategy. Equals `allocation` if no positions are seeded, or `allocation + sum(seeded position values)` when initial positions are claimed (see [Seeding Positions](#seeding-positions)). |
| `cash`            | Available cash to spend. Starts at `initial_capital`, decreases on buys, increases on sells. Grows with profits.                                                                                                  |
| `nav`             | Net asset value: `cash + sum(position market values)`. The total value of the strategy.                                                                                                                           |

## Seeding Positions

When creating a strategy, you can optionally **seed it with positions you already hold at the broker**. Instead of starting from zero and buying everything from scratch, the strategy claims existing shares and begins managing them immediately.

This is useful when you already have a portfolio at your brokerage and want a strategy to take over management of some or all of those holdings. You specify which positions and how many shares to seed — the platform handles the rest. This works across all supported venues: equities on [Alpaca](/docs/trading/venues/alpaca), [Polymarket](/docs/trading/venues/polymarket) outcome tokens, [Jupiter DEX](/docs/trading/venues/jupiter) tokens, and long [Hyperliquid](/docs/trading/venues/hyperliquid) positions (seeding is long-only; short perps cannot be seeded).

### Reconciliation at Approval

Before a strategy with seeded positions can go active, the platform runs a reconciliation check against your live broker state. This ensures everything is consistent before the strategy starts trading.

The reconciliation verifies:

* **Positions exist at the broker.** Each symbol you want to seed must be an actual holding in your brokerage account. If the symbol is not found, approval is rejected.
* **Sufficient shares are available.** The broker must hold at least as many shares as you want to seed. If you request 10 shares of AAPL but only hold 6, approval fails.
* **Shares are not already claimed by other strategies.** Since multiple strategies can share a brokerage account, the platform tracks which shares are "claimed" by each strategy. If another strategy on the same account has already claimed some of those shares, only the remaining unclaimed shares are available. If there aren't enough unclaimed shares, approval fails with a message showing how many are already claimed.
* **A live price can be resolved.** The platform fetches a live price from the broker for each position. This is needed to compute the strategy's starting capital. If the market is closed or the symbol is invalid and no price can be determined, approval fails.

If all checks pass, positions are written to the strategy's ledger and `initial_capital` is computed as `allocation + sum(qty x price)` of all seeded positions. After seeding, `strategy.state()` immediately reflects the claimed positions — no trades are needed.

If **any** position fails reconciliation, the entire operation is rolled back. No positions are seeded and the strategy remains in a pending state so you can fix the issue and retry.

### Reconciliation Errors

| Error                                  | What It Means                                                                                                                                                                                                  | What to Do                                                                                                                                       |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Position not found at broker**       | The symbol you specified is not held in your brokerage account.                                                                                                                                                | Verify the symbol is correct and that you actually hold it at the broker before retrying.                                                        |
| **Insufficient shares at broker**      | You requested more shares than the broker holds. For example, you asked to seed 10 shares of AAPL but the broker only holds 6.                                                                                 | Reduce the seed quantity to match or be below what you actually hold.                                                                            |
| **Shares claimed by other strategies** | Other strategies on the same brokerage account have already claimed some of those shares, leaving fewer available than you requested. The error message shows how many are claimed and how many are available. | Either reduce the seed quantity to fit within available shares, or free up shares by adjusting or deleting the other strategy that claimed them. |
| **Cannot determine price**             | The platform could not fetch a live price for this symbol — the market may be closed or the symbol may be invalid. A price is required to compute `initial_capital`.                                           | Retry when the market is open, or verify the symbol is valid and actively traded.                                                                |

### Immutability

Seeded positions can be adjusted freely while the strategy is still **pending approval**. Each edit re-validates against the broker. Once the strategy is **approved and active**, seeded positions and allocation are locked. To change them, delete the strategy and create a new one.

## Strategy Isolation

```mermaid theme={null}
flowchart TB
    subgraph broker ["Your Brokerage Account (e.g. Alpaca Paper)"]
        direction TB
        subgraph stratA ["Strategy A"]
            posA["Positions: AAPL 10, MSFT 5\nCash: $5,000"]
        end
        subgraph stratB ["Strategy B"]
            posB["Positions: NVDA 20\nCash: $3,000"]
        end
        subgraph stratC ["Strategy C"]
            posC["Positions: TSLA 15\nCash: $2,000"]
        end
    end
    aggregate["Platform Aggregate: AAPL 10, MSFT 5, NVDA 20, TSLA 15"]
    brokerActual["Broker Actual Holdings"]
    broker --> aggregate
    aggregate -->|"verified against"| brokerActual
```

* Each strategy has its own rows in the platform's position ledger, isolated by strategy identity.
* The platform sums all strategy positions to produce an **aggregate** and continuously reconciles it against the broker's actual holdings (see [Reconciliation](/docs/trading/reconciliation)).
* A strategy **cannot** read sibling strategies' positions, cash, or trade history.

## Order Execution

Strategies place trades by specifying a **target position size** for a symbol. The platform computes the required buy or sell delta, checks buying power, and routes the order to the brokerage.

| Venue                    | Execution Model  | Details                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Alpaca Paper / Live      | **Asynchronous** | Orders are submitted as `day` time-in-force. Returns `PENDING` immediately. Fills are tracked automatically and applied when the broker confirms. Orders submitted outside market hours (before 9:30 AM or after 4:00 PM ET) queue until the next open.                                                                                                                                                                                   |
| Polymarket               | **Synchronous**  | Blocks until the fill is confirmed. Returns `FILLED` with positions and cash updated immediately. Trades 24/7.                                                                                                                                                                                                                                                                                                                            |
| Jupiter DEX              | **Synchronous**  | Same as Polymarket — blocks until confirmed. Trades 24/7.                                                                                                                                                                                                                                                                                                                                                                                 |
| Hyperliquid Live / Paper | **Synchronous**  | Blocks until the fill is confirmed. Trades 24/7. Perp targets are **signed** — a negative target opens or maintains a short. Hourly funding payments and exchange liquidations on the shared account are attributed to each agent's book automatically — see [Hyperliquid Agent Architecture](/docs/trading/venues/hyperliquid-agents). Paper fills are simulated against the live order book with the same funding and liquidation mechanics. |

## Strategy Lifecycle

| State      | Meaning                                                                                                                                                                                                                       |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Active** | Trading normally. The strategy executes on its schedule and can place orders.                                                                                                                                                 |
| **Frozen** | Automatically paused because a position discrepancy was detected between the strategy's book and the broker's actual holdings. No new orders are accepted. See [Reconciliation](/docs/trading/reconciliation) for how to unfreeze. |
| **Paused** | Manually stopped by you. The strategy will not execute until you resume it.                                                                                                                                                   |

## NAV Tracking

Strategy NAV is computed as `cash + sum(position market values)` using live prices from the broker. Historical NAV is recorded over time, enabling performance charts, win rate calculations, and P\&L summaries on the strategy detail page.

# Strategy Functions Reference

The `strategy` module is available **only** inside strategy agent code (the `manageAutomatedAgent` tool). Do not use `strategy.*` in chat code execution — use `venue.*` instead.

All timestamps are in New York time, timezone-naive (format: `"YYYY-MM-DD HH:MM:SS"`).

## `strategy.state()`

Returns the current strategy snapshot. **Reconciles all pending broker orders before returning**, so positions and cash are always up to date.

```python theme={null}
from scalarlib import strategy

state = strategy.state()
if state["tradable"]:
    cash = state["capital"]["cash"]
    nav = state["capital"]["nav"]
    positions = state["positions"]
    pending = state["pending"]
```

### Return Schema

| Field                     | Type           | Description                                                                      |
| ------------------------- | -------------- | -------------------------------------------------------------------------------- |
| `ts`                      | string         | Snapshot timestamp (New York time)                                               |
| `tradable`                | bool           | `true` if the strategy can place orders; `false` if frozen or blocked            |
| `block_reason`            | string or null | Why the strategy cannot trade (e.g. `"STRATEGY_FROZEN_RECONCILIATION_MISMATCH"`) |
| `capital.initial_capital` | float          | Starting budget                                                                  |
| `capital.cash`            | float          | Available cash                                                                   |
| `capital.nav`             | float          | Net asset value                                                                  |
| `positions`               | list           | Current holdings (see below)                                                     |
| `pending`                 | list           | In-flight orders not yet terminal (see below)                                    |

**Position fields:**

| Field            | Type   | Description            |
| ---------------- | ------ | ---------------------- |
| `symbol`         | string | Ticker symbol          |
| `qty`            | float  | Shares held            |
| `avg_price`      | float  | Average entry price    |
| `market_value`   | float  | Current market value   |
| `unrealized_pnl` | float  | Unrealized profit/loss |
| `side`           | string | `"long"`               |

**Pending order fields:**

| Field             | Type   | Description                                           |
| ----------------- | ------ | ----------------------------------------------------- |
| `trade_id`        | string | Internal trade identifier                             |
| `symbol`          | string | Ticker symbol                                         |
| `broker_order_id` | string | Broker's order identifier                             |
| `target_qty`      | float  | Desired target position                               |
| `filled_qty`      | float  | Quantity filled so far (may be > 0 for partial fills) |
| `status`          | string | `"PENDING"`                                           |
| `ts`              | string | Order submission timestamp                            |

***

## `strategy.execute()`

Sets the **target position** for a single symbol. The platform computes the delta from the current position and places a broker order only if needed. **Idempotent** — calling `execute("AAPL", 10)` twice is a no-op the second time.

```python theme={null}
from scalarlib import strategy

# Buy 10 shares of AAPL (target position = 10)
resp = strategy.execute("AAPL", 10)

# Sell everything (target = 0)
resp = strategy.execute("AAPL", 0)

# After executing, always read actual state
state = strategy.state()
```

### Parameters

| Parameter      | Type  | Required | Description                                                                                                                                                                                                                                                                                                                                        |
| -------------- | ----- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbol`       | str   | Yes      | Ticker to trade (e.g. `"AAPL"`, Jupiter `"J:<mint>"` ticker, Polymarket token ID)                                                                                                                                                                                                                                                                  |
| `qty`          | float | Yes      | **Target** position size. `10` = own 10 shares. `0` = flatten. This is not a delta — the platform computes the required buy/sell automatically.                                                                                                                                                                                                    |
| `slippage_bps` | int   | No       | Slippage tolerance in basis points. Applies to Jupiter swaps (default 50 = 0.5%), Polymarket live and paper (default 500 = 5%), and Hyperliquid live and paper market orders (default 50 = 0.5%). Ignored on other venues. Orders exceeding the tolerance fail with `EXCESSIVE_SLIPPAGE`. Also accepted by `strategy.buy()` and `strategy.sell()`. |

### Return Schema

**On success** (`FILLED` or `PENDING`):

| Field             | Type           | Description                                             |
| ----------------- | -------------- | ------------------------------------------------------- |
| `status`          | string         | `"FILLED"` (synchronous venues) or `"PENDING"` (Alpaca) |
| `symbol`          | string         | Ticker symbol                                           |
| `target_qty`      | float          | Requested target position                               |
| `current_qty`     | float          | Position before this order                              |
| `delta`           | float          | Computed buy/sell quantity                              |
| `broker_order_id` | string         | Broker's order identifier                               |
| `filled_qty`      | float          | Quantity filled (0 for PENDING)                         |
| `avg_price`       | float or null  | Average fill price (null for PENDING)                   |
| `reason`          | string or null | Additional context                                      |

**On rejection** (`BLOCKED` or `FAILED`):

| Field    | Type   | Description                        |
| -------- | ------ | ---------------------------------- |
| `status` | string | `"BLOCKED"` or `"FAILED"`          |
| `symbol` | string | Ticker symbol                      |
| `reason` | string | Rejection reason (see table below) |

<Info>On `BLOCKED` or `FAILED`, only `status`, `symbol`, and `reason` are present. Fields like `delta`, `filled_qty`, `avg_price`, and `broker_order_id` will be missing. Always use `.get()` to access these fields safely.</Info>

### Block / Rejection Reasons

| Reason                                    | Description                                                                    |
| ----------------------------------------- | ------------------------------------------------------------------------------ |
| `INSUFFICIENT_CASH`                       | Buy cost exceeds strategy cash                                                 |
| `INSUFFICIENT_BUYING_POWER`               | Buy cost exceeds broker/venue funds                                            |
| `ORDER_BELOW_MINIMUM`                     | Order value below \$1.00 minimum, or below venue-specific minimum (Polymarket) |
| `PENDING_ORDER_EXISTS`                    | A pending order for this symbol already exists                                 |
| `EXCESSIVE_SLIPPAGE`                      | Order book worst price too far from last trade                                 |
| `STRATEGY_FROZEN_RECONCILIATION_MISMATCH` | Broker holdings below the strategy's protected floor — strategy is frozen      |

***

## `strategy.liquidate()`

Flattens strategy positions by calling `execute(symbol, 0)` for each symbol.

```python theme={null}
from scalarlib import strategy

# Flatten a single symbol
strategy.liquidate(["AAPL"])

# Flatten everything
strategy.liquidate()
```

### Parameters

| Parameter      | Type        | Required | Description                                                                                                                                            |
| -------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `symbols`      | list of str | No       | Symbols to flatten. `None` or omitted = flatten all positions.                                                                                         |
| `slippage_bps` | int         | No       | Slippage tolerance in basis points, applied to each flattening order (same venue defaults as `execute()`; ignored on venues without slippage control). |

### Return Schema

| Field     | Type           | Description                                              |
| --------- | -------------- | -------------------------------------------------------- |
| `status`  | string         | `"ok"`                                                   |
| `reason`  | string or null | Additional context                                       |
| `results` | list           | One entry per symbol with the same schema as `execute()` |

***

## `strategy.cancel()`

Cancels a pending broker order. Reconciles first to check if the order has already filled. If the order was partially filled, the filled portion is applied to positions and cash — only the unfilled remainder is cancelled.

```python theme={null}
from scalarlib import strategy

resp = strategy.execute("AAPL", 10)
if resp["status"] == "PENDING":
    cancel_resp = strategy.cancel(resp["broker_order_id"])
```

### Parameters

| Parameter         | Type | Required | Description                                             |
| ----------------- | ---- | -------- | ------------------------------------------------------- |
| `broker_order_id` | str  | Yes      | The `broker_order_id` from a `PENDING` execute response |

### Return Schema

| Field        | Type   | Description                                                                             |
| ------------ | ------ | --------------------------------------------------------------------------------------- |
| `status`     | string | `"CANCELLED"` (clean cancel) or `"PARTIALLY_FILLED"` (some shares filled before cancel) |
| `trade_id`   | string | Internal trade identifier                                                               |
| `symbol`     | string | Ticker symbol                                                                           |
| `filled_qty` | float  | Shares filled before cancellation (0 for clean cancel)                                  |
| `avg_price`  | float  | Average fill price of the filled portion                                                |

***

## `strategy.activity()`

Returns the strategy's event stream, sorted newest first.

```python theme={null}
from scalarlib import strategy

# Recent events
events = strategy.activity(limit=10)

# Filter fills
fills = [e for e in events if e["kind"] in ("ORDER_FILLED", "ORDER_PARTIALLY_FILLED")]

# Events for a specific symbol
aapl_events = strategy.activity(symbol="AAPL")
```

### Parameters

| Parameter | Type | Required | Description                        |
| --------- | ---- | -------- | ---------------------------------- |
| `limit`   | int  | No       | Maximum number of events to return |
| `symbol`  | str  | No       | Filter events by symbol            |

### Event Kinds

| Kind                     | Description                                                                                                                    |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `ORDER_PLACED`           | Order submitted to broker                                                                                                      |
| `ORDER_FILLED`           | Order fully filled at broker                                                                                                   |
| `ORDER_PARTIALLY_FILLED` | Order partially filled then cancelled/expired — filled portion applied to positions                                            |
| `ORDER_CANCELLED`        | Order cancelled with zero fills                                                                                                |
| `FUNDING_ACCRUED`        | Hyperliquid hourly funding applied to strategy cash — see [Hyperliquid Agent Architecture](/docs/trading/venues/hyperliquid-agents) |
| `LIQUIDATION_APPLIED`    | Exchange (forced) liquidation attributed to this strategy's book at the actual liquidation price (Hyperliquid)                 |
| `FROZEN`                 | Strategy frozen (broker holdings below protected floor)                                                                        |
| `UNFROZEN`               | Strategy unfrozen (positions realigned to broker)                                                                              |

# Strategy Code Best Practices

When writing strategy agent code, follow these patterns to avoid common pitfalls:

**1. Always check state before trading**

```python theme={null}
state = strategy.state()
if not state["tradable"]:
    print(f"Cannot trade: {state['block_reason']}")
    return
```

**2. Check for pending orders before placing a new one**

Only one pending order per symbol is allowed. If a pending order exists, wait for it to fill or cancel it.

```python theme={null}
pending_symbols = {p["symbol"] for p in state["pending"]}
if "AAPL" in pending_symbols:
    print("AAPL order already pending, skipping")
```

**3. Get live prices — never hardcode**

```python theme={null}
from scalarlib import getLatestPrice
price_data = getLatestPrice(["AAPL"])
price = price_data["AAPL"]["price"]
```

**4. `execute()` takes a target position, not a delta**

To increase a position, read the current quantity and add:

```python theme={null}
current_qty = next((p["qty"] for p in state["positions"] if p["symbol"] == "AAPL"), 0)
strategy.execute("AAPL", current_qty + 5)  # buy 5 more
```

**5. Size from cash with a buffer for fees and slippage**

```python theme={null}
cash = state["capital"]["cash"]
max_qty = round(cash / price * 0.95, 2)  # 5% buffer
```

**6. Use fractional shares — never truncate with `int()`**

`int(285 / 400)` returns 0, silently skipping the position. Use `round()`:

```python theme={null}
qty = round(target_value / price, 2)
if qty < 0.01:
    print("Order too small, skipping")
```

**7. Always use `strategy.state()` as the source of truth**

Never track positions, balances, or filled quantities in `state.json`. The broker is the source of truth, and `strategy.state()` reconciles with the broker before returning. After every `execute()` call, re-read state:

```python theme={null}
resp = strategy.execute("AAPL", 10)
state = strategy.state()  # always re-read
actual_qty = next((p["qty"] for p in state["positions"] if p["symbol"] == "AAPL"), 0)
```
