Skip to main content

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:
MetricDescription
initial_capitalThe 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).
cashAvailable cash to spend. Starts at initial_capital, decreases on buys, increases on sells. Grows with profits.
navNet 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

ErrorWhat It MeansWhat to Do
Position not found at brokerThe 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 brokerYou 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 strategiesOther 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 priceThe 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.
VenueExecution ModelDetails
Alpaca Paper / LiveAsynchronousOrders 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.
PolymarketSynchronousBlocks until the fill is confirmed. Returns FILLED with positions and cash updated immediately. Trades 24/7.
Jupiter DEXSynchronousSame as Polymarket — blocks until confirmed. Trades 24/7.

Strategy Lifecycle

StateMeaning
ActiveTrading normally. The strategy executes on its schedule and can place orders.
FrozenAutomatically 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.
PausedManually stopped by you. The strategy will not execute until you resume it.
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

FieldTypeDescription
tsstringSnapshot timestamp (New York time)
tradablebooltrue if the strategy can place orders; false if frozen or blocked
block_reasonstring or nullWhy the strategy cannot trade (e.g. "STRATEGY_FROZEN_RECONCILIATION_MISMATCH")
capital.initial_capitalfloatStarting budget
capital.cashfloatAvailable cash
capital.navfloatNet asset value
positionslistCurrent holdings (see below)
pendinglistIn-flight orders not yet terminal (see below)
Position fields:
FieldTypeDescription
symbolstringTicker symbol
qtyfloatShares held
avg_pricefloatAverage entry price
market_valuefloatCurrent market value
unrealized_pnlfloatUnrealized profit/loss
sidestring"long"
Pending order fields:
FieldTypeDescription
trade_idstringInternal trade identifier
symbolstringTicker symbol
broker_order_idstringBroker’s order identifier
target_qtyfloatDesired target position
filled_qtyfloatQuantity filled so far (may be > 0 for partial fills)
statusstring"PENDING"
tsstringOrder 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

ParameterTypeRequiredDescription
symbolstrYesTicker to trade (e.g. "AAPL", "T:AAPLX", Polymarket token ID)
qtyfloatYesTarget 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):
FieldTypeDescription
statusstring"FILLED" (synchronous venues) or "PENDING" (Alpaca)
symbolstringTicker symbol
target_qtyfloatRequested target position
current_qtyfloatPosition before this order
deltafloatComputed buy/sell quantity
broker_order_idstringBroker’s order identifier
filled_qtyfloatQuantity filled (0 for PENDING)
avg_pricefloat or nullAverage fill price (null for PENDING)
reasonstring or nullAdditional context
On rejection (BLOCKED or FAILED):
FieldTypeDescription
statusstring"BLOCKED" or "FAILED"
symbolstringTicker symbol
reasonstringRejection 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

ReasonDescription
INSUFFICIENT_CASHBuy cost exceeds strategy cash
INSUFFICIENT_BUYING_POWERBuy cost exceeds broker/venue funds
ORDER_BELOW_MINIMUMOrder value below $1.00 minimum, or below venue-specific minimum (Polymarket)
PENDING_ORDER_EXISTSA pending order for this symbol already exists
EXCESSIVE_SLIPPAGEOrder book worst price too far from last trade
STRATEGY_FROZEN_RECONCILIATION_MISMATCHBroker 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

ParameterTypeRequiredDescription
symbolslist of strNoSymbols to flatten. None or omitted = flatten all positions.

Return Schema

FieldTypeDescription
statusstring"ok"
reasonstring or nullAdditional context
resultslistOne 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

ParameterTypeRequiredDescription
broker_order_idstrYesThe broker_order_id from a PENDING execute response

Return Schema

FieldTypeDescription
statusstring"CANCELLED" (clean cancel) or "PARTIALLY_FILLED" (some shares filled before cancel)
trade_idstringInternal trade identifier
symbolstringTicker symbol
filled_qtyfloatShares filled before cancellation (0 for clean cancel)
avg_pricefloatAverage 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

ParameterTypeRequiredDescription
limitintNoMaximum number of events to return
symbolstrNoFilter events by symbol

Event Kinds

KindDescription
ORDER_PLACEDOrder submitted to broker
ORDER_FILLEDOrder fully filled at broker
ORDER_PARTIALLY_FILLEDOrder partially filled then cancelled/expired — filled portion applied to positions
ORDER_CANCELLEDOrder cancelled with zero fills
FROZENStrategy frozen (broker holdings below protected floor)
UNFROZENStrategy 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)