Mutanabby AI Algo Pro
该策略在出现看涨吞没形态、RSI 低于阈值且价格低于 N 根前的收盘价时做多。出现看跌吞没或触发止损时平仓。
细节
- 入场条件:看涨吞没、稳定蜡烛、RSI 低于阈值、价格低于 N 根前。
- 方向:仅做多。
- 出场条件:看跌吞没或止损。
- 止损:可选。
- 默认值:
CandleStabilityIndex= 0.5RsiIndex= 50CandleDeltaLength= 5DisableRepeatingSignals= falseEnableStopLoss= trueStopLossMethod= EntryPriceBasedEntryStopLossPercent= 2.0LookbackPeriod= 10StopLossBufferPercent= 0.5CandleType= TimeSpan.FromMinutes(1)
- 过滤器:
- 分类:趋势跟随
- 方向:做多
- 指标:RSI
- 止损:是
- 复杂度:基础
- 时间框架:日内
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Mutanabby AI Algo Pro strategy.
/// Enters long on bullish engulfing with RSI and price filters.
/// </summary>
public class MutanabbyAiAlgoProStrategy : Strategy
{
/// <summary>
/// Defines stop loss calculation modes.
/// </summary>
public enum StopLossModes
{
/// <summary>
/// Stop loss is calculated from entry price.
/// </summary>
EntryPriceBased,
/// <summary>
/// Stop loss is based on the lowest low over a period.
/// </summary>
LowestLowBased
}
private readonly StrategyParam<decimal> _candleStabilityIndex;
private readonly StrategyParam<int> _rsiIndex;
private readonly StrategyParam<int> _candleDeltaLength;
private readonly StrategyParam<bool> _disableRepeatingSignals;
private readonly StrategyParam<bool> _enableStopLoss;
private readonly StrategyParam<StopLossModes> _stopLossMethod;
private readonly StrategyParam<decimal> _entryStopLossPercent;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _stopLossBufferPercent;
private readonly StrategyParam<DataType> _candleType;
private static readonly object _sync = new();
private decimal _prevOpen;
private decimal _prevClose;
private readonly Queue<decimal> _closeQueue = new();
private readonly Queue<decimal> _lowQueue = new();
private decimal _lowestLow = decimal.MaxValue;
private string _lastSignal = string.Empty;
private decimal _stopLossPrice;
private decimal _entryPrice;
/// <summary>
/// Minimum ratio between candle body and true range.
/// </summary>
public decimal CandleStabilityIndex
{
get => _candleStabilityIndex.Value;
set => _candleStabilityIndex.Value = value;
}
/// <summary>
/// RSI threshold.
/// </summary>
public int RsiIndex
{
get => _rsiIndex.Value;
set => _rsiIndex.Value = value;
}
/// <summary>
/// Bars for price comparison.
/// </summary>
public int CandleDeltaLength
{
get => _candleDeltaLength.Value;
set => _candleDeltaLength.Value = value;
}
/// <summary>
/// Prevent consecutive identical signals.
/// </summary>
public bool DisableRepeatingSignals
{
get => _disableRepeatingSignals.Value;
set => _disableRepeatingSignals.Value = value;
}
/// <summary>
/// Enable stop loss.
/// </summary>
public bool EnableStopLoss
{
get => _enableStopLoss.Value;
set => _enableStopLoss.Value = value;
}
/// <summary>
/// Stop loss calculation method.
/// </summary>
public StopLossModes StopLossMethod
{
get => _stopLossMethod.Value;
set => _stopLossMethod.Value = value;
}
/// <summary>
/// Entry based stop loss percent.
/// </summary>
public decimal EntryStopLossPercent
{
get => _entryStopLossPercent.Value;
set => _entryStopLossPercent.Value = value;
}
/// <summary>
/// Lookback period for lowest low stop.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Buffer percent below lowest low.
/// </summary>
public decimal StopLossBufferPercent
{
get => _stopLossBufferPercent.Value;
set => _stopLossBufferPercent.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MutanabbyAiAlgoProStrategy"/> class.
/// </summary>
public MutanabbyAiAlgoProStrategy()
{
_candleStabilityIndex = Param(nameof(CandleStabilityIndex), 0.5m)
.SetDisplay("Candle Stability Index", "Minimum body/true range ratio", "Technical")
.SetRange(0m, 1m)
.SetOptimize(0.1m, 1m, 0.1m);
_rsiIndex = Param(nameof(RsiIndex), 50)
.SetDisplay("RSI Index", "RSI threshold for entries", "Technical")
.SetRange(0, 100)
;
_candleDeltaLength = Param(nameof(CandleDeltaLength), 5)
.SetDisplay("Candle Delta Length", "Bars for price comparison", "Technical")
.SetRange(3, 50)
;
_disableRepeatingSignals = Param(nameof(DisableRepeatingSignals), false)
.SetDisplay("Disable Repeating Signals", "Avoid consecutive identical signals", "Technical");
_enableStopLoss = Param(nameof(EnableStopLoss), true)
.SetDisplay("Enable Stop Loss", "Activate stop loss", "Risk Management");
_stopLossMethod = Param(nameof(StopLossMethod), StopLossModes.EntryPriceBased)
.SetDisplay("Stop Loss Method", "Entry price or lowest low based", "Risk Management");
_entryStopLossPercent = Param(nameof(EntryStopLossPercent), 2.0m)
.SetDisplay("Entry Stop Loss %", "Stop loss percent from entry", "Risk Management")
.SetGreaterThanZero();
_lookbackPeriod = Param(nameof(LookbackPeriod), 10)
.SetDisplay("Lookback Period", "Bars for lowest low stop", "Risk Management")
.SetGreaterThanZero();
_stopLossBufferPercent = Param(nameof(StopLossBufferPercent), 0.5m)
.SetDisplay("Stop Loss Buffer %", "Additional buffer below lowest low", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevOpen = 0;
_prevClose = 0;
_closeQueue.Clear();
_lowQueue.Clear();
_lowestLow = decimal.MaxValue;
_lastSignal = string.Empty;
_stopLossPrice = 0;
_entryPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RSI { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(candle => ProcessCandle(candle, rsi))
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, RelativeStrengthIndex rsi)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
decimal rsiValue;
lock (_sync)
{
var rsiResult = rsi.Process(new DecimalIndicatorValue(rsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
if (!rsiResult.IsFinal || !rsi.IsFormed)
return;
rsiValue = rsiResult.ToDecimal();
}
// update low queue
if (_lowQueue.Count == LookbackPeriod)
{
var removed = _lowQueue.Dequeue();
if (removed <= _lowestLow)
{
_lowestLow = decimal.MaxValue;
foreach (var v in _lowQueue)
{
if (v < _lowestLow)
_lowestLow = v;
}
}
}
_lowQueue.Enqueue(candle.LowPrice);
if (candle.LowPrice < _lowestLow)
_lowestLow = candle.LowPrice;
var priceN = _closeQueue.Count == CandleDeltaLength ? _closeQueue.Peek() : (decimal?)null;
var trueRange = candle.HighPrice - candle.LowPrice;
var stableCandle = trueRange > 0 && Math.Abs(candle.ClosePrice - candle.OpenPrice) / trueRange > CandleStabilityIndex;
var bullishEngulfing = _prevClose < _prevOpen && candle.ClosePrice > candle.OpenPrice && candle.ClosePrice > _prevOpen;
var rsiBelow = rsiValue < RsiIndex;
var decreaseOver = priceN != null && candle.ClosePrice < priceN;
var entrySignal = bullishEngulfing && stableCandle && rsiBelow && decreaseOver;
var bearishEngulfing = _prevClose > _prevOpen && candle.ClosePrice < candle.OpenPrice && candle.ClosePrice < _prevOpen;
var rsiAbove = rsiValue > 100 - RsiIndex;
var increaseOver = priceN != null && candle.ClosePrice > priceN;
var exitSignal = bearishEngulfing && stableCandle && rsiAbove && increaseOver;
if (entrySignal && Position <= 0 && (!DisableRepeatingSignals || _lastSignal != "buy"))
{
BuyMarket(Volume + Math.Abs(Position));
_entryPrice = candle.ClosePrice;
if (EnableStopLoss)
{
_stopLossPrice = StopLossMethod == StopLossModes.EntryPriceBased
? _entryPrice * (1 - EntryStopLossPercent / 100m)
: _lowestLow * (1 - StopLossBufferPercent / 100m);
}
_lastSignal = "buy";
}
if (exitSignal && Position > 0 && (!DisableRepeatingSignals || _lastSignal != "sell"))
{
SellMarket(Position);
_lastSignal = "sell";
}
if (EnableStopLoss && Position > 0 && _stopLossPrice > 0 && candle.ClosePrice <= _stopLossPrice)
{
SellMarket(Position);
_lastSignal = "sell";
}
_closeQueue.Enqueue(candle.ClosePrice);
if (_closeQueue.Count > CandleDeltaLength)
_closeQueue.Dequeue();
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class mutanabby_ai_algo_pro_strategy(Strategy):
def __init__(self):
super(mutanabby_ai_algo_pro_strategy, self).__init__()
self._candle_stability_index = self.Param("CandleStabilityIndex", 0.5) \
.SetDisplay("Candle Stability Index", "Minimum body/true range ratio", "Technical")
self._rsi_index = self.Param("RsiIndex", 50) \
.SetDisplay("RSI Index", "RSI threshold for entries", "Technical")
self._candle_delta_length = self.Param("CandleDeltaLength", 5) \
.SetDisplay("Candle Delta Length", "Bars for price comparison", "Technical")
self._disable_repeating_signals = self.Param("DisableRepeatingSignals", False) \
.SetDisplay("Disable Repeating Signals", "Avoid consecutive identical signals", "Technical")
self._enable_stop_loss = self.Param("EnableStopLoss", True) \
.SetDisplay("Enable Stop Loss", "Activate stop loss", "Risk Management")
self._entry_stop_loss_percent = self.Param("EntryStopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Entry Stop Loss %", "Stop loss percent from entry", "Risk Management")
self._lookback_period = self.Param("LookbackPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Bars for lowest low stop", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_open = 0.0
self._prev_close = 0.0
self._close_queue = []
self._low_queue = []
self._lowest_low = 999999999.0
self._last_signal = ""
self._stop_loss_price = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(mutanabby_ai_algo_pro_strategy, self).OnReseted()
self._prev_open = 0.0
self._prev_close = 0.0
self._close_queue = []
self._low_queue = []
self._lowest_low = 999999999.0
self._last_signal = ""
self._stop_loss_price = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(mutanabby_ai_algo_pro_strategy, self).OnStarted2(time)
self._prev_open = 0.0
self._prev_close = 0.0
self._close_queue = []
self._low_queue = []
self._lowest_low = 999999999.0
self._last_signal = ""
self._stop_loss_price = 0.0
self._entry_price = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
opn = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
rsi_result = process_float(self._rsi, candle.ClosePrice, candle.OpenTime, True)
if not self._rsi.IsFormed:
self._prev_open = opn
self._prev_close = close
self._close_queue.append(close)
cdl = self._candle_delta_length.Value
if len(self._close_queue) > cdl:
self._close_queue.pop(0)
self._low_queue.append(low)
lp = self._lookback_period.Value
if len(self._low_queue) > lp:
self._low_queue.pop(0)
self._lowest_low = min(self._low_queue) if self._low_queue else 999999999.0
return
rsi_value = float(rsi_result)
lp = self._lookback_period.Value
if len(self._low_queue) >= lp:
self._low_queue.pop(0)
self._low_queue.append(low)
self._lowest_low = min(self._low_queue) if self._low_queue else 999999999.0
cdl = self._candle_delta_length.Value
price_n = self._close_queue[0] if len(self._close_queue) == cdl else None
true_range = high - low
csi = float(self._candle_stability_index.Value)
stable_candle = true_range > 0.0 and abs(close - opn) / true_range > csi
bullish_engulfing = self._prev_close < self._prev_open and close > opn and close > self._prev_open
rsi_idx = self._rsi_index.Value
rsi_below = rsi_value < rsi_idx
decrease_over = price_n is not None and close < price_n
entry_signal = bullish_engulfing and stable_candle and rsi_below and decrease_over
bearish_engulfing = self._prev_close > self._prev_open and close < opn and close < self._prev_open
rsi_above = rsi_value > (100 - rsi_idx)
increase_over = price_n is not None and close > price_n
exit_signal = bearish_engulfing and stable_candle and rsi_above and increase_over
disable_rep = self._disable_repeating_signals.Value
if entry_signal and self.Position <= 0 and (not disable_rep or self._last_signal != "buy"):
self.BuyMarket()
self._entry_price = close
if self._enable_stop_loss.Value:
sl_pct = float(self._entry_stop_loss_percent.Value)
self._stop_loss_price = self._entry_price * (1.0 - sl_pct / 100.0)
self._last_signal = "buy"
if exit_signal and self.Position > 0 and (not disable_rep or self._last_signal != "sell"):
self.SellMarket()
self._last_signal = "sell"
if self._enable_stop_loss.Value and self.Position > 0 and self._stop_loss_price > 0.0 and close <= self._stop_loss_price:
self.SellMarket()
self._last_signal = "sell"
self._close_queue.append(close)
if len(self._close_queue) > cdl:
self._close_queue.pop(0)
self._prev_open = opn
self._prev_close = close
def CreateClone(self):
return mutanabby_ai_algo_pro_strategy()