3Commas Bot 策略
3Commas Bot 策略的简化版本。当快速 EMA 上穿或下穿慢速 EMA 时开仓,风险管理使用 ATR 止损,可选的固定收益目标和基于 ATR 的追踪止损。
详情
- 入场条件:
- 多头: 快速 EMA 上穿慢速 EMA。
- 空头: 快速 EMA 下穿慢速 EMA。
- 方向: 多空皆可。
- 出场条件:
- ATR 止损,可选止盈,可选达到收益阈值后的 ATR 追踪止损。
- 止损: 基于 ATR。
- 默认参数:
MaLength1= 21MaLength2= 50AtrLength= 14RnR= 1RiskM= 1
- 过滤器:
- 类别: 趋势跟随
- 方向: 双向
- 指标: EMA, ATR
- 止损: 是
- 复杂度: 中等
- 时间框架: 任意
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险级别: 中等
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()