Skip to main content

Signal Generator - Build Trading Signals Without Code

The Signal Generator transforms how traders create entry and exit signals. Instead of learning Pine Script, MQL4, or Python, you build signals using a visual condition builder that's both powerful and intuitive. This guide covers everything from basic conditions to advanced nested logic and percentage-based signals.

Why Signal Generator?

Traditional vs ApudFlow Approach

Traditional Approach (Pine Script):

// Requires learning Pine Script syntax
longCondition = ta.crossover(ta.rsi(close, 14), 30) and close > ta.sma(close, 200)
if (longCondition)
strategy.entry("Long", strategy.long)

ApudFlow Approach (Visual):

{
"long_conditions": [
{"left": "rsi", "operator": "crosses_above", "right": "30"},
{"left": "close", "operator": ">", "right": "sma_200"}
],
"long_logic": "AND"
}

Both achieve the same result, but the ApudFlow version:

  • ✅ Requires no coding knowledge
  • ✅ Uses visual dropdown selectors
  • ✅ Auto-completes field names from your data
  • ✅ Validates conditions before execution

Basic Condition Structure

Every condition has three parts:

left: field or expression
operator: comparison operator
right: value, field, or expression

Left Side (What to Check)

  • Field name: rsi, close, sma_20
  • Expression: high - low, close * 0.99
  • Previous bar: close[-1]
  • Percentage function: pct_change(close)

Operator (How to Compare)

CategoryOperators
Numeric>, >=, <, <=, ==, !=
Crossingcrosses_above, crosses_below
Stringcontains, not_contains, starts_with, ends_with, matches
Existenceis_empty, is_not_empty

Right Side (Compare Against)

  • Static number: 30, 1.5, -10
  • Field: sma_20, bb_upper
  • Expression: close[-1] + atr
  • Text: "USDT" (for string operators)

Numeric Operators in Detail

Basic Comparisons

Greater Than

{"left": "rsi", "operator": ">", "right": "70"}

Signal when RSI is above 70.

Less Than or Equal

{"left": "close", "operator": "<=", "right": "bb_lower"}

Signal when price touches or breaks below lower Bollinger Band.

Crossing Operators

Crosses Above - Signal only on the bar where crossing occurs

{"left": "sma_20", "operator": "crosses_above", "right": "sma_50"}

Golden cross signal - fast MA crosses above slow MA.

Crosses Below - Opposite direction

{"left": "rsi", "operator": "crosses_below", "right": "70"}

RSI exits overbought zone.

Key Difference:

  • > triggers on EVERY bar where condition is true
  • crosses_above triggers ONLY on the bar where it becomes true

Field Math and Expressions

You can perform calculations directly in conditions:

Simple Math

{"left": "volume", "operator": ">", "right": "avg_volume * 2"}

Volume is more than 2x average.

Two-Field Comparison

{"left": "high - low", "operator": ">", "right": "atr"}

Candle range exceeds ATR (high volatility bar).

Percentage of Price

{"left": "high - low", "operator": ">", "right": "close * 0.02"}

Candle range exceeds 2% of price.

Previous Bar Reference

{"left": "close", "operator": ">", "right": "close[-1] * 1.03"}

Price jumped 3% from previous bar.

Percentage Functions

Three powerful functions for percentage-based conditions:

pct_change(field)

Returns % change from previous bar.

Signal on 3%+ price spike:

{"left": "pct_change(close)", "operator": ">=", "right": "3"}

Signal on volume surge (50%+ increase):

{"left": "pct_change(volume)", "operator": ">=", "right": "50"}

abs_pct_change(field)

Returns absolute % change (always positive).

Signal on any big move (2%+ up or down):

{"left": "abs_pct_change(close)", "operator": ">=", "right": "2"}

pct(a, b)

Returns percentage difference: (a - b) / b × 100

Close is 2% above open (bullish candle):

{"left": "pct(close, open)", "operator": ">=", "right": "2"}

High broke above previous high by 1%:

{"left": "pct(high, high[-1])", "operator": ">=", "right": "1"}

AND/OR Logic

Simple AND (All Must Be True)

{
"long_conditions": [
{"left": "rsi", "operator": "<", "right": "30"},
{"left": "close", "operator": ">", "right": "sma_200"}
],
"long_logic": "AND"
}

RSI oversold AND price above 200 SMA (trend filter).

Simple OR (Any Can Be True)

{
"long_conditions": [
{"left": "rsi", "operator": "<", "right": "30"},
{"left": "close", "operator": "<=", "right": "bb_lower"}
],
"long_logic": "OR"
}

RSI oversold OR price at lower Bollinger Band.

Nested Groups (Complex Logic)

For (A AND B) OR (C AND D):

{
"long_conditions": [
{
"logic": "AND",
"conditions": [
{"left": "rsi", "operator": "<", "right": "30"},
{"left": "macd", "operator": ">", "right": "0"}
]
},
{
"logic": "AND",
"conditions": [
{"left": "close", "operator": ">", "right": "sma_200"},
{"left": "adx", "operator": ">", "right": "25"}
]
}
],
"long_logic": "OR"
}

Signal when: (RSI oversold AND MACD positive) OR (Above 200 SMA AND strong trend)

Signal Modes (Avoiding Duplicates)

One of the most important settings to understand:

The Problem

Condition: close > 4000

  • every mode: Generates signal on EVERY bar where close > 4000 (duplicates!)
  • first mode: Only generates signal on the FIRST bar where close > 4000
{
"signal_mode": "first",
"long_conditions": [{"left": "close", "operator": ">", "right": "sma_20"}]
}

Signal only when price CROSSES above SMA, not while it stays above.

cooldown Mode

{
"signal_mode": "cooldown",
"cooldown_bars": 10,
"long_conditions": [{"left": "rsi", "operator": "<", "right": "30"}]
}

After a signal, wait at least 10 bars before allowing another.

{
"signal_mode": "cooldown",
"cooldown_seconds": 3600
}

After a signal, wait at least 1 hour.

Close Modes

Control when positions are closed:

ModeDescriptionUse Case
reverseClose on opposite signalMost strategies
conditionsClose only when explicit conditions metSeparate exit logic
bothClose on reverse OR conditionsMaximum flexibility
noneNever generate close signalsUse backtest SL/TP

reverse Mode (Default)

{
"close_mode": "reverse"
}

Long signal → enter long Short signal → close long + enter short

conditions Mode

{
"close_mode": "conditions",
"long_conditions": [{"left": "rsi", "operator": "<", "right": "30"}],
"close_long_conditions": [{"left": "rsi", "operator": ">", "right": "50"}]
}

Enter when RSI < 30, exit when RSI > 50 (specific exit).

{
"close_mode": "none"
}

Let Backtest Strategy handle exits via SL/TP/trailing.

String/Text Operators

Filter by symbol, news, or text patterns:

Symbol Filtering

{"left": "symbol", "operator": "contains", "right": "BTC"}

Only BTC pairs.

{"left": "symbol", "operator": "ends_with", "right": "USDT"}

Only USDT pairs.

News/Headline Filtering

{"left": "headline", "operator": "matches", "right": "bull|bullish|surge|rally"}

Regex match for bullish keywords.

Non-Empty Check

{"left": "signal", "operator": "is_not_empty", "right": ""}

Only when signal field has a value.

Candle Pattern Conditions

Bullish Candle (Close > Open)

{"left": "close - open", "operator": ">", "right": "0"}

Large Candle (20+ Pips for Forex)

{
"long_conditions": [
{"left": "high - low", "operator": ">", "right": "0.0020"},
{"left": "close - open", "operator": ">", "right": "0"}
],
"long_logic": "AND"
}

Bullish Reversal (Up After Down)

{
"long_conditions": [
{"left": "close - open", "operator": ">", "right": "0"},
{"left": "close[-1] - open[-1]", "operator": "<", "right": "0"}
],
"long_logic": "AND"
}

Bullish Engulfing

{
"long_conditions": [
{"left": "close - open", "operator": ">", "right": "0"},
{"left": "close[-1] - open[-1]", "operator": "<", "right": "0"},
{"left": "close - open", "operator": ">", "right": "open[-1] - close[-1]"}
],
"long_logic": "AND"
}

Gap Up

{"left": "open", "operator": ">", "right": "high[-1]"}

Higher High + Higher Low (Trend)

{
"long_conditions": [
{"left": "high", "operator": ">", "right": "high[-1]"},
{"left": "low", "operator": ">", "right": "low[-1]"}
],
"long_logic": "AND"
}

Complete Strategy Examples

1. RSI Mean Reversion

{
"long_conditions": [
{"left": "rsi", "operator": "crosses_above", "right": "30"}
],
"short_conditions": [
{"left": "rsi", "operator": "crosses_below", "right": "70"}
],
"close_mode": "reverse"
}

2. Moving Average Crossover

{
"long_conditions": [
{"left": "sma_20", "operator": "crosses_above", "right": "sma_50"}
],
"short_conditions": [
{"left": "sma_20", "operator": "crosses_below", "right": "sma_50"}
]
}

3. Breakout with Volume

{
"long_conditions": [
{"left": "close", "operator": ">", "right": "high[-1]"},
{"left": "volume", "operator": ">", "right": "volume_sma * 1.5"}
],
"long_logic": "AND",
"close_mode": "none"
}

4. Bollinger Band Squeeze

{
"long_conditions": [
{"left": "close", "operator": "<=", "right": "bb_lower"}
],
"close_long_conditions": [
{"left": "close", "operator": ">=", "right": "bb_middle"}
],
"close_mode": "conditions"
}

5. Momentum with Trend Filter

{
"long_conditions": [
{
"logic": "AND",
"conditions": [
{"left": "pct_change(close)", "operator": ">=", "right": "2"},
{"left": "close", "operator": ">", "right": "sma_200"}
]
}
],
"signal_mode": "cooldown",
"cooldown_bars": 5
}

6. Multi-Symbol with Text Filter

{
"long_conditions": [
{"left": "symbol", "operator": "contains", "right": "BTC"},
{"left": "rsi", "operator": "<", "right": "35"}
],
"long_logic": "AND"
}

Output Structure

Signal Generator produces:

signals Array

{
"signals": [
{"time": 1700000000, "action": "long"},
{"time": 1700003600, "action": "close"},
{"time": 1700007200, "action": "short"}
]
}

summary Statistics

{
"summary": {
"total_signals": 150,
"long_signals": 50,
"short_signals": 50,
"close_signals": 50,
"data_rows": 1000
}
}

Connecting to Backtest Strategy

Signal Generator output connects directly to Backtest Strategy:

[Data Source] → [Indicators] → [Signal Generator] → [Backtest Strategy]

{signals: [...]}

In Backtest Strategy, set:

signals: {{workers[X].signals}}

Tips and Best Practices

1. Use signal_mode: first by Default

Prevents duplicate signals on consecutive bars.

2. Use crosses_above/below for Entry

More precise than >/>= which trigger every bar.

3. Set close_mode: none with Backtest

Let SL/TP handle exits for cleaner results.

4. Pre-calculate Complex Indicators

If expression is complex, calculate in upstream worker.

5. Test Signal Count

Check summary.total_signals - if too high, add more conditions or use cooldown.

6. Use Percentage Functions

pct_change(close) is more universal than absolute price differences.

Troubleshooting

No Signals Generated?

  1. Check time_field matches your data (datetime, time, date)
  2. Verify field names exist in your data
  3. Try signal_mode: every temporarily
  4. Check for data gaps

Too Many Signals?

  1. Use signal_mode: first (default)
  2. Add more restrictive conditions
  3. Use crosses_above/below instead of >/>=
  4. Add cooldown period

Signals Not Closing?

  1. Check close_mode setting
  2. If using conditions, verify close conditions exist
  3. Try close_mode: both

The Signal Generator empowers you to build sophisticated trading signals without code. Start simple, test thoroughly, and gradually add complexity as you understand each feature. Combined with Backtest Strategy's AI optimization, you have a complete toolkit for strategy development.