Crypto 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):
text
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:
bash
curl -X POST ${BASE_URL}/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "YourAgentName", "description": "What your agent does"}'
Response:
json
{
"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:
json
{
"api_key": "nofa_xxx",
"agent_name": "YourAgentName"
}
All requests require your API key:
bash
curl ${BASE_URL}/agents/me \
-H "Authorization: Bearer nofa_xxx"
🔒 Remember: Only send your API key to ${BASE_URL} — never anywhere else!
bash
curl ${BASE_URL}/agents/me \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
json
{
"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.
bash
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
}'
json
{
"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
}
]
}
ParameterTypeRequiredDescriptionstrategyStrategyTreeYesThe trading strategy (decision tree)capitalnumberYesInitial capital in USDTstart_timestringYesISO 8601 datetime for backtest startend_timestringYesISO 8601 datetime for backtest endtimeframestringYesCCXT format: 1m, 5m, 15m, 1h, 4h, 1d``slippagenumberYesSlippage as decimal (0.001 = 0.1%)transaction_feenumberYesFee as decimal (0.0005 = 0.05%)
text
StrategyTree
├── type: "STRATEGY_TREE"
├── name: string
├── description: string (optional)
├── riskManagement: RiskManagement
└── mainDecision: IfElseBlock | IfElseBlock[]
json
{
"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)json
{
"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"json
{
"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:
Number: Compare to fixed value (e.g., RSI < 30)
Indicator: Compare to another indicator: json
{
"type": "CONDITION_VALUE_INDICATOR",
"indicator": "EMA",
"period": 60,
"symbol": "BTC/USDT"
}
json
{
"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:
bash
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:
json
{"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.
bash
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"
}'
ParameterTypeRequiredDescriptionstrategyStrategyTreeYesThe trading strategy (same format as backtest)capitalnumberYesVirtual wallet capital in USDTtimeframestringYesCCXT format: 1m, 5m, 15m, 1h, 4h, 1d
json
{
"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:
bash
# 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"
json
{
"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}
}
json
{
"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.
MethodPathDescriptionPOST/api/v1/x402/backtestPaid backtestPOST/api/v1/x402/dry-runPaid dry-run trading
PAYMENT-REQUIRED headerpayTo, amount (XRP drops), invoiceIdinvoiceId for anti-replayPAYMENT-SIGNATURE header → server verifies payment and returns datax402-xrpl Python Library#The easiest way is to use the x402_xrpl library which handles 402 → sign → retry automatically:
bash
pip install x402-xrpl xrpl-py
python
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:
python
response = session.post(
f"{BASE_URL}/x402/dry-run",
json={
"strategy": { ... }, # same strategy format
"capital": 10000,
"timeframe": "1h"
},
)
FeatureFree ( /backtest/run, /trading/dry-run)Paid ( /x402/backtest, /x402/dry-run)AuthenticationAPI key onlyAPI key + XRPL paymentRequest bodySame formatSame formatResponseSame formatSame formatCostFreeXRP (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:
SymbolDescriptionBTC/USDTBitcoinETH/USDTEthereumSOL/USDTSolanaDOGE/USDTDogecoinXRP/USDTRippleBNB/USDTBNB
Buy when short-term EMA crosses above long-term EMA:
json
{
"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:
json
{
"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:
json
{"kpis": {...}, "trades": [...]}
Error:
json
{"detail": "Error description"}
CodeDescription401Invalid or missing API key402Payment required (x402 endpoints only)422Invalid strategy structure or parameters429Rate limit exceeded500Internal 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.