📰 Latest: HaasOnline Academy Is Back — Structured Education for Smarter Trade Bots
Account
Using Data

Signals

Signals

Signals are enumerated values that represent trading actions: enter long, enter short, exit position, or do nothing. Using signals provides a clean, consistent way to manage trading logic, especially when combining multiple indicators or creating reusable strategies.

Signal Types

HaasScript provides the following signal enumerations:

Signal Action Description
SignalLong / SignalBuy DoLong() Enter long position
SignalShort / SignalSell DoShort() Enter short position
SignalExitPosition DoExitPosition() Exit any current position
SignalExitLong DoCloseLong() Exit long position only
SignalExitShort DoCloseShort() Exit short position only
SignalNone No action Do not take any trading action
SignalError Error Indicates an error condition

Basic Signal Usage

Signals are returned by indicator functions and can be processed with DoSignal():

local rsiSignal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)

DoSignal(rsiSignal)  -- Executes appropriate action based on signal

DoSignal() automatically maps signals to trading commands:

  • SignalLongDoLong()
  • SignalShortDoShort()
  • SignalExitPositionDoExitPosition()
  • SignalNone → No action

Creating Signals from Values

GetBuySellLevelSignal

Generate signals based on value thresholds:

local rsi = RSI(ClosePrices(), 14)
local rsiSignal = GetBuySellLevelSignal(rsi, 30, 70)

-- Returns SignalLong when RSI < 30
-- Returns SignalShort when RSI > 70
-- Returns SignalNone when 30 ≤ RSI ≤ 70

GetThresholdSignal

Generate signals with optional swing buffer to prevent frequent flipping:

local macdValue = MACD(ClosePrices(), 12, 26, 9)[1]
local macdSignal = GetThresholdSignal(macdValue, 0, 0.5)

-- Below -0.5: SignalLong
-- Above 0.5: SignalShort
-- Between -0.5 and 0.5: SignalNone (swing buffer)

Combining Multiple Signals

GetConsensusSignal

Returns the majority signal from multiple indicators:

local rsiSignal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)
local macdSignal = GetThresholdSignal(MACD(ClosePrices(), 12, 26, 9)[1], 0, 0.1)
local bbSignal = GetThresholdSignal(BBANDS(ClosePrices(), 20, 2.0, 2.0)[1], 0, 0.1)

-- Get majority vote (at least 2 out of 3 must agree)
local consensusSignal = GetConsensusSignal(rsiSignal, macdSignal, bbSignal)

DoSignal(consensusSignal)

GetUnanimousSignal

Returns a signal only when all indicators agree:

local rsiSignal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)
local macdSignal = GetThresholdSignal(MACD(ClosePrices(), 12, 26, 9)[1], 0, 0.1)
local bbSignal = GetThresholdSignal(BBANDS(ClosePrices(), 20, 2.0, 2.0)[1], 0, 0.1)

-- Only act when all three signals match
local unanimousSignal = GetUnanimousSignal(rsiSignal, macdSignal, bbSignal)

DoSignal(unanimousSignal)

Signal Processing

IgnoreSignalIf

Prevent specific signals from being executed:

local rsiSignal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)

-- Ignore short signals
local longOnlySignal = IgnoreSignalIf(rsiSignal, SignalShort)

DoSignal(longOnlySignal)  -- Will only execute long or no action

ReverseSignal

Flip signals from long to short and vice versa:

local originalSignal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)
local reversedSignal = ReverseSignal(originalSignal)

-- SignalLong becomes SignalShort
-- SignalShort becomes SignalLong
-- SignalNone stays SignalNone

DelaySignal

Delay signal execution by a specified number of minutes:

local rsiSignal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)
local delayedSignal = DelaySignal(rsiSignal, 5)  -- Delay by 5 minutes

DoSignal(delayedSignal)  -- Signal from 5 minutes ago (if it exists)

This is useful for confirming signals before execution.

Signal Position Checks

Signals can be combined with position state for safer trading:

local rsiSignal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)

-- Only enter long if not already in long position
if rsiSignal == SignalLong and GetPositionDirection() != PositionLong then
    DoLong()
end

-- Only enter short if not already in short position
if rsiSignal == SignalShort and GetPositionDirection() != PositionShort then
    DoShort()
end

-- Exit on any exit signal
if rsiSignal == SignalExitPosition then
    DoExitPosition()
end

Easy Indicators

HaasScript provides "Easy" indicator functions that automatically handle input fields, charting, and return signals:

-- Easy RSI with automatic signal generation
local easyRsiSignal = EasyRSI(0, "MyRSI", 60)

-- The signal is based on default buy/sell levels (typically 30/70)
DoSignal(easyRsiSignal)

Easy indicators are convenient for quick prototyping but offer less control than manual signal generation.

Complete Example: Multi-Indicator Strategy

Here's a comprehensive example combining signals from multiple indicators:

-- Input parameters
local rsiLength = Input("RSI Length", 14)
local rsiOverbought = Input("RSI Overbought", 70)
local rsiOversold = Input("RSI Oversold", 30)

local macdFast = Input("MACD Fast", 12)
local macdSlow = Input("MACD Slow", 26)
local macdSignal = Input("MACD Signal", 9)

local bbLength = Input("BB Length", 20)
local bbDeviation = Input("BB Deviation", 2.0)

local closes = ClosePrices()

-- Generate individual signals
local rsiValue = RSI(closes, rsiLength)
local rsiSignal = GetBuySellLevelSignal(rsiValue, rsiOversold, rsiOverbought)

local macdLine = MACD(closes, macdFast, macdSlow, macdSignal).macd
local macdValue = macdLine[1] - macdLine[3]  -- MACD - Signal line
local macdTradeSignal = GetThresholdSignal(macdValue, 0, 0.2)

local bb = BBANDS(closes, bbLength, bbDeviation, bbDeviation)
local bbValue = (closes[1] - bb.lower) / (bb.upper - bb.lower)  -- Position within bands
local bbSignal = GetBuySellLevelSignal(bbValue, 0.2, 0.8)

-- Use consensus: at least 2 out of 3 must agree
local consensusSignal = GetConsensusSignal(rsiSignal, macdTradeSignal, bbSignal)
local posDirection = GetPositionDirection()

-- Position-aware execution
if consensusSignal == SignalLong and posDirection != PositionLong then
    DoLong()
    Log('Long entry: RSI=' .. rsiValue .. ' MACD=' .. macdValue)

elseif consensusSignal == SignalShort and posDirection != PositionShort then
    DoShort()
    Log('Short entry: RSI=' .. rsiValue .. ' MACD=' .. macdValue)

elseif consensusSignal == SignalExitPosition then
    local note = ''
    if posDirection == PositionLong then
        note = 'Closed long position'
    else
        note = 'Closed short position'
    end

    DoExitPosition(note)
    Log(note)
end

Advanced: Custom Signal Logic

You can create custom signal logic by returning signal enumerations:

function GenerateCustomSignal()
    local rsi = RSI(ClosePrices(), 14)
    local ema9 = EMA(ClosePrices(), 9)
    local ema21 = EMA(ClosePrices(), 21)

    -- Custom logic: RSI oversold AND fast MA above slow MA
    if rsi < 30 and ema9 > ema21 then
        return SignalLong
    end

    -- Custom logic: RSI overbought AND fast MA below slow MA
    if rsi > 70 and ema9 < ema21 then
        return SignalShort
    end

    -- No signal
    return SignalNone
end

local customSignal = GenerateCustomSignal()
DoSignal(customSignal)

Signal vs Direct Commands

Using signals:

local signal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)
DoSignal(signal)

Direct commands:

local rsi = RSI(ClosePrices(), 14)
if rsi < 30 then
    DoLong()
elseif rsi > 70 then
    DoShort()
end

Signals are preferable when:

  • Combining multiple indicators
  • Building reusable components
  • Need flexible signal processing (delay, reverse, consensus)
  • Want cleaner, more readable code

Direct commands are simpler for single-indicator strategies.

Common Mistakes

Not checking signal values:

-- Wrong: Always executes DoSignal, even on SignalNone
DoSignal(GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70))

-- Better: Check if there's an actual signal
local signal = GetBuySellLevelSignal(RSI(ClosePrices(), 14), 30, 70)
if signal ~= SignalNone then
    DoSignal(signal)
end

Forgetting position checks:

-- Risky: Tries to enter multiple long positions, produces spam
if signal == SignalLong then
    DoLong()
end

-- Better: Check current position
if signal == SignalLong and GetPositionDirection() != PositionLong then
    DoLong() -- Not going to spam "already in bought/long position"
end