Python Part 3 - Common Patterns
Python to HaasScript: Common Patterns
Python trading strategies use certain patterns repeatedly. This page shows how those patterns map conceptually to HaasScript — not line-by-line translations, but the HaasScript equivalent of what you are actually trying to accomplish.
Configuration and Constants
Python — variables at module level, or a config dict/class:
RSI_LENGTH = 14
RSI_OVERSOLD = 30
TAKE_PROFIT_MULT = 2.0
config = {
'symbol': 'BTC/USDT',
'timeframe': '1h',
'dry_run': True,
}
HaasScript — Input() for user-configurable values, plain variables for internal constants:
-- User configurable (appears in bot UI)
local rsiLength = Input('RSI Length', 14)
local dryRun = Input('Dry Run', true)
-- Internal constant (not exposed in UI)
local takeProfitMult = 2.0
Use Input() for anything a user might want to change without editing code. Use plain local variables for values that are part of the strategy's logic and should not be modified by users.
Multiple Indicator Confirmation
Python — chain conditions in an if statement:
if (df['rsi'].iloc[-1] < 30
and df['close'].iloc[-1] > df['ema_50'].iloc[-1]
and df['macd'].iloc[-1] > df['macd_signal'].iloc[-1]):
exchange.create_market_buy_order(symbol, amount)
HaasScript — same pattern works, but the signal system is often cleaner:
-- Direct conditions (works fine)
local rsi = RSI(ClosePrices(), 14)
local ema = EMA(ClosePrices(), 50)
local macd = MACD(ClosePrices(), 12, 26, 9)
if rsi < 30 and closePrices > ema and macd[1] > macd[3] then
DoLong()
end
-- Signal system (better for 3+ indicators, reusable, composable)
local rsiSignal = GetBuySellLevelSignal(rsi, 30, 70)
local macdSignal = GetCrossOverUnderSignal(macd[1], macd[3])
local consensus = GetConsensusSignal(rsiSignal, macdSignal)
DoSignal(consensus)
Use direct conditions for two indicators. Use the signal system when you have three or more, or when you want to mix long/short/exit signals from different sources.
ATR-Based Dynamic Stop Loss
Python — calculate a price level and place a stop order:
atr = ta.volatility.average_true_range(df['high'], df['low'], df['close'], 14)
current_atr = atr.iloc[-1]
stop_price = entry_price - (current_atr * 2)
exchange.create_order(
symbol, type='stop_loss', side='sell',
amount=amount, price=stop_price
)
HaasScript — convert ATR from price to percentage, then use StopLoss():
local atr = ATR(HighPrices(), LowPrices(), ClosePrices(), 14)
-- ATR is a price value. StopLoss() takes a percentage.
-- Convert: (atr / currentPrice) * 100 = percentage
local atrPercentage = (atr / ClosePrices()) * 100
StopLoss(atrPercentage * 2)
Call this once at the top of the script, unconditionally. The bot applies it to whichever position is currently open and updates it on every tick as ATR changes.
Trailing Stop
Python — manually track the highest price and adjust the stop level:
if position is not None:
if current_price > highest_price:
highest_price = current_price
trailing_stop = highest_price * (1 - trailing_pct)
if current_price <= trailing_stop:
exchange.create_market_sell_order(symbol, amount)
position = None
HaasScript — single command:
TrailingStopLoss(1.5) -- 1.5% trailing
For more control, HaasScript offers variations:
TrailingStopLoss(1.5) -- Simple trailing
GrowingTrailingStopLoss(1.5, 0.5) -- Widens as profit increases
ShrinkingTrailingStopLoss(1.5, 0.5) -- Tightens as profit increases
TrailingArmStopLoss(1.5, 2.0) -- Activates only after 2% profit
Cooldown Between Trades
Python — track the last trade time and compare:
last_trade_time = 0
cooldown_seconds = 3600
if time.time() - last_trade_time > cooldown_seconds:
# Allow trade
exchange.create_market_buy_order(symbol, amount)
last_trade_time = time.time()
HaasScript — TradeOncePerBar() or StopLossCooldown():
-- Only trade once per candle bar
TradeOncePerBar()
-- Wait N minutes after a safety-triggered exit
StopLossCooldown(60)
TradeOncePerBar() prevents multiple signals on the same candle. StopLossCooldown() specifically prevents re-entry after a stop loss or take profit triggered.
Multi-Timeframe Confirmation
Python — resample the dataframe:
df_1h = fetch_ohlcv(symbol, '1h')
df_4h = fetch_ohlcv(symbol, '4h')
df_4h['ema'] = ta.trend.ema_indicator(df_4h['close'], 20)
trend_bullish = df_1h['close'].iloc[-1] > df_4h['ema'].iloc[-1]
HaasScript — pass the interval parameter (in minutes) to price functions:
local hourlyClose = ClosePrices(60)
local fourHourClose = ClosePrices(240)
local fourHourEMA = EMA(fourHourClose, 20)
local trendBullish = hourlyClose > fourHourEMA
Common intervals in minutes: 1, 5, 15, 30, 60, 120, 240, 1440, 10080.
Position State Checks
Python — manual tracking:
in_position = False
position_side = None
if position is not None:
in_position = True
position_side = position['side']
if in_position and position_side == 'long':
# do something
HaasScript — GetPositionDirection():
local position = GetPositionDirection()
if position == NoPosition then
-- Not in a trade
elseif position == PositionLong then
-- Currently long
elseif position == PositionShort then
-- Currently short
end
Additional position queries:
GetPositionEnterPrice() -- Your average entry price
GetPositionProfit() -- Realized + unrealized profit
GetPositionROI() -- ROI as a percentage
GetPositionAmount() -- Position size
Conditional Feature Toggles
Python — boolean flags:
USE_TRAILING_STOP = True
USE_TAKE_PROFIT = False
if USE_TRAILING_STOP:
set_trailing_stop(...)
HaasScript — Input() booleans:
local useTrailingStop = Input('Use Trailing Stop', true)
local useTakeProfit = Input('Use Take Profit', false)
if useTrailingStop then
TrailingStopLoss(1.5)
end
if useTakeProfit then
TakeProfit(5.0)
end
This is cleaner than editing constants in the script. Users can toggle features from the bot interface.
Logging and Debugging
Python — print() or the logging module:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info(f'RSI: {current_rsi:.2f}')
logger.warning('Position size too small')
HaasScript — Log(), LogWarning(), LogError():
Log('RSI: ' .. tostring(rsi))
LogWarning('Position size too small')
LogError('Failed to calculate indicator')
For debug-only logging, gate behind an input:
local debugMode = Input('Debug Mode', false)
if debugMode then
Log('RSI: ' .. tostring(rsi))
Log('EMA: ' .. tostring(ema))
Log('ATR%: ' .. tostring(atrPercentage))
end
Recording Metrics
Python — write to a file or database:
with open('trades.csv', 'a') as f:
f.write(f'{timestamp},{side},{profit}\n')
HaasScript — CustomReport() for backtest metrics, Save()/Load() for persistent counters:
-- Backtest metrics (appear in backtest results)
CustomReport('Total Trades', tradeCount)
CustomReport('Win Rate', winRate .. '%')
-- Persistent counter between ticks
local totalSignals = Load('totalSignals', 0)
totalSignals = totalSignals + 1
Save('totalSignals', totalSignals)
CustomReport() values appear in the backtest report alongside built-in fields. Save()/Load() persists data across ticks but does not survive bot restarts — use it for runtime counters, not permanent records.
Crossunder / Crossover Detection
Python — compare current and previous values:
prev_macd = df['macd'].iloc[-2]
curr_macd = df['macd'].iloc[-1]
prev_signal = df['macd_signal'].iloc[-2]
curr_signal = df['macd_signal'].iloc[-1]
bullish_cross = prev_macd < prev_signal and curr_macd > curr_signal
bearish_cross = prev_macd > prev_signal and curr_macd < curr_signal
HaasScript — CrossOver() and CrossUnder():
local macd = MACD(ClosePrices(), 12, 26, 9)
if CrossOver(macd[1], macd[3]) then
-- MACD line crossed above signal line
DoLong()
end
if CrossUnder(macd[1], macd[3]) then
-- MACD line crossed below signal line
DoShort()
end
For time since the last crossover (useful for filters):
local barsSinceCross = CrossOverSince(macd[1], macd[3])
if barsSinceCross > 3 then
-- Crossover happened more than 3 bars ago
end
Error Handling
Python — try/except:
try:
order = exchange.create_market_buy_order(symbol, amount)
except ccxt.InsufficientFunds:
logging.error('Insufficient funds')
except ccxt.NetworkError:
logging.error('Network error, retrying')
HaasScript — IfNull() for nil checks, LogWalletError() for trade amount problems:
local rsi = RSI(ClosePrices(), 14)
-- Handle missing data
local threshold = IfNull(rsi, 50)
-- Flag wallet/balance issues (stops trade execution until resolved)
local tradeAmount = TradeAmount()
local minAmount = MinimumTradeAmount()
if tradeAmount < minAmount then
LogWalletError('Trade amount too small: ' .. tostring(tradeAmount))
end
HaasScript does not have try/catch. Exchange errors, network issues, and order failures are handled by the TradeServer platform. Your script handles data validation and business logic errors.
Highest / Lowest in a Window
Python — rolling window on a dataframe:
df['highest_20'] = df['high'].rolling(20).max()
df['lowest_20'] = df['low'].rolling(20).min()
current_high = df['highest_20'].iloc[-1]
current_low = df['lowest_20'].iloc[-1]
HaasScript — GetHigh() and GetLow():
local highest20 = GetHigh(HighPrices(), 20) -- Highest high in 20 periods
local lowest20 = GetLow(LowPrices(), 20) -- Lowest low in 20 periods
if ClosePrices() >= highest20 then
Log('20-period high broken')
end
For full rolling series (not just the single highest/lowest value), use GetHighs() and GetLows() which return HaasNumberCollection.