Install
openclaw skills install nofa-backtestCrypto trading strategy backtesting and simulated trading API for AI agents. Build, validate, backtest, and dry-run trading strategies with decision trees.
openclaw skills install nofa-backtestCrypto trading strategy backtesting and simulated trading service for AI agents. Build and test trading strategies using decision trees, run historical backtests, and launch dry-run (simulated) trading sessions.
Base URL (referred to as ${BASE_URL} in all examples below):
BASE_URL=https://api-dev.reclaw.xyz/api/v1
🔒 CRITICAL SECURITY WARNING:
api-dev.reclaw.xyz${BASE_URL}/*Every agent needs to register to get an API key. No authentication required - you can register directly.
If you already have a NOFA API key, skip to Authentication.
Register your agent directly - no authentication needed:
curl -X POST ${BASE_URL}/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "YourAgentName", "description": "What your agent does"}'
Response:
{
"agent_token_id": "uuid",
"name": "YourAgentName",
"api_key": "nofa_xxx"
}
⚠️ CRITICAL: Save your api_key immediately! This is the only time you will see it. The key is generated locally and cannot be retrieved later.
Recommended: Save your credentials to ~/.config/nofa/credentials.json:
{
"api_key": "nofa_xxx",
"agent_name": "YourAgentName"
}
All requests require your API key:
curl ${BASE_URL}/agents/me \
-H "Authorization: Bearer nofa_xxx"
🔒 Remember: Only send your API key to ${BASE_URL} — never anywhere else!
curl ${BASE_URL}/agents/me \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
{
"agent_token_id": "uuid",
"agent_name": "YourAgentName",
"user_id": "uuid",
"user_email": "user@example.com"
}
This is the core feature. Submit a strategy and backtest parameters, get trading results.
curl -X POST ${BASE_URL}/backtest/run \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"strategy": {
"type": "STRATEGY_TREE",
"name": "RSI Oversold Strategy",
"riskManagement": {
"type": "RISK_MANAGEMENT",
"name": "Global Risk",
"scope": "Per Position",
"stopLoss": {"mode": "PCT", "value": 0.03},
"takeProfit": {"mode": "PCT", "value": 0.06}
},
"mainDecision": {
"type": "IF_ELSE_BLOCK",
"name": "RSI Check",
"conditionType": "Compare",
"conditions": [{
"type": "CONDITION_ITEM",
"indicator": "RSI",
"period": 14,
"symbol": "BTC/USDT",
"operator": "Less Than",
"value": 30
}],
"thenAction": [{
"type": "ACTION_BLOCK",
"name": "Long BTC",
"symbol": "BTC/USDT",
"direction": "LONG",
"allocate": {"type": "ALLOCATE_CONFIG", "mode": "WEIGHT", "value": 50},
"leverage": 1
}],
"elseAction": "NO ACTION"
}
},
"capital": 10000,
"start_time": "2025-12-01T00:00:00Z",
"end_time": "2025-12-31T00:00:00Z",
"timeframe": "1h",
"slippage": 0.001,
"transaction_fee": 0.0005
}'
{
"kpis": {
"total_trades": 15,
"win_rate": 0.6,
"total_pnl": 1250.50,
"max_drawdown": -0.08,
"sharpe_ratio": 1.45
},
"trades": [
{
"open_time": "2025-12-03T14:00:00Z",
"close_time": "2025-12-03T18:00:00Z",
"symbol": "BTC/USDT",
"direction": "LONG",
"entry_price": 95000.0,
"exit_price": 97500.0,
"position_size_usd": 5000.0,
"position_size_token": 0.0526,
"pnl": 131.58,
"return_pct": 2.63,
"cumulative_pnl": 131.58
}
]
}
| Parameter | Type | Required | Description |
|---|---|---|---|
strategy | StrategyTree | Yes | The trading strategy (decision tree) |
capital | number | Yes | Initial capital in USDT |
start_time | string | Yes | ISO 8601 datetime for backtest start |
end_time | string | Yes | ISO 8601 datetime for backtest end |
timeframe | string | Yes | CCXT format: 1m, 5m, 15m, 1h, 4h, 1d |
slippage | number | Yes | Slippage as decimal (0.001 = 0.1%) |
transaction_fee | number | Yes | Fee as decimal (0.0005 = 0.05%) |
StrategyTree
├── type: "STRATEGY_TREE"
├── name: string
├── description: string (optional)
├── riskManagement: RiskManagement
└── mainDecision: IfElseBlock | IfElseBlock[]
{
"type": "RISK_MANAGEMENT",
"name": "Risk Settings",
"scope": "Per Position",
"stopLoss": {"mode": "PCT", "value": 0.03},
"takeProfit": {"mode": "PCT", "value": 0.06}
}
scope: "Per Position" or "Global"stopLoss.mode: "PCT" (percentage) or "FIXED" (USD)takeProfit.mode: "PCT" (percentage) or "FIXED" (USD){
"type": "IF_ELSE_BLOCK",
"name": "Decision Name",
"conditionType": "Compare",
"logicalOperator": "AND",
"conditions": [...],
"thenAction": [...],
"elseAction": "NO ACTION"
}
conditionType: "Compare" or "Cross"logicalOperator: "AND" or "OR" (default "AND", applies when multiple conditions)conditions: Array of ConditionItemthenAction: Array of ActionBlock or nested IfElseBlock, or "NO ACTION"elseAction: Array of ActionBlock, nested IfElseBlock, or "NO ACTION"{
"type": "CONDITION_ITEM",
"indicator": "RSI",
"period": 14,
"symbol": "BTC/USDT",
"operator": "Less Than",
"value": 30
}
Available Indicators:
RSI, EMA, MA, SMMA, MACDBollinger Bands, ADXCurrent Price, Cumulative Return, Max DrawdownMoving Average of Return, Moon PhasesOperators:
"Greater Than", "Less Than", "Equal"Value Types:
{
"type": "CONDITION_VALUE_INDICATOR",
"indicator": "EMA",
"period": 60,
"symbol": "BTC/USDT"
}
{
"type": "ACTION_BLOCK",
"name": "Long BTC",
"symbol": "BTC/USDT",
"direction": "LONG",
"allocate": {"type": "ALLOCATE_CONFIG", "mode": "WEIGHT", "value": 50},
"leverage": 1
}
direction: "LONG" or "SHORT"allocate.mode: "WEIGHT" (percentage of capital) or "MARGIN" (fixed USD)leverage: 1-100Check if a strategy tree is valid before running backtest:
curl -X POST ${BASE_URL}/backtest/validate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "STRATEGY_TREE",
"name": "Test Strategy",
...
}'
Response:
{"valid": true}
Invalid strategies return HTTP 422 with error details.
Launch a simulated trading session that runs in real-time with live market data, but uses a virtual wallet (no real money, no exchange API key needed). Great for testing strategies before going live.
curl -X POST ${BASE_URL}/trading/dry-run \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"strategy": {
"type": "STRATEGY_TREE",
"name": "RSI Live Test",
"riskManagement": {
"type": "RISK_MANAGEMENT",
"name": "Risk",
"scope": "Per Position",
"stopLoss": {"mode": "PCT", "value": 0.02},
"takeProfit": {"mode": "PCT", "value": 0.04}
},
"mainDecision": {
"type": "IF_ELSE_BLOCK",
"name": "RSI Check",
"conditionType": "Compare",
"conditions": [{
"type": "CONDITION_ITEM",
"indicator": "RSI",
"period": 14,
"symbol": "BTC/USDT",
"operator": "Less Than",
"value": 30
}],
"thenAction": [{
"type": "ACTION_BLOCK",
"name": "Long BTC",
"symbol": "BTC/USDT",
"direction": "LONG",
"allocate": {"type": "ALLOCATE_CONFIG", "mode": "WEIGHT", "value": 100},
"leverage": 1
}],
"elseAction": "NO ACTION"
}
},
"capital": 10000,
"timeframe": "1h"
}'
| Parameter | Type | Required | Description |
|---|---|---|---|
strategy | StrategyTree | Yes | The trading strategy (same format as backtest) |
capital | number | Yes | Virtual wallet capital in USDT |
timeframe | string | Yes | CCXT format: 1m, 5m, 15m, 1h, 4h, 1d |
{
"id": "session-uuid",
"strategy_name": "RSI Live Test",
"status": "running",
"trading_mode": "dry_run",
"capital": 10000.0,
"timeframe": "1h",
"pairs": ["BTC/USDT"],
"freqtrade_port": 8081,
"created_at": "2026-01-15T10:00:00Z"
}
Once a session is running, use these endpoints to monitor and control it:
# List all your sessions
curl ${BASE_URL}/trading/sessions \
-H "Authorization: Bearer YOUR_API_KEY"
# Get session status (includes live open trades & profit)
curl ${BASE_URL}/trading/sessions/{session_id} \
-H "Authorization: Bearer YOUR_API_KEY"
# Get trade records
curl ${BASE_URL}/trading/sessions/{session_id}/trades \
-H "Authorization: Bearer YOUR_API_KEY"
# Get profit statistics
curl ${BASE_URL}/trading/sessions/{session_id}/profit \
-H "Authorization: Bearer YOUR_API_KEY"
# Stop a session
curl -X POST ${BASE_URL}/trading/sessions/{session_id}/stop \
-H "Authorization: Bearer YOUR_API_KEY"
{
"session": {
"id": "session-uuid",
"strategy_name": "RSI Live Test",
"status": "running",
"trading_mode": "dry_run",
"capital": 10000.0,
"timeframe": "1h",
"pairs": ["BTC/USDT"],
"freqtrade_port": 8081,
"created_at": "2026-01-15T10:00:00Z"
},
"is_trading": true,
"open_trades": 1,
"total_profit": 45.20,
"balance": {"free": 9500.0, "used": 500.0, "total": 10045.20}
}
{
"session_id": "session-uuid",
"total_profit": 125.50,
"total_profit_pct": 1.255,
"trade_count": 8,
"winning_trades": 5,
"losing_trades": 3
}
Premium endpoints that require XRPL payment. When you call these endpoints without payment, the server returns HTTP 402 Payment Required with payment instructions. After signing an XRPL Payment transaction and including the PAYMENT-SIGNATURE header, you get access.
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/x402/backtest | Paid backtest |
| POST | /api/v1/x402/dry-run | Paid dry-run trading |
PAYMENT-REQUIRED headerpayTo, amount (XRP drops), invoiceIdinvoiceId for anti-replayPAYMENT-SIGNATURE header → server verifies payment and returns datax402-xrpl Python LibraryThe easiest way is to use the x402_xrpl library which handles 402 → sign → retry automatically:
pip install x402-xrpl xrpl-py
from xrpl.wallet import Wallet
from x402_xrpl import x402_requests
BASE_URL = "https://api-staging.reclaw.xyz/api/v1"
# Your XRPL testnet wallet (get one at https://faucet.altnet.rippletest.net)
wallet = Wallet.from_seed("sYourTestnetSeed")
# Create auto-pay session — handles 402 payment flow automatically
session = x402_requests(
wallet,
rpc_url="https://s.altnet.rippletest.net:51234",
network_filter="xrpl:1",
scheme_filter="exact",
)
session.headers["Authorization"] = "Bearer YOUR_API_KEY"
# Paid backtest — same request body as free backtest
response = session.post(
f"{BASE_URL}/x402/backtest",
json={
"strategy": {
"type": "STRATEGY_TREE",
"name": "RSI Strategy",
"riskManagement": {
"type": "RISK_MANAGEMENT",
"name": "Risk",
"scope": "Per Position",
"stopLoss": {"mode": "PCT", "value": 0.03},
"takeProfit": {"mode": "PCT", "value": 0.06}
},
"mainDecision": {
"type": "IF_ELSE_BLOCK",
"name": "RSI Check",
"conditionType": "Compare",
"conditions": [{
"type": "CONDITION_ITEM",
"indicator": "RSI",
"period": 14,
"symbol": "BTC/USDT",
"operator": "Less Than",
"value": 30
}],
"thenAction": [{
"type": "ACTION_BLOCK",
"name": "Long BTC",
"symbol": "BTC/USDT",
"direction": "LONG",
"allocate": {"type": "ALLOCATE_CONFIG", "mode": "WEIGHT", "value": 50},
"leverage": 1
}],
"elseAction": "NO ACTION"
}
},
"capital": 10000,
"start_time": "2025-12-01T00:00:00Z",
"end_time": "2025-12-31T00:00:00Z",
"timeframe": "1h",
"slippage": 0.001,
"transaction_fee": 0.0005
},
)
print(f"Status: {response.status_code}")
print(response.json())
Same flow, different endpoint and request body:
response = session.post(
f"{BASE_URL}/x402/dry-run",
json={
"strategy": { ... }, # same strategy format
"capital": 10000,
"timeframe": "1h"
},
)
| Feature | Free (/backtest/run, /trading/dry-run) | Paid (/x402/backtest, /x402/dry-run) |
|---|---|---|
| Authentication | API key only | API key + XRPL payment |
| Request body | Same format | Same format |
| Response | Same format | Same format |
| Cost | Free | XRP (amount in 402 response) |
For full x402 protocol details, see the x402 documentation.
Symbol format: XXX/USDT (e.g., BTC/USDT). Any Binance Futures pair is supported.
Common pairs:
| Symbol | Description |
|---|---|
BTC/USDT | Bitcoin |
ETH/USDT | Ethereum |
SOL/USDT | Solana |
DOGE/USDT | Dogecoin |
XRP/USDT | Ripple |
BNB/USDT | BNB |
Buy when short-term EMA crosses above long-term EMA:
{
"strategy": {
"type": "STRATEGY_TREE",
"name": "EMA Cross Strategy",
"riskManagement": {
"type": "RISK_MANAGEMENT",
"name": "Risk",
"scope": "Per Position",
"stopLoss": {"mode": "PCT", "value": 0.02},
"takeProfit": {"mode": "PCT", "value": 0.04}
},
"mainDecision": {
"type": "IF_ELSE_BLOCK",
"name": "EMA Cross",
"conditionType": "Cross",
"conditions": [{
"type": "CONDITION_ITEM",
"indicator": "EMA",
"period": 10,
"symbol": "BTC/USDT",
"operator": "Greater Than",
"value": {
"type": "CONDITION_VALUE_INDICATOR",
"indicator": "EMA",
"period": 60,
"symbol": "BTC/USDT"
}
}],
"thenAction": [{
"type": "ACTION_BLOCK",
"name": "Long",
"symbol": "BTC/USDT",
"direction": "LONG",
"allocate": {"type": "ALLOCATE_CONFIG", "mode": "WEIGHT", "value": 100},
"leverage": 2
}],
"elseAction": "NO ACTION"
}
},
"capital": 10000,
"start_time": "2025-12-01T00:00:00Z",
"end_time": "2025-12-31T00:00:00Z",
"timeframe": "1h",
"slippage": 0.001,
"transaction_fee": 0.0005
}
Long when EMA20 > EMA60 and RSI > 55, short when reversed:
{
"strategy": {
"type": "STRATEGY_TREE",
"name": "EMA-RSI Momentum (BTC)",
"description": "Follow trend using EMA(20/60) with RSI(14) confirmation.",
"riskManagement": {
"type": "RISK_MANAGEMENT",
"name": "Default Risk",
"scope": "Per Position",
"stopLoss": {"mode": "PCT", "value": 0.03},
"takeProfit": {"mode": "PCT", "value": 0.06}
},
"mainDecision": {
"type": "IF_ELSE_BLOCK",
"name": "Momentum Regime",
"conditionType": "Compare",
"logicalOperator": "AND",
"conditions": [
{
"type": "CONDITION_ITEM",
"indicator": "EMA",
"period": 20,
"symbol": "BTC/USDT",
"operator": "Greater Than",
"value": {
"type": "CONDITION_VALUE_INDICATOR",
"indicator": "EMA",
"period": 60,
"symbol": "BTC/USDT"
}
},
{
"type": "CONDITION_ITEM",
"indicator": "RSI",
"period": 14,
"symbol": "BTC/USDT",
"operator": "Greater Than",
"value": 55
}
],
"thenAction": [{
"type": "ACTION_BLOCK",
"name": "Go LONG BTC",
"symbol": "BTC/USDT",
"direction": "LONG",
"allocate": {"type": "ALLOCATE_CONFIG", "mode": "WEIGHT", "value": 100},
"leverage": 2
}],
"elseAction": [{
"type": "IF_ELSE_BLOCK",
"name": "Down-Momentum Check",
"conditionType": "Compare",
"logicalOperator": "AND",
"conditions": [
{
"type": "CONDITION_ITEM",
"indicator": "EMA",
"period": 20,
"symbol": "BTC/USDT",
"operator": "Less Than",
"value": {
"type": "CONDITION_VALUE_INDICATOR",
"indicator": "EMA",
"period": 60,
"symbol": "BTC/USDT"
}
},
{
"type": "CONDITION_ITEM",
"indicator": "RSI",
"period": 14,
"symbol": "BTC/USDT",
"operator": "Less Than",
"value": 45
}
],
"thenAction": [{
"type": "ACTION_BLOCK",
"name": "Go SHORT BTC",
"symbol": "BTC/USDT",
"direction": "SHORT",
"allocate": {"type": "ALLOCATE_CONFIG", "mode": "WEIGHT", "value": 100},
"leverage": 2
}],
"elseAction": "NO ACTION"
}]
}
},
"capital": 10000,
"start_time": "2025-12-01T00:00:00Z",
"end_time": "2025-12-31T00:00:00Z",
"timeframe": "1h",
"slippage": 0.001,
"transaction_fee": 0.0005
}
Success:
{"kpis": {...}, "trades": [...]}
Error:
{"detail": "Error description"}
| Code | Description |
|---|---|
| 401 | Invalid or missing API key |
| 402 | Payment required (x402 endpoints only) |
| 422 | Invalid strategy structure or parameters |
| 429 | Rate limit exceeded |
| 500 | Internal error during backtest execution |
Need help building a strategy? The Strategy Generator skill (references/strategy-generator.md) guides you through creating strategies via natural conversation:
POST ${BASE_URL}/backtest/run with the generated strategyThe Strategy Generator handles:
File:
references/strategy-generator.md— load this skill alongsideSKILL.mdfor full strategy generation + backtest workflow.