Skip to main content

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

ComponentPurpose
TriggerStart workflow (manual, scheduled, or webhook)
Data FetchGet OHLC data from market data provider
IndicatorsCalculate technical indicators (RSI, SMA, etc.)
Signal GeneratorConvert indicators into trading signals
Backtest StrategyEvaluate signal performance with AI optimization
Alerts/ExecutionNotify or execute trades

Step 1: Create the Trigger

Manual Trigger

For testing and development:

  1. Add Trigger worker to canvas
  2. Leave as manual trigger
  3. 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: Timestamp
  • open, high, low, close: OHLC data
  • volume: 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

  1. Check time_field mapping
  2. Verify indicator calculations
  3. Test with signal_mode: every
  4. Review data for gaps

Poor Backtest Results

  1. Try different optimization target
  2. Test alternative SL/TP types
  3. Review signal logic
  4. Check data quality

Execution Delays

  1. Use faster data intervals
  2. Optimize workflow complexity
  3. Consider webhook triggers
  4. 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.