3Commas Bot Strategy
Simplified version of the 3Commas Bot strategy. It trades when a fast EMA crosses a slower EMA and manages risk using an ATR based stop. A fixed reward target and optional ATR trailing stop are supported.
Details
- Entry Criteria:
- Long: fast EMA crosses above slow EMA.
- Short: fast EMA crosses below slow EMA.
- Long/Short: Both sides.
- Exit Criteria:
- ATR stop, optional take-profit, optional ATR trailing stop once a reward threshold is met.
- Stops: ATR based.
- Default Values:
MaLength1= 21MaLength2= 50AtrLength= 14RnR= 1RiskM= 1
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: EMA, ATR
- Stops: Yes
- Complexity: Medium
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Simplified port of the "3Commas Bot" TradingView strategy.
/// Uses two moving averages for trend detection.
/// Enters long on bullish cross, short on bearish cross.
/// Exits via opposite cross or ATR-based stop-loss.
/// </summary>
public class ThreeCommasBotStrategy : Strategy
{
private readonly StrategyParam<int> _maLength1;
private readonly StrategyParam<int> _maLength2;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _riskM;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _stopPrice;
private decimal _entryPrice;
private bool _initialized;
private bool _wasFastAboveSlow;
private int _cooldownRemaining;
public int MaLength1 { get => _maLength1.Value; set => _maLength1.Value = value; }
public int MaLength2 { get => _maLength2.Value; set => _maLength2.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
public decimal RiskM { get => _riskM.Value; set => _riskM.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public ThreeCommasBotStrategy()
{
_maLength1 = Param(nameof(MaLength1), 50)
.SetDisplay("MA Length #1", "Fast moving average length", "MA Settings")
.SetGreaterThanZero();
_maLength2 = Param(nameof(MaLength2), 100)
.SetDisplay("MA Length #2", "Slow moving average length", "MA Settings")
.SetGreaterThanZero();
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR length", "ATR calculation period", "Risk Management")
.SetGreaterThanZero();
_riskM = Param(nameof(RiskM), 3m)
.SetDisplay("Risk Adjustment", "ATR multiplier for stop", "Risk Management")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stopPrice = 0;
_entryPrice = 0;
_initialized = false;
_wasFastAboveSlow = false;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastMa = new ExponentialMovingAverage { Length = MaLength1 };
var slowMa = new ExponentialMovingAverage { Length = MaLength2 };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastMa, slowMa, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_initialized)
{
_wasFastAboveSlow = fastValue > slowValue;
_initialized = true;
return;
}
// Check stop-loss exits (always, regardless of cooldown)
if (Position > 0 && _stopPrice > 0 && candle.LowPrice <= _stopPrice)
{
SellMarket(Math.Abs(Position));
_stopPrice = 0;
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && _stopPrice > 0 && candle.HighPrice >= _stopPrice)
{
BuyMarket(Math.Abs(Position));
_stopPrice = 0;
_cooldownRemaining = CooldownBars;
}
var isFastAboveSlow = fastValue > slowValue;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_wasFastAboveSlow = isFastAboveSlow;
return;
}
// MA crossover entries
if (_wasFastAboveSlow != isFastAboveSlow)
{
if (isFastAboveSlow && Position <= 0)
{
// Bullish cross - go long
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_entryPrice = candle.ClosePrice;
_stopPrice = candle.ClosePrice - atrValue * RiskM;
_cooldownRemaining = CooldownBars;
}
else if (!isFastAboveSlow && Position >= 0)
{
// Bearish cross - go short
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_entryPrice = candle.ClosePrice;
_stopPrice = candle.ClosePrice + atrValue * RiskM;
_cooldownRemaining = CooldownBars;
}
}
_wasFastAboveSlow = isFastAboveSlow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class three_commas_bot_strategy(Strategy):
"""3Commas Bot Strategy."""
def __init__(self):
super(three_commas_bot_strategy, self).__init__()
self._ma_length1 = self.Param("MaLength1", 50) \
.SetDisplay("MA Length #1", "Fast moving average length", "MA Settings")
self._ma_length2 = self.Param("MaLength2", 100) \
.SetDisplay("MA Length #2", "Slow moving average length", "MA Settings")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR length", "ATR calculation period", "Risk Management")
self._risk_m = self.Param("RiskM", 3.0) \
.SetDisplay("Risk Adjustment", "ATR multiplier for stop", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._stop_price = 0.0
self._entry_price = 0.0
self._initialized = False
self._was_fast_above_slow = False
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(three_commas_bot_strategy, self).OnReseted()
self._stop_price = 0.0
self._entry_price = 0.0
self._initialized = False
self._was_fast_above_slow = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(three_commas_bot_strategy, self).OnStarted2(time)
fast_ma = ExponentialMovingAverage()
fast_ma.Length = int(self._ma_length1.Value)
slow_ma = ExponentialMovingAverage()
slow_ma.Length = int(self._ma_length2.Value)
atr = AverageTrueRange()
atr.Length = int(self._atr_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ma, slow_ma, atr, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_v = float(fast_value)
slow_v = float(slow_value)
atr_v = float(atr_value)
cooldown = int(self._cooldown_bars.Value)
if not self._initialized:
self._was_fast_above_slow = fast_v > slow_v
self._initialized = True
return
# Check stop-loss exits
if self.Position > 0 and self._stop_price > 0 and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(Math.Abs(self.Position))
self._stop_price = 0.0
self._cooldown_remaining = cooldown
elif self.Position < 0 and self._stop_price > 0 and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(Math.Abs(self.Position))
self._stop_price = 0.0
self._cooldown_remaining = cooldown
is_fast_above_slow = fast_v > slow_v
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._was_fast_above_slow = is_fast_above_slow
return
if self._was_fast_above_slow != is_fast_above_slow:
risk_m = float(self._risk_m.Value)
if is_fast_above_slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._entry_price = float(candle.ClosePrice)
self._stop_price = float(candle.ClosePrice) - atr_v * risk_m
self._cooldown_remaining = cooldown
elif not is_fast_above_slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._entry_price = float(candle.ClosePrice)
self._stop_price = float(candle.ClosePrice) + atr_v * risk_m
self._cooldown_remaining = cooldown
self._was_fast_above_slow = is_fast_above_slow
def CreateClone(self):
return three_commas_bot_strategy()