Skip to main content

Code Mode

Code Mode lets you write full Python trading strategies using the Freqtrade strategy API. This gives you complete control over every aspect of your strategy — custom indicators, advanced callbacks, hyperopt parameters, and any logic that fits inside the Freqtrade contract.

info

Code Mode is available on Pro and Team plans.

Getting Started

  1. Navigate to StrategiesNew Strategy
  2. Select Code Mode
  3. Write your strategy in the Monaco code editor
  4. Click Save Strategy

Strategy Structure

A VolatiCloud strategy is a standard Freqtrade strategy class.

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from pandas import DataFrame
import talib.abstract as ta

class MyStrategy(IStrategy):
# Required: candle timeframe
timeframe = '1h'

# Required: stop-loss (-10%)
stoploss = -0.10

# Optional: ROI table
minimal_roi = {
"60": 0.01, # 1% profit after 60 minutes
"30": 0.02, # 2% profit after 30 minutes
"0": 0.04 # 4% profit immediately
}

def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Add indicators to dataframe
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200)

macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']

bollinger = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2, nbdevdn=2)
dataframe['bb_upper'] = bollinger['upperband']
dataframe['bb_lower'] = bollinger['lowerband']
dataframe['bb_mid'] = bollinger['middleband']

return dataframe

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Define entry conditions
dataframe.loc[
(dataframe['rsi'] < 30) &
(dataframe['close'] > dataframe['ema_200']),
'enter_long'
] = 1
return dataframe

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Define exit conditions
dataframe.loc[
dataframe['rsi'] > 70,
'exit_long'
] = 1
return dataframe

Key Methods

populate_indicators

Calculate technical indicators and add them to the dataframe. This runs once per new candle.

def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# metadata['pair'] contains the current pair, e.g., 'BTC/USDT'
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe

populate_entry_trend

Set enter_long = 1 rows where you want to open long positions. Set enter_short = 1 for shorts.

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['rsi'] < 30) &
(dataframe['volume'] > 0),
'enter_long'
] = 1
return dataframe

populate_exit_trend

Set exit_long = 1 where you want to close long positions. Use exit_short = 1 for shorts.

custom_stoploss (optional)

Define dynamic stop-loss based on current trade data:

def custom_stoploss(self, pair: str, trade, current_time, current_rate,
current_profit: float, **kwargs) -> float:
# Trail stop-loss at 1% below the 20-candle high
if current_profit > 0.02:
return -0.01 # Tight stop after 2% profit
return self.stoploss # Use default otherwise

Hyperparameters

Use IntParameter and DecimalParameter to define tunable parameters for hyperopt.

class MyStrategy(IStrategy):
# Define tunable parameters
buy_rsi = IntParameter(20, 40, default=30, space='buy')
sell_rsi = IntParameter(60, 80, default=70, space='sell')
ema_period = IntParameter(50, 300, default=200, space='buy')

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
dataframe['rsi'] < self.buy_rsi.value,
'enter_long'
] = 1
return dataframe

VolatiCloud's strategy analyzer automatically discovers these declarations and pre-populates them in the hyperopt UI — no manual entry required.

Long/Short Strategies

For futures trading with both long and short positions:

class LongShortStrategy(IStrategy):
can_short = True

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Long entry
dataframe.loc[dataframe['rsi'] < 30, 'enter_long'] = 1
# Short entry
dataframe.loc[dataframe['rsi'] > 70, 'enter_short'] = 1
return dataframe

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Close longs
dataframe.loc[dataframe['rsi'] > 70, 'exit_long'] = 1
# Close shorts
dataframe.loc[dataframe['rsi'] < 30, 'exit_short'] = 1
return dataframe

Available Libraries

The following Python libraries are available in Code Mode.

LibraryPurpose
ta-libTechnical analysis (via talib)
pandasData manipulation
numpyNumerical computing
pandas_taAdditional technical indicators
freqtrade.strategyStrategy base classes and utilities

Code Editor Features

The integrated Monaco editor provides:

  • Syntax highlighting for Python
  • Auto-completion for common patterns
  • Inline lint markers from the strategy analyzer (errors, warnings, info — see Insights tab)
  • Line numbers and code folding

Strategy Studio Preview tab showing a generated Freqtrade Python strategy class in the Monaco code editor with Python syntax highlighting and line numbers

Saving and Versioning

Every time you click Save Strategy:

  • A new immutable version is created
  • The previous version is preserved
  • Backtests linked to old versions remain valid
  • Bots continue running the version they were configured with

See the versioning guide for the full lifecycle.

Testing Your Strategy

Before deploying live:

  1. Save your strategy and review the Insights tab for lint errors
  2. Run a Backtest to validate historical performance
  3. Deploy as Dry Run bot to test real-time behavior
  4. Only then switch to Live mode

Common Patterns

Moving Average Crossover

def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=9)
dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=21)
return dataframe

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(dataframe['ema_fast'] > dataframe['ema_slow']) &
(dataframe['ema_fast'].shift(1) <= dataframe['ema_slow'].shift(1)),
'enter_long'
] = 1
return dataframe

Bollinger Band Bounce

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
dataframe['close'] < dataframe['bb_lower'],
'enter_long'
] = 1
return dataframe

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
dataframe['close'] > dataframe['bb_mid'],
'exit_long'
] = 1
return dataframe