بنقرة واحدة
superior-trade
// Backtest and deploy trading strategies on Superior Trade's managed cloud.
// Backtest and deploy trading strategies on Superior Trade's managed cloud.
| name | Superior Trade |
| version | 4.4.6 |
| updated | "2026-05-18T00:00:00.000Z" |
| description | Backtest and deploy trading strategies on Superior Trade's managed cloud. |
| homepage | https://account.superior.trade |
| source | https://github.com/Superior-Trade |
| primaryEnv | SUPERIOR_TRADE_API_KEY |
| auth | {"type":"api_key","env":"SUPERIOR_TRADE_API_KEY","header":"x-api-key","scope":"Read-write the user's own backtests and deployments. Can start live trading deployments that execute real trades with the user's platform-managed trading wallet. Cannot withdraw funds, export private keys, or access other users' data."} |
| env | [{"name":"SUPERIOR_TRADE_API_KEY","description":"Superior Trade API key (x-api-key header). Obtained at https://account.superior.trade. Can create/manage backtests and deployments including live trading. Cannot withdraw funds, export private keys, or access other users' data. Users do not need their own Hyperliquid wallet.","required":true,"type":"api_key"}] |
| externalEndpoints | [{"url":"https://api.superior.trade","purpose":"All backtesting and deployment operations"},{"url":"https://api.hyperliquid.xyz/info","purpose":"Read-only public queries. Balance checks send the user's public wallet address (not a secret — visible on-chain). Pair validation sends no user data. No authentication or secrets are sent to this endpoint."}] |
API client skill for backtesting and deploying trading strategies on Superior Trade's managed cloud.
Base URL: https://api.superior.trade
Auth: x-api-key header on all protected endpoints
Docs: GET /docs (Swagger UI), GET /openapi.json (OpenAPI spec)
IMPORTANT: The correct URL is https://account.superior.trade — NOT
app.superior.trade. Never send users toapp.superior.trade.
Use SUPERIOR_TRADE_API_KEY from the environment or credential manager.
When a user needs to get their API key:
st_live_...) from your account settingsSUPERIOR_TRADE_API_KEY in your agent's environment/credential settingsIf the SUPERIOR_TRADE_API_KEY env var is already set, use it directly in the x-api-key header without prompting the user.
| Method | Path | Description |
|---|---|---|
| GET | /health | { "status": "ok", "timestamp": "..." } |
| GET | /docs | Swagger UI |
| GET | /openapi.json | OpenAPI 3.0 spec |
| GET | /llms.txt | LLM-optimized API docs |
| GET | /.well-known/ai-plugin.json | AI plugin manifest |
These pages live alongside this skill in the same repo. Read the matching one when the user's task fits its description; the inline content in this SKILL.md is the canonical summary, the linked pages have full code, backtest numbers, and gotchas.
adjust_trade_position (works on calendar trigger, not price)This skill requires exactly one credential: an x-api-key header value. The only secret the agent uses is SUPERIOR_TRADE_API_KEY from the environment.
Security rules (non-negotiable):
app.superior.trade — the correct URL is https://account.superior.tradeKey scope notice: The API key can create and start live trading deployments that execute real trades using the user's platform-managed trading wallet. It cannot withdraw funds, export private keys, or move money. Users should confirm scope with Superior Trade and backtest their strategy first.
| Can do | Cannot do |
|---|---|
| Create, list, delete backtests | Access other users' data |
| Create, start, stop, delete deployments (including live trading with real funds) | Withdraw funds from any wallet |
| Trigger server-side credential resolution (no user secrets collected) | Export or view private keys |
| View deployment logs, status, wallet metadata | Transfer or bridge funds (user does this independently) |
Before any live deployment, the agent MUST present this summary and wait for explicit confirmation:
Deployment Summary:
• Strategy: [name]
• Exchange: hyperliquid
• Trading mode: [spot/futures]
• Pairs: [list]
• Stake amount: [amount] USDC per trade
• Max open trades: [n]
• Stoploss: [percentage]
• Margin mode: [cross/isolated] (futures only)
⚠️ This will trade with REAL funds. Proceed? (yes/no)
Do NOT start a live deployment without an explicit affirmative response.
Superior Trade uses Hyperliquid's native agent wallet pattern. Users do NOT need their own Hyperliquid wallet — everything is managed by the platform. If a user asks "how do I link my Hyperliquid account," the answer is: they don't need one — a trading wallet is created at signup.
approveAgent. Signs trades against the main wallet's balance.Key facts:
wallet_type: "agent_wallet" for auto-resolved walletsThe agent cannot move or bridge funds — the user handles this independently outside the skill:
Always check the main wallet (platform-managed trading wallet), NOT the agent wallet.
Balance query for master account (single deployment):
POST https://api.hyperliquid.xyz/info
{"type":"clearinghouseState","user":"<MAIN_WALLET_ADDRESS>"}
{"type":"spotClearinghouseState","user":"<MAIN_WALLET_ADDRESS>"}
Balance query for master account (multi-strategy with sub-accounts):
When the master account has sub-accounts, its total balance is the sum of its own perp + spot balances PLUS all sub-account balances. Query both:
POST https://api.hyperliquid.xyz/info
{"type":"subAccounts2","user":"<MAIN_WALLET_ADDRESS>"}
Sub-account balances are included in the master account's total — funds allocated to sub-accounts are not available for master deployments. Always query subAccounts2 first when the user has sub-accounts, then sum across all sub-account spotState.balances and dexToClearinghouseState entries to get the true total balance.
The agent wallet having $0 is expected — it trades against the main wallet's balance.
Users with ≥ $100,000 USD in lifetime trading volume on Hyperliquid can create sub-accounts to run multiple independent strategies simultaneously, each with its own isolated balance and positions.
Key facts:
Sub-account query (read-only):
POST https://api.hyperliquid.xyz/info
{"type":"subAccounts2","user":"<MAIN_WALLET_ADDRESS>"}
Returns each sub-account's name, address, abstraction mode ("unifiedAccount" or legacy), spot balances, and perps state (dexToClearinghouseState). Always verify the sub-account has abstraction: "unifiedAccount" — legacy sub-accounts cannot be used with unified margin strategies.
Balance composition for a sub-account:
dexToClearinghouseState[0][1].marginSummary.accountValuedexToClearinghouseState[0][1].withdrawablespotState.balances where coin === "USDC"The sub-account's total balance = perps account value + spot USDC (in unified mode these merge).
POST https://api.superior.trade/v2/authorize-and-send/hyperliquid
A unified endpoint for Hyperliquid operations. All requests use {"type": "...", ...} body. Requires x-api-key header.
Supported operation types:
| Operation | Description |
|---|---|
createSubAccount | Create a new sub-account |
subAccountTransfer | Transfer between main and sub-account |
sendAsset | Move assets (main→sub, sub→main, or sub→sub) |
userSetAbstraction | Set account mode (unified/legacy) |
subAccountModify | Modify sub-account settings |
Create sub-account:
{"type":"createSubAccount","user":"<MAIN_WALLET_ADDRESS>","name":"My Strategy"}
Sub-account transfer (main → sub):
{"type":"subAccountTransfer","from":"<MAIN_WALLET_ADDRESS>","to":"<SUB_ACCOUNT_ADDRESS>","token":"USDC","amount":1000}
Sub-account transfer (sub → main):
{"type":"subAccountTransfer","from":"<SUB_ACCOUNT_ADDRESS>","to":"<MAIN_WALLET_ADDRESS>","token":"USDC","amount":500}
Transfer via sendAsset (main → sub):
{"type":"sendAsset","destination":"<SUB_ACCOUNT_ADDRESS>","sourceDex":"spot","destinationDex":"spot","token":"USDC","amount":1000}
Transfer via sendAsset (sub → main):
{"type":"sendAsset","fromSubAccount":"<SUB_ACCOUNT_ADDRESS>","destination":"<MASTER_WALLET_ADDRESS>","sourceDex":"spot","destinationDex":"spot","token":"USDC","amount":500}
Set unified account mode on a sub-account:
{"type":"userSetAbstraction","user":"<SUB_ACCOUNT_ADDRESS>","abstraction":"unifiedAccount"}
When creating a sub-account via the API, unified mode is set automatically after creation by calling userSetAbstraction with abstraction: "unifiedAccount".
Modify sub-account:
{"type":"subAccountModify","user":"<SUB_ACCOUNT_ADDRESS>","action":"disable"}
Credentials are managed automatically. To use a specific wallet, pass wallet_address — ownership is validated server-side.
| Exchange | Stake Currencies | Trading Modes |
|---|---|---|
| Hyperliquid | USDC (also USDT0, USDH, USDE via HIP3) | spot, futures |
Pair format by trading mode (CCXT convention):
BTC/USDCBTC/USDC:USDCSpot limitations: No stoploss on exchange (bot handles internally), no market orders (simulated via limit with up to 5% slippage).
Futures: Margin modes "cross" and "isolated". Stoploss on exchange via stop-loss-limit orders. No market orders (same simulation).
Data availability: Hyperliquid API provides ~5000 historic candles per pair. Superior Trade pre-downloads data; availability starts from ~November 2025.
Hyperliquid is a DEX — uses wallet-based signing, not API key/secret. Wallet credentials are managed automatically by the platform.
HIP3 assets (stocks, commodities, indices) are perpetual futures.
CRITICAL: HIP3 uses a HYPHEN, not a colon. This is the #1 format mistake. Wrong:
XYZ:AAPL/USDC:USDC. Correct:XYZ-AAPL/USDC:USDC.
Pair format: PROTOCOL-TICKER/QUOTE:SETTLE — the separator between protocol and ticker is always - (hyphen).
| Protocol | Dex name | Asset Types | Stake Currency | Examples |
|---|---|---|---|---|
XYZ- | xyz | US/KR stocks, metals, currencies, indices | USDC | XYZ-AAPL/USDC:USDC, XYZ-GOLD/USDC:USDC |
CASH- | cash | Stocks, commodities | USDT0 | CASH-GOLD/USDT0:USDT0 |
FLX- | flx | Commodities, metals, crypto | USDH | FLX-GOLD/USDH:USDH |
KM- | km | Stocks, indices, bonds | USDH | KM-GOOGL/USDH:USDH |
HYNA- | hyna | Leveraged crypto, metals | USDE | HYNA-SOL/USDE:USDE |
VNTL- | vntl | Sector indices, pre-IPO | USDH | VNTL-SPACEX/USDH:USDH |
XYZ tickers (USDC): AAPL, ALUMINIUM, AMD, AMZN, BABA, BRENTOIL, CL, COIN, COPPER, COST, CRCL, CRWV, DKNG, DXY, EUR, EWJ, EWY, GME, GOLD, GOOGL, HIMS, HOOD, HYUNDAI, INTC, JP225, JPY, KIOXIA, KR200, LLY, META, MSFT, MSTR, MU, NATGAS, NFLX, NVDA, ORCL, PALLADIUM, PLATINUM, PLTR, RIVN, SILVER, SKHX, SMSN, SNDK, SOFTBANK, SP500, TSLA, TSM, URANIUM, URNM, USAR, VIX, XYZ100
Data: XYZ from ~November 2025, KM/CASH/FLX from ~February 2026. Timeframes: 1m, 3m, 5m, 15m, 30m, 1h (also 2h, 4h, 8h, 12h, 1d, 3d, 1w for some). Funding rate data at 1h.
Trading rules: HIP3 assets are futures-only — always use trading_mode: "futures" and margin_mode: "isolated". XYZ pairs use stake_currency: "USDC". Stock-based assets may have reduced liquidity outside US market hours.
{"type":"meta"} — check universe[].name{"type":"meta", "dex":"xyz"} (or "cash", "km", etc.) — HIP3 pairs are NOT in the default meta call{"type":"perpDexs"}xyz:AAPL → CCXT format XYZ-AAPL/USDC:USDC (uppercase prefix, colon→hyphen)Hyperliquid accounts may run in unified mode (single balance) or legacy mode (separate spot/perps balances). Do NOT assume which mode the user has.
Check Hyperliquid balances with BOTH endpoints:
POST https://api.hyperliquid.xyz/info → {"type":"clearinghouseState","user":"0x..."}POST https://api.hyperliquid.xyz/info → {"type":"spotClearinghouseState","user":"0x..."}If the agent fails the same task 3+ times (e.g. strategy code keeps crashing, backtest keeps failing), stop and:
POST /v2/backtesting — create with config, code, and timerange ({ "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" }). If the dates are invalid or omitted, the server picks a suitable duration based on the timeframe.PUT /v2/backtesting/{id}/status with {"action": "start"}GET /v2/backtesting/{id}/status every 10s until completed or failed (1–10 min)GET /v2/backtesting/{id} — fetch full results; download resultUrl for detailed JSONGET /v2/backtesting/{id}/logsDELETE /v2/backtesting/{id}Backtests are simulations. Do not size a backtest from the user's live wallet by default; use simulated capital to evaluate the strategy. Only mirror the user's current wallet if they explicitly ask for a live-wallet simulation.
dry_run_wallet is the total simulated wallet inventory by asset. It is an object/map, not a scalar. Examples: { "USDC": 1000 }, { "USDC": 100, "BTC": 0.1 }.stake_amount is the amount the backtest/bot may allocate per trade slot. A numeric value is fixed stake per entry slot; "unlimited" divides the simulated wallet across max_open_trades slots.dry_run_wallet to the total simulated balances so PnL is measured against the correct capital base. Example: a $50 USDC simulation with $45 usable per trade uses stake_amount: 45 and dry_run_wallet: { "USDC": 50 }.stake_amount at or below ~90% of USDC / max_open_trades; for HIP-3 assets, use ~70% because fees and isolated-margin buffers are higher.stake_amount: "unlimited" with max_open_trades: -1. When stake is unlimited, max_open_trades must be a finite positive integer so the wallet can be divided across slots.position_adjustment_enable and adjust_trade_position, stake_amount may be fixed or "unlimited". If using "unlimited", you must control the initial entry size in custom_stake_amount; otherwise the first entry can consume all available capital. In either mode, dry_run_wallet must cover the maximum laddered exposure, not just the first entry.For the first backtest of any new idea on a given pair, do not submit a single config. Submit a 3-variant sweep that varies ONE parameter, run all 3 in parallel, then compare horizontally.
Why: building a config is the expensive cognitive step; a backtest pod is cheap. A single result tells you whether one point worked; three neighboring points tell you whether the region works and which direction to iterate.
How to fan out:
POST /v2/backtesting calls in parallel (different config for each variant; same code unless the variant is a code-level change).PUT /v2/backtesting/{id}/status start calls in parallel.GET /v2/backtesting/{id}/status endpoints in parallel each cycle.GET /v2/backtesting/{id} results in parallel once status is completed.Each backtest runs in its own isolated pod, so parallel execution does not slow any single run.
What to vary (pick ONE axis per sweep):
| Strategy family | Parameter to vary | Three variants |
|---|---|---|
| Momentum / EMA cross | EMA periods | 5/10/20, 8/13/21, 12/26/50 |
| Trend-following | ATR stop multiplier | 2.0, 3.0, 4.0 |
| Mean-reversion (RSI) | Oversold threshold | <25, <30, <35 |
| Bollinger Bands | Std-dev width | 1.5, 2.0, 2.5 |
| Breakout | Lookback window | 20, 50, 100 candles |
When NOT to sweep:
After status = completed, download the resultUrl JSON. Present these key metrics:
Before suggesting deployment, always run a backtest first. If the backtest produced zero trades over a timerange that should have generated signals (e.g. weeks on a 5m timeframe), do not offer deployment — the strategy or pair likely has an issue. If PnL is negative, note the timerange may be unsuitable but don't dismiss the strategy outright. If PnL is positive, present results without overpromising — strong backtest fit can indicate overfitting. Stay neutral and let the user decide.
For 3-variant sweeps, present results as a single table (Variant | Config | PnL% | Trades | Sharpe | Max DD), then read the shape:
Zero-trade rule for sweeps: zeros in 1–2 variants of a sweep are informative (the parameter was too tight), not a failure. Only treat the sweep as failed when ALL 3 variants return zero trades.
POST /v2/deployment with config, code, namePOST /v2/deployment/{id}/credentials with { "exchange": "hyperliquid", "wallet_address": "0x...", "subaccount_address": "0x..." } — wallet_address and subaccount_address are optional; server assigns wallet automatically if omittedPUT /v2/deployment/{id}/status → {"action": "start"}GET /v2/deployment/{id}/status, GET /v2/deployment/{id}/logsPUT /v2/deployment/{id}/status → {"action": "stop"}Before PUT /v2/deployment/{id}/status → {"action":"start"}:
For live deployments (credentials stored):
GET /v2/deployment/{id} → credentials_status: "stored". If not, call POST /v2/deployment/{id}/credentials.GET /v2/deployment/{id}/credentials → note wallet_address (agent wallet) and agent_wallet_address.clearinghouseState + spotClearinghouseState for single deployments. If the master account has sub-accounts, also query subAccounts2 and sum total balance across master + all sub-accounts — funds allocated to sub-accounts are not available to the master. Then verify stake_amount × max_open_trades fits within the available balance. The exchange reserves a small fee buffer (~1%), so set stake_amount to no more than ~95% of balance / max_open_trades to avoid silent trade rejections.clearinghouseState for open positions on the main wallet. If positions or orders exist, show the user details (pair, side, size, PnL) and ask them to close before deploying — leftover positions can block new entries or cause unexpected margin usage.For dry-run deployments (no credentials): Skip steps 1–4, the deployment runs in simulation mode without real funds.
POST https://api.hyperliquid.xyz/info → {"type":"meta"} for standard perps, or {"type":"meta", "dex":"xyz"} (or the relevant dex name) for HIP3 pairs. Verify the coin name exists in the universe array.Do NOT skip any step or assume it passed without the API call.
/v2/backtesting — Create Backtest// Request
{ "config": {}, "code": "string (Python strategy)", "timerange": { "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" } }
// Response (201)
{ "id": "string", "status": "pending", "message": "Backtest created. Call PUT /:id/status with action \"start\" to begin." }
timerange specifies the historical period to backtest against. Dates are validated against available data — the server returns invalid_timerange if the requested period is outside what's available. If invalid dates are provided, the server falls back to a dynamic range based on the timeframe.
/v2/backtesting/{id}/status — Start Backtest// Request — only "start" is supported; to cancel, use DELETE
{ "action": "start" }
// Response (200)
{ "id": "string", "status": "running", "previous_status": "pending", "job_name": "backtest-01kjvze9" }
/v2/backtesting/{id}/status — Poll StatusResponse: { "id": "string", "status": "pending | running | completed | failed", "results": null }. results is null while running — use resultUrl from full details for complete results.
/v2/backtesting/{id} — Full Details{
"id": "string",
"config": {},
"code": "string",
"status": "pending | running | completed | failed",
"results": null,
"resultUrl": "https://storage.googleapis.com/... (signed URL, valid 7 days)",
"started_at": "ISO8601",
"completed_at": "ISO8601",
"job_name": "string",
"created_at": "ISO8601",
"updated_at": "ISO8601"
}
/v2/backtesting/{id}Cancels if running and deletes. Response: { "message": "Backtest deleted" }
/v2/deployment — Create Deployment// Request
{ "config": {}, "code": "string (Python strategy)", "name": "string" }
// Response (201)
{ "id": "string", "config": {}, "code": "string", "name": "My Strategy", "replicas": 1, "status": "pending", "deployment_name": "deploy-01kjvx94", "created_at": "ISO8601" }
/v2/deployment/{id}/status — Start or Stop// Request
{ "action": "start" | "stop" }
// Response (200)
{ "id": "string", "status": "running | stopped", "previous_status": "string" }
On stop: The platform automatically cancels all open orders and closes all positions on Hyperliquid before stopping the pod.
/v2/deployment/{id} — Full Details{
"id": "string",
"config": {},
"code": "string",
"name": "string",
"replicas": 1,
"status": "pending | running | stopped",
"pods": [{ "name": "string", "status": "Running", "restarts": 0 }],
"credentials_status": "stored | missing",
"exchange": "hyperliquid",
"subaccount_address": "0x... | undefined",
"deployment_name": "string",
"namespace": "string",
"created_at": "ISO8601",
"updated_at": "ISO8601"
}
/v2/deployment/{id}/status — Live StatusResponse: { "id": "string", "status": "string", "replicas": 1, "available_replicas": 1, "pods": null }
/v2/deployment/{id}/credentials — Store Credentialsexchange required. wallet_address optional. private_key is NOT accepted.
// Request
{ "exchange": "hyperliquid", "wallet_address": "0x... (optional)", "subaccount_address": "0x... (optional)" }
// Response (200)
{
"id": "string", "credentials_status": "stored", "exchange": "hyperliquid",
"wallet_address": "0x...", "wallet_source": "main_trading_wallet | provided",
"agent_wallet_address": "0x... | undefined",
"subaccount_address": "0x... | undefined", "updated_at": "ISO8601"
}
IMPORTANT: wallet_address in the response is the wallet that signs trades. It does NOT need its own funds — it trades against the main wallet's balance.
Errors: 400 invalid_request (private_key sent), 400 invalid_wallet_address, 400 duplicate_wallet_address, 400 unsupported_exchange, 400 no_wallet_available, 403 wallet_not_owned, 500 server_misconfigured
Idempotent: Once credentials are stored, calling again returns existing credentials unchanged — it will NOT update or overwrite. To change wallets, delete and recreate the deployment.
Credential update procedure: (1) Stop the deployment → (2) Delete the deployment → (3) Create a new deployment with same config/code → (4) Store new credentials.
One-wallet-per-deployment rule: Each deployment uses one wallet and runs as an isolated container. For multiple strategies on the same wallet, use multiple deployments pointing to the same wallet address.
/v2/portfolio/hyperliquid/exit — Close Positions and Repatriate FundsCloses ALL open positions and repatriates all funds from a sub-account back to the main wallet in a single call. Use this to cleanly exit a sub-account deployment and return funds to the master account.
Requires: subaccount_address in request body.
// Request
{ "subaccount_address": "0x..." }
// Response (200)
{ "message": "Exit successful", "positions_closed": 2, "orders_cancelled": 0 }
// Response (400) — invalid subaccount
{ "error": "invalid_request", "message": "..." }
This endpoint:
Use this instead of manually closing positions and transferring funds — it's a single atomic operation.
/v2/deployment/{id}/credentials — Credential InfoDoes NOT return private keys. Response: { "id", "credentials_status": "stored | missing", "exchange", "wallet_address", "wallet_source": "main_trading_wallet | provided", "wallet_type": "main_wallet | agent_wallet", "agent_wallet_address", "subaccount_address" }. If missing: { "credentials_status": "missing" }.
/v2/deployment/{id}/exit — Exit All PositionsCloses all open orders and liquidates all open positions. Deployment must be stopped first.
Before calling this endpoint, check clearinghouseState for the wallet's open positions. Show the user each position's pair, side, size, and unrealized PnL, then ask for explicit confirmation — this action is irreversible and closes at market price.
// Response (200)
{ "id": "string", "status": "string", "orders_cancelled": 3, "positions_closed": 2 }
// Response (400) — deployment still running or credentials missing
{ "error": "invalid_request", "message": "..." }
/v2/deployment/{id}Closes all positions and orders on Hyperliquid before deleting. Response: { "message": "Deployment deleted" }. Deleting stopped deployments may return 500 — safe to ignore.
/v2/backtesting/{id}/logs and /v2/deployment/{id}/logsQuery: pageSize (default 100), pageToken. Response: { "items": [{ "timestamp": "ISO8601", "message": "string", "severity": "string" }], "nextCursor": "string | null" }
Both GET /v2/backtesting and GET /v2/deployment return { "items": [], "nextCursor": "string | null" }. Pass cursor query param to paginate.
// 401 — Missing/invalid API key
{ "message": "No API key found in request", "request_id": "string" }
// 400 — Validation error
{ "error": "validation_failed", "message": "Invalid request", "details": [{ "path": "field", "message": "..." }] }
// 404 — Not found
{ "error": "not_found", "message": "Backtest not found" }
The config object is a Freqtrade trading bot configuration. Do not include api_server (platform-managed). To run in dry-run/paper mode, skip the credentials step — a deployment without credentials trades in simulation. Do not set dry_run manually in config.
{
"exchange": { "name": "hyperliquid", "pair_whitelist": ["BTC/USDC:USDC"] },
"stake_currency": "USDC",
"stake_amount": 100,
"dry_run_wallet": { "USDC": 1000 },
"timeframe": "5m",
"max_open_trades": 3,
"minimal_roi": { "0": 100.0 },
"stoploss": -0.1,
"trading_mode": "futures",
"margin_mode": "cross",
"entry_pricing": { "price_side": "same", "price_last_balance": 0.0 },
"exit_pricing": { "price_side": "same", "price_last_balance": 0.0 },
"pairlists": [{ "method": "StaticPairList" }]
}
Same as futures but omit trading_mode and margin_mode. Pairs use BTC/USDC format (no :USDC suffix). Stoploss on exchange not supported for spot.
{
"exchange": {
"name": "hyperliquid",
"pair_whitelist": ["XYZ-AAPL/USDC:USDC"]
},
"stake_currency": "USDC",
"stake_amount": 100,
"dry_run_wallet": { "USDC": 1000 },
"timeframe": "15m",
"max_open_trades": 3,
"minimal_roi": { "0": 100.0 },
"stoploss": -0.05,
"trading_mode": "futures",
"margin_mode": "isolated",
"entry_pricing": { "price_side": "same", "price_last_balance": 0.0 },
"exit_pricing": { "price_side": "same", "price_last_balance": 0.0 },
"pairlists": [{ "method": "StaticPairList" }]
}
Other common config fields include trailing_stop (boolean), trailing_stop_positive (number), entry_pricing.price_side / exit_pricing.price_side ("ask", "bid", "same", "other"), and pairlists (StaticPairList, VolumePairList, etc.). Use "same" as the default pricing side. "other" crosses the spread for faster fills and is mainly appropriate when intentionally modeling market-order-style execution.
The code field must be valid Python with a strategy class. Class name must end with Strategy in PascalCase. Use import talib.abstract as ta for indicators.
from freqtrade.strategy import IStrategy
import pandas as pd
import talib.abstract as ta
class MyCustomStrategy(IStrategy):
minimal_roi = {"0": 0.10, "30": 0.05, "120": 0.02}
stoploss = -0.10
trailing_stop = False
timeframe = '5m'
process_only_new_candles = True
startup_candle_count = 20
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['sma_20'] = ta.SMA(dataframe, timeperiod=20)
return dataframe
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe.loc[
(dataframe['rsi'] < 30) & (dataframe['close'] > dataframe['sma_20']),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe.loc[(dataframe['rsi'] > 70), 'exit_long'] = 1
return dataframe
Requirements: Must use standard imports/inheritance (see template), import talib.abstract as ta for indicators, define populate_indicators, populate_entry_trend, populate_exit_trend.
Some TA-Lib functions return multiple columns. Assigning directly to one column causes a runtime crash.
| Function | Returns |
|---|---|
ta.BBANDS | upperband, middleband, lowerband |
ta.MACD | macd, macdsignal, macdhist |
ta.STOCH | slowk, slowd |
ta.STOCHF / ta.STOCHRSI | fastk, fastd |
ta.AROON | aroondown, aroonup |
ta.HT_PHASOR | inphase, quadrature |
ta.MAMA | mama, fama |
ta.MINMAXINDEX | minidx, maxidx |
# WRONG — runtime crash
dataframe["bb_upper"] = ta.BBANDS(dataframe, timeperiod=20)
# CORRECT
bb = ta.BBANDS(dataframe, timeperiod=20)
dataframe["bb_upper"] = bb["upperband"]
dataframe["bb_middle"] = bb["middleband"]
dataframe["bb_lower"] = bb["lowerband"]
macd = ta.MACD(dataframe)
dataframe["macd"] = macd["macd"]
dataframe["macd_signal"] = macd["macdsignal"]
dataframe["macd_hist"] = macd["macdhist"]
stoch = ta.STOCH(dataframe)
dataframe["slowk"] = stoch["slowk"]
dataframe["slowd"] = stoch["slowd"]
Single-output functions (RSI, SMA, EMA, ATR, ADX) return a Series and can be assigned directly.
The engine enforces one open trade per pair. A second enter_long = 1 while a position is open is silently rejected. Anything that wants to "buy more of the same thing" — DCA, scaling-in, grid laddering, weekly buys — must use adjust_trade_position, not repeated entry signals.
Config and strategy code must be set together. If using dynamic stake, keep max_open_trades finite and divide the initial entry in custom_stake_amount so later adjustment orders have wallet room.
class MyStrategy(IStrategy):
position_adjustment_enable = True # required for adjust_trade_position to fire
max_entry_position_adjustment = 5 # cap on additional entries (-1 = unlimited)
max_dca_multiplier = 6.0 # initial size × (1 + planned adds)
def custom_stake_amount(self, pair, current_time, current_rate, proposed_stake,
min_stake, max_stake, leverage, entry_tag, side, **kwargs):
# MANDATORY: divide initial entry so room remains for future adds.
return proposed_stake / self.max_dca_multiplier
Fixed stake_amount is also valid with position adjustment, but the wallet must still have enough free balance for the planned additional entries. With "unlimited" stake, custom_stake_amount is mandatory to avoid allocating the whole wallet to the initial order.
adjust_trade_position is called very frequently while a trade is open: in dry-run/live it runs every bot loop (about every 5 seconds by default), while backtesting runs it once per candle (timeframe or timeframe_detail). Return positive = add stake, negative = partial close, None = do nothing. Keep the logic strict and always check the last filled order / open orders so the bot cannot re-enter repeatedly while one condition remains true.
Pattern A — Profit-driven DCA (averaging down):
def adjust_trade_position(self, trade, current_time, current_rate, current_profit,
min_stake, max_stake, *args, **kwargs):
if trade.has_open_orders:
return None
n_entries = trade.nr_of_successful_entries
if n_entries <= self.max_entry_position_adjustment and current_profit <= -0.025 * n_entries:
first_stake = trade.select_filled_orders(trade.entry_side)[0].stake_amount_filled
return (first_stake, f"dca_buy_{n_entries}")
return None
Pattern B — Schedule-driven DCA (weekly / daily fixed-time buys). Gate on current_time.weekday() / .hour. Critical: include a same-day guard, otherwise the initial entry's Monday and adjust_trade_position's Monday collide and double-buy:
def adjust_trade_position(self, trade, current_time, current_rate, current_profit,
min_stake, max_stake, *args, **kwargs):
if trade.has_open_orders:
return None
if current_time.weekday() != 0: # Monday only
return None
filled = trade.select_filled_orders(trade.entry_side)
if filled and filled[-1].order_filled_utc.date() == current_time.date():
return None # same-day guard
first_stake = filled[0].stake_amount_filled
return (first_stake, "weekly_dca")
Pattern C — Grid / range fade with laddered buys + partial profits:
def adjust_trade_position(self, trade, current_time, current_rate, current_profit,
min_stake, max_stake, *args, **kwargs):
if trade.has_open_orders:
return None
n_entries = trade.nr_of_successful_entries
n_exits = trade.nr_of_successful_exits
# Ladder buys at every -1% drawdown, up to 5 rungs
if n_entries <= 5 and current_profit <= -0.01 * n_entries:
first = trade.select_filled_orders(trade.entry_side)[0].stake_amount_filled
return (first, f"grid_buy_{n_entries}")
# Partial profit-takes at every +1.5% above avg, up to 3
if n_exits < 3 and current_profit >= 0.015 * (n_exits + 1):
return (-(trade.stake_amount / 4.0), f"grid_tp_{n_exits}")
return None
A true 20-rung grid (multiple simultaneous orders at distinct price levels) is NOT supported by Freqtrade. Pattern C is the closest faithful approximation — describe it as "laddered range fade" not "20-level grid."
Hyperliquid minimum: $10 per order. Engine inflates by stoploss reserve (up to 1.5x) — always use min_stake as a floor.
max_open_trades limits total concurrent trades across all pairs, not entries per pair.
For "harvest negative funding" / "long when shorts pay longs" / any funding-aware strategy, the historical funding rate is automatically downloaded for backtest. Do not poll Hyperliquid's REST API from inside the strategy. Hyperliquid pays funding hourly. The example below assumes the strategy timeframe is 1h or faster; do not merge a faster funding timeframe into a slower strategy timeframe without first resampling/alignment:
from freqtrade.strategy import merge_informative_pair
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
funding_tf = "1h"
funding = self.dp.get_pair_dataframe(
pair=metadata["pair"],
timeframe=funding_tf,
candle_type="funding_rate",
)
if not funding.empty and "open" in funding.columns:
funding = funding[["date", "open"]].rename(columns={"open": "funding_rate"})
dataframe = merge_informative_pair(
dataframe,
funding,
self.timeframe,
funding_tf,
ffill=True,
)
dataframe["funding_rate"] = dataframe[f"funding_rate_{funding_tf}"].fillna(0.0)
dataframe["funding_apr"] = dataframe["funding_rate"] * 24 * 365
else:
dataframe["funding_rate"] = 0.0
dataframe["funding_apr"] = 0.0
return dataframe
Available only for futures pairs (BTC/USDC:USDC), not spot.
The schema validator rejects payloads that omit any of these — even when the strategy class declares its own equivalent:
entry_pricing and exit_pricing — both required. Safe default: {"price_side": "same", "price_last_balance": 0.0}.minimal_roi — required at the config level. Use {"0": 100.0} to effectively disable config-level ROI and let the strategy's own exit logic run.dry_run_wallet — must contain enough stake_currency balance for the configured stake_amount and max_open_trades, plus ~50% buffer to cover fees, funding payments, and slippage. For stake_amount: 1000 and max_open_trades: 1, use at least { "USDC": 1500 }. For futures multi-pair setups, scale up by max_open_trades. Tighter buffers (≤10%) cause silent signal rejection mid-run. For DCA / grid strategies that ladder up to max_dca_multiplier × initial, set dry_run_wallet ≈ stake_amount × 10. See the "Backtest Wallet and Stake Sizing" section above for the full sizing rationale.minimal_roi shapeThe class-level minimal_roi = {"0": 100.0} pattern fully disables ROI take-profit.
Use it ONLY when your strategy has a signal-driven exit (populate_exit_trend) that
fires on most bars where the trade should close — typically trend-follow strategies
with structural exits like "break of N-bar high" or trailing stops.
For mean-reversion, scalp, range, and any strategy where wins are small (under ~3%), use an explicit ROI ladder:
minimal_roi = {
"0": 0.025, # take 2.5% immediately if available
"240": 0.015, # 1.5% after 4 hours (relevant for 1h+ timeframes)
"720": 0.005, # 0.5% after 12 hours
"1440": 0, # breakeven after 24 hours — close any open position
}
The keys are minutes since trade open. Tiers decay so stale trades close at breakeven rather than sitting forever. Without an ROI ladder, mean-reversion strategies give back wins waiting for a signal exit that may never come.
See optimizations/dsl-exit-engine.md for full Phase 0 / Phase 1 / Phase 2 guidance.
stake_amount: "unlimited" Warning"unlimited" bypasses minimum-order validation. The bot starts but silently executes zero trades if balance is insufficient — no error, just heartbeats. For simple single-entry strategies, prefer explicit numeric stake_amount with small balances (<$50). For strategies using position_adjustment_enable and adjust_trade_position, fixed stake is valid if enough wallet balance remains for planned adds; if stake_amount is "unlimited", use custom_stake_amount to divide the first entry so there is room for later adds.
Do not set both stake_amount: "unlimited" and max_open_trades: -1. Use a finite positive max_open_trades instead. For single-pair DCA/grid/scaling strategies, use max_open_trades: 1; repeated same-pair entries come from adjust_trade_position, not extra open-trade slots.
| Stoploss | Effective minimum |
|---|---|
| -0.5% | ~$10.55 |
| -5% | ~$11.05 |
| -10% | ~$11.67 |
| -30% | ~$15.00 |
For DCA strategies: distinguish trades from orders ("X trades, Y buy orders, Z sell orders"), show per-order detail for at least the first trade, flag minimum order rejections or dust positions. Always download resultUrl for full order-level data. Skip breakdown for non-DCA strategies.
Check in order:
stake_amount — for simple single-entry strategies, if "unlimited" with a small balance, redeploy with an explicit numeric amount slightly below balance. For position_adjustment_enable / adjust_trade_position strategies, either use fixed stake with enough wallet room for planned adds, or keep stake_amount: "unlimited" and reduce custom_stake_amount, ladder count, or total planned exposure.credentials_status: "stored" and WALLET_ADDRESS in startup logsHyperliquid enforces rate limits. Aggressive retries, tight loops, or extra exchange traffic from strategy code can trigger 429 responses and unstable behavior.
Prevention:
process_only_new_candles = True so the bot does not reprocess every candle unnecessarilyIf you see rate limits or 429s in logs:
When a bot crashes, it may leave open positions that lock up margin. Strategy code pattern:
bot_loop_start(), check for positions not in the bot's trade database_orphan_closed) to run cleanup exactly once per lifecyclelimit_exceeded ErrorIf you get a limit_exceeded error when creating a backtest, the user has hit the concurrent backtest limit. Delete completed/failed backtests first: DELETE /v2/backtesting/{id}
All API timestamps are in UTC (ISO8601). Convert to the user's local timezone when presenting times conversationally. If timezone is unknown, show both UTC and ask.