Building Complete Trading Systems - Signal Generator + Backtest Integration
This guide shows you how to build a complete trading system workflow in ApudFlow. We'll walk through every step from data fetching to AI-optimized backtesting to live signal deployment.
Workflow Architecture Overview
[Trigger] → [Data Fetch] → [Indicators] → [Signal Generator] → [Backtest Strategy]
↓
[AI Optimization]
↓
[Telegram Alert] ← [Live Signals]
Component Roles
| Component | Purpose |
|---|---|
| Trigger | Start workflow (manual, scheduled, or webhook) |
| Data Fetch | Get OHLC data from market data provider |
| Indicators | Calculate technical indicators (RSI, SMA, etc.) |
| Signal Generator | Convert indicators into trading signals |
| Backtest Strategy | Evaluate signal performance with AI optimization |
| Alerts/Execution | Notify or execute trades |
Step 1: Create the Trigger
Manual Trigger
For testing and development:
- Add Trigger worker to canvas
- Leave as manual trigger
- Click "Run" to test workflow
Scheduled Trigger
For production systems:
{
"everySeconds": 900,
"type": "interval"
}
Runs every 15 minutes (900 seconds).
Webhook Trigger
For external integration:
{
"type": "webhook"
}
Workflow runs when webhook URL receives POST request.
Step 2: Fetch Market Data
Using Twelve Data
Configuration:
{
"symbol": "NVDA",
"interval": "1h",
"type": "time_series",
"outputsize": 5000,
"api_key": "YOUR_API_KEY"
}
Output Fields:
datetime: Timestampopen,high,low,close: OHLC datavolume: Trading volume
Using Polygon.io
Configuration:
{
"ticker": "AAPL",
"multiplier": 1,
"timespan": "hour",
"from": "2024-01-01",
"to": "2024-12-31",
"api_key": "YOUR_API_KEY"
}
Using Binance
Configuration:
{
"symbol": "BTCUSDT",
"interval": "1h",
"limit": 1000,
"type": "klines"
}
Step 3: Add Technical Indicators
Option A: Using Twelve Data Built-in Indicators
RSI:
{
"symbol": "NVDA",
"interval": "1h",
"type": "rsi",
"time_period": 14
}
SMA:
{
"symbol": "NVDA",
"interval": "1h",
"type": "sma",
"time_period": 20
}
Option B: Using Python Code Worker
Calculate Multiple Indicators:
import numpy as np
# Get data from previous worker
data = workers[0]['results']
# Calculate RSI
def calculate_rsi(prices, period=14):
deltas = np.diff(prices)
gains = np.where(deltas > 0, deltas, 0)
losses = np.where(deltas < 0, -deltas, 0)
avg_gain = np.convolve(gains, np.ones(period)/period, mode='valid')
avg_loss = np.convolve(losses, np.ones(period)/period, mode='valid')
rs = avg_gain / (avg_loss + 1e-10)
rsi = 100 - (100 / (1 + rs))
# Pad to match original length
return np.concatenate([[np.nan] * period, rsi])
# Calculate SMAs
def sma(prices, period):
return np.convolve(prices, np.ones(period)/period, mode='same')
closes = np.array([row['close'] for row in data])
# Add indicators to data
for i, row in enumerate(data):
row['rsi'] = calculate_rsi(closes, 14)[i] if i >= 14 else None
row['sma_20'] = sma(closes, 20)[i]
row['sma_50'] = sma(closes, 50)[i]
result = data
Option C: Using Merge Data Worker
If you fetch OHLC and indicators separately, merge them:
{
"table-1": "{{workers[0].results}}",
"table-2": "{{workers[1].rsi}}",
"merge_key": "datetime"
}
Step 4: Configure Signal Generator
Connect Data
{
"data": "{{workers[2].result}}"
}
Replace workers[2] with the index of your indicator worker.
Define Entry Conditions
RSI + Trend Strategy:
{
"long_conditions": [
{"left": "rsi", "operator": "crosses_above", "right": "30"},
{"left": "close", "operator": ">", "right": "sma_50"}
],
"long_logic": "AND",
"short_conditions": [
{"left": "rsi", "operator": "crosses_below", "right": "70"},
{"left": "close", "operator": "<", "right": "sma_50"}
],
"short_logic": "AND"
}
Configure Signal Handling
For Backtesting (let backtest handle exits):
{
"close_mode": "none",
"signal_mode": "first"
}
For Live Trading (explicit closes):
{
"close_mode": "reverse",
"signal_mode": "first"
}
Step 5: Configure Backtest Strategy
Basic Connection
{
"data": "{{workers[2].result}}",
"signals": "{{workers[3].signals}}"
}
Field Mapping
{
"time_field": "datetime",
"open_field": "open",
"high_field": "high",
"low_field": "low",
"close_field": "close"
}
Enable AI Optimization
{
"ai_optimize": true,
"ai_optimize_target": "sharpe_ratio",
"ai_sl_tp_type": "percent"
}
Full Configuration Example
{
"data": "{{workers[2].result}}",
"signals": "{{workers[3].signals}}",
"initial_capital": 10000,
"commission": 0.001,
"slippage": 0.0005,
"time_field": "datetime",
"open_field": "open",
"high_field": "high",
"low_field": "low",
"close_field": "close",
"ai_optimize": true,
"ai_optimize_target": "sharpe_ratio",
"ai_sl_tp_type": "percent",
"ai_top_results_count": 10
}
Step 6: Analyze Results
Understanding AI Output
Message:
🤖 AI analyzed 280 combinations based on your data's volatility. Found profitable strategy with Sharpe ratio of 1.72.
Best Parameters:
{
"stop_loss_value": 1.8,
"take_profit_value": 4.5,
"position_size": 0.15,
"trailing_stop": true
}
Recommendations:
- ✅ Strong risk-adjusted returns
- ✅ Win rate above 50%
- 💡 Consider block analysis validation
Verify Different Top Results
Change ai_show_trades_for to see trades for different parameter sets:
{
"ai_show_trades_for": 3
}
Now you see trades, equity curve, and drawdown for the 3rd best result.
Add Block Analysis
After confirming AI finds good parameters, validate with walk-forward:
{
"analysis_blocks": 6
}
Check consistency score - should be 60+ for reliable strategy.
Step 7: Deploy to Live Trading
Option A: Alert-Only Deployment
Add Telegram Notify worker after Signal Generator:
Configuration:
{
"chatId": "YOUR_CHAT_ID",
"message": "🚨 {{workers[3].signals[-1].action|upper}} Signal\n📊 Symbol: NVDA\n💰 Current Price: {{workers[2].result[-1].close}}\n📈 RSI: {{workers[2].result[-1].rsi|round(1)}}"
}
Condition (only on new signal):
{
"expression": "workers[3].signals|length > 0"
}
Option B: Live Execution
Add broker integration after Signal Generator:
Binance Example:
{
"api_key": "YOUR_KEY",
"api_secret": "YOUR_SECRET",
"type": "create_order",
"symbol": "BTCUSDT",
"side": "{{workers[3].signals[-1].action|upper}}",
"quantity": "0.01"
}
Important: Use conditions to prevent duplicate orders:
{
"expression": "workers[3].signals[-1].time > vars.last_signal_time"
}
Complete Workflow Example
Workflow Structure
[Trigger: Schedule 15m]
↓
[Twelve Data: NVDA 1h]
↓
[Python Code: Add RSI, SMA20, SMA50]
↓
[Signal Generator: RSI Mean Reversion]
↓
[Backtest Strategy: AI Optimize]
↓
[Condition: Has New Signal?]
↓ ↓
No Yes
↓ ↓
[End] [Telegram: Alert]
Worker Configurations
Worker 0 - Schedule Trigger:
{
"everySeconds": 900,
"type": "interval"
}
Worker 1 - Twelve Data:
{
"symbol": "NVDA",
"interval": "1h",
"type": "time_series",
"outputsize": 2000,
"api_key": "{{vars.twelve_data_key}}"
}
Worker 2 - Python Indicators:
import numpy as np
data = workers[0]['values']
closes = np.array([r['close'] for r in data])
# RSI calculation
def rsi(prices, period=14):
deltas = np.diff(prices)
gains = np.where(deltas > 0, deltas, 0)
losses = np.where(deltas < 0, -deltas, 0)
avg_gain = np.convolve(gains, np.ones(period)/period, mode='valid')
avg_loss = np.convolve(losses, np.ones(period)/period, mode='valid')
rs = avg_gain / (avg_loss + 1e-10)
rsi_values = 100 - (100 / (1 + rs))
return np.concatenate([[np.nan] * period, rsi_values])
# SMA calculation
def sma(prices, period):
return np.convolve(prices, np.ones(period)/period, mode='same')
rsi_vals = rsi(closes, 14)
sma20 = sma(closes, 20)
sma50 = sma(closes, 50)
for i, row in enumerate(data):
row['rsi'] = float(rsi_vals[i]) if not np.isnan(rsi_vals[i]) else None
row['sma_20'] = float(sma20[i])
row['sma_50'] = float(sma50[i])
result = data
Worker 3 - Signal Generator:
{
"data": "{{workers[2].result}}",
"time_field": "datetime",
"long_conditions": [
{"left": "rsi", "operator": "crosses_above", "right": "30"},
{"left": "close", "operator": ">", "right": "sma_50"}
],
"long_logic": "AND",
"short_conditions": [
{"left": "rsi", "operator": "crosses_below", "right": "70"},
{"left": "close", "operator": "<", "right": "sma_50"}
],
"short_logic": "AND",
"close_mode": "none",
"signal_mode": "first"
}
Worker 4 - Backtest Strategy:
{
"data": "{{workers[2].result}}",
"signals": "{{workers[3].signals}}",
"initial_capital": 10000,
"commission": 0.001,
"slippage": 0.0005,
"ai_optimize": true,
"ai_optimize_target": "sharpe_ratio"
}
Worker 5 - Condition:
{
"expression": "workers[3].summary.total_signals > vars.last_signal_count"
}
Worker 6 - Telegram Notify:
{
"chatId": "{{vars.telegram_chat}}",
"message": "📊 NVDA Strategy Update\n\n🎯 Latest Signal: {{workers[3].signals[-1].action}}\n💰 Backtest Return: {{workers[4].best_results.total_return_pct|round(1)}}%\n📈 Sharpe: {{workers[4].best_results.sharpe_ratio|round(2)}}\n🎲 Win Rate: {{workers[4].best_results.win_rate|round(1)}}%"
}
Advanced: Multi-Symbol Workflow
Parallel Symbol Processing
[Trigger]
↓
[Loop: For each symbol]
↓
[Data Fetch: {{loop.item}}]
↓
[Indicators]
↓
[Signal Generator]
↓
[Backtest]
↓
[Condition: Good strategy?]
↓
[Alert: Symbol opportunity]
Using Variables for Symbol List
Set Variables:
{
"symbols": ["NVDA", "AAPL", "MSFT", "GOOGL", "AMZN"],
"current_index": 0
}
Loop Worker:
{
"max_iterations": 5,
"iterate_over": "vars.symbols"
}
Data Fetch (Dynamic Symbol):
{
"symbol": "{{loop.item}}",
"interval": "1h",
"type": "time_series"
}
Monitoring and Maintenance
Track Strategy Performance
Use Table Widget to display backtest results:
{
"widgetId": "strategy_performance",
"columns": ["symbol", "return_pct", "sharpe", "win_rate", "trades"],
"data": "{{vars.strategy_results}}"
}
Alert on Degradation
Add condition to detect strategy degradation:
{
"expression": "workers[4].best_results.sharpe_ratio < 0.5"
}
Then notify if triggered:
{
"message": "⚠️ Strategy performance degraded!\nSharpe: {{workers[4].best_results.sharpe_ratio}}"
}
Regular Re-optimization
Schedule weekly re-optimization to adapt to market changes:
{
"everySeconds": 604800,
"type": "interval"
}
Best Practices
1. Start Simple
- Begin with one symbol, one strategy
- Validate before adding complexity
- Build incrementally
2. Always Backtest Before Live
- Never deploy untested strategies
- Require minimum Sharpe of 1.0
- Validate with block analysis
3. Use Paper Trading First
- Test with small amounts
- Monitor for unexpected behavior
- Check execution quality
4. Monitor Continuously
- Set up alerts for anomalies
- Track live vs backtest performance
- Adjust parameters as needed
5. Document Everything
- Save successful configurations
- Record parameter changes
- Note market conditions
Troubleshooting
No Signals Generated
- Check time_field mapping
- Verify indicator calculations
- Test with
signal_mode: every - Review data for gaps
Poor Backtest Results
- Try different optimization target
- Test alternative SL/TP types
- Review signal logic
- Check data quality
Execution Delays
- Use faster data intervals
- Optimize workflow complexity
- Consider webhook triggers
- Check API rate limits
Building complete trading systems in ApudFlow brings together powerful components that would traditionally require extensive coding and multiple platforms. Start with this template, customize for your strategy, and iterate based on results.