Pitfalls
The mistakes that most often make a first draft fail validation, or pass it and then do the wrong thing. Check your code against this list before calling validate_agent_code.
Before you submit
- No
import, no wrapper function, no redeclared built-in name (RSI,News,Trade,manager, …). - Every indicator,
getIntervalDataand listener is created at the root; everygetValue(), price read, order and notification is inside a callback. - Every symbol you trade or price from a
cronis subscribed at the root (manager.onPreciseCandle(symbol, () => {})or a candle series). - Intervals are minutes (
60,240,1440) or'week'/'month'; daily and longer listeners pass the asset's time zone; schedules pass the zone of their wall-clock time. - Every value that can be missing is checked:
hasValue()beforegetValue(), then=== undefinedon numbers and on each object field you use;getCurrentPricecan beundefined. - Indicator inputs match the signature:
new RSI(candles.close, 14), notnew RSI(candles, 14);BollingerandRSIwant a stream that is neverundefined(candles.close), not another indicator's output. Check the Indicator reference. - Label values are compared with labels:
Crossovergives'firstUp' | 'secondUp' | null, not a number. - Orders:
position: 'buy' | 'sell';qtyoramount; everyamounthasamountCurrency;LIMITuseslimitPrice(onplaceOrder) orprice(onopenTrade). - Trades:
positionSizeis an object ({ type: 'proportion', proportion: 0.1 }); stop and take-profitvalues are prices, not distances;closeTrade(trade, { cause })takes onlycauseanddelta. -
inhibitAutoInvest: trueon orders that spend existing cash (sized from a capital reading, rebalances, sells); none on fixed contribution buys. - Rebalances trade the difference from target within one decremented cash budget.
-
getStatus<MyState>('default')with an interface whose fields are all optional; initialised before use. -
getVariable('name', literal)called directly, once per name, at the root. - No
Date.now(),new Date(),DateTime.now(),setTimeout,fetch,process,globalThis,as any,@ts-ignore. - Function parameters are typed; empty arrays are typed (
const lines: string[] = []). - Only ES2020 library methods: no
.at(),replaceAll,Object.hasOwn,findLast. -
manager.llmon a schedule or candle claims a period key fromgetStatusfirst. - At most one
sendNotificationper callback run, and only if the user asked for alerts.
Behaviour traps that pass validation
A schedule never fires at launch. "Buy now, then weekly" needs a launch catch-up (see Acting at launch). A restart does not replay a missed occurrence either.
A schedule on a candle boundary runs before the candle closes. A '0 0 * * *' schedule reads yesterday's daily indicators. Use onIntervalCandle, or schedule a minute later.
Backtests start cold. Indicators are empty on the backtest start date: a 200-day average needs 200 days of the window before it has a value.
A condition that stays true fires every time. Without a latch in the status bag, "RSI below 30" re-alerts or re-buys on every candle it stays below 30.
Comparing the current value with a window that contains it. close > highest high of the last 20 candles is never true when the window includes the current candle. Exclude it with slice(0, -1).
Extreme and DC include the current candle, and DC is the channel's midline. For Donchian upper and lower bands, use History (or Extreme on the previous candle).
A missing value becomes 0. price ?? 0 or Number(undefined) turns "no data" into a price of zero, which satisfies every "below" condition. Skip the cycle instead.
Buying the whole target again at each rebalance. Portfolio value includes what you already hold; buying value × weight compounds exposure. Trade the difference.
Sizing a fixed contribution on free cash. "Buy $100 a week" sized on getAvailableCapital() buys all the idle cash the first time.
Sizing by getCurrentCapital(). It does not go down when you buy: it is not free cash. Use getAvailableCapital().
Deciding "am I in a position?" on the portfolio. getPositionQty(symbol) counts everything the portfolio holds, your own shares and other agents' included. A strategy gated on getPositionQty(symbol) === 0 never buys on a portfolio that already holds the symbol, and a sell of getPositionQty(symbol) sells shares the agent never bought. A backtest cannot show it: there the portfolio is the agent's alone. Test the agent's own trades, or its own fills (TS9047).
Exiting a position you did not open with an opposite order. Use closePosition(symbol): on a live account an opposite order can open a reverse position if the position changed meanwhile.
risk sizing without a stop. It then behaves like proportion, and a very tight stop asks for a very large position.
Trusting the status bag alone for orders. It is saved about every minute; after a crash the last changes can be lost. Also check getPositions() and pending orders before placing again.
handleError in a backtest fails the run. It is right for real failures (a data outage), not for expected conditions such as "no signal".
Ratios are fractions. A 20% margin from getPieuvreMetric is 0.20: margin > 20 never fires.
An unknown ranking universe returns nothing. universe: 'US' silently returns []; use a real tag such as 'us_equities' or 'sp500'.
Trading a ticker string. A calendar ticker, a model's answer or a name is not a trading key. Trade declared symbols, rank keys, resolveAssets keys or search_assets keys.
Showing internal keys to the user. Run-time trading keys are opaque identifiers. Name assets by ticker, name or your own symbol.
A US stock order at 09:00 New York. It is pre-market. Schedule at 09:31 or later, and gate catch-up schedules on the session.
Backtest or demo
A backtest replays history on a fresh paper wallet. These agents cannot be backtested and are validated in a demo run instead:
- agents that read or manage the positions you already hold (a backtest wallet starts empty);
- agents using
searchNews,onNews,getPieuvreSeries,getPieuvreUpcomingoronPieuvreEvent; - agents calling
manager.llmfrom a schedule or candle callback (a model call in a backtest only works insideonMessageon an X account); - agents following a Telegram channel, or an X handle that is not a literal;
- agents with no declared asset whose universe does not come from
getPieuvreRank; - alert-only agents that never trade.
getPieuvreRank and getPieuvreMetric universes, and X follows with a literal handle, are backtestable, point in time. A schedule that fires every minute makes long backtests too expensive: prefer hourly or coarser schedules for recurring work.
Also note that sendNotification, sendWebhook and comment do nothing in a backtest: use debugFlag and debugLog to see what a backtest did.