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.
Python module: from scalarlib import strategy — available only in strategy agent code
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). |
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, Polymarket outcome tokens, and Jupiter DEX tokens.
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
- 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).
- 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. |
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 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.
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.
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", "T:AAPLX", 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. |
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) |
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.
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.
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. |
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.
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.
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 |
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
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.
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
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:
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
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():
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:
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)