Gandalf PRO 策略
该策略复刻了 MQL 平台上的 Gandalf PRO 智能交易顾问。策略在每根 K 线收盘价上计算 LWMA 和 SMA,并将结果送入原始 递归平滑公式,生成预测目标价。当预测价相对当前收盘价至少偏移 15 个价格步长时,策略会开出市价单,并记录基于预 测值的止损和止盈水平。
多头信号要求目标价比当前收盘价高出不少于 15 个步长;空头信号要求目标价比收盘价低出同样的幅度。止损参数以价格 步长为单位输入,运行时会根据证券的最小价格变动自动换算。止盈等于预测目标价,并在每根完成的 K 线上进行监控。 风险倍数参数用于放大或缩小基础下单量,从而实现简单的资金管理。
参数
- K 线类型
- 启用做多
- 做多长度
- 做多价格系数
- 做多趋势系数
- 做多止损
- 做多风险倍数
- 启用做空
- 做空长度
- 做空价格系数
- 做空趋势系数
- 做空止损
- 做空风险倍数
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>
/// Gandalf PRO strategy converted from MQL.
/// Calculates dynamic targets using LWMA/SMA smoothing filters and
/// trades when the projected level is sufficiently far from the current price.
/// </summary>
public class GandalfProStrategy : Strategy
{
private readonly StrategyParam<decimal> _entryBufferSteps;
private readonly StrategyParam<bool> _enableBuy;
private readonly StrategyParam<int> _buyLength;
private readonly StrategyParam<decimal> _buyPriceFactor;
private readonly StrategyParam<decimal> _buyTrendFactor;
private readonly StrategyParam<int> _buyStopLoss;
private readonly StrategyParam<decimal> _buyRiskMultiplier;
private readonly StrategyParam<bool> _enableSell;
private readonly StrategyParam<int> _sellLength;
private readonly StrategyParam<decimal> _sellPriceFactor;
private readonly StrategyParam<decimal> _sellTrendFactor;
private readonly StrategyParam<int> _sellStopLoss;
private readonly StrategyParam<decimal> _sellRiskMultiplier;
private readonly StrategyParam<DataType> _candleType;
private WeightedMovingAverage _buyWeighted = null!;
private SimpleMovingAverage _buySimple = null!;
private WeightedMovingAverage _sellWeighted = null!;
private SimpleMovingAverage _sellSimple = null!;
private decimal[] _closeHistory = Array.Empty<decimal>();
private int _availableHistory;
private int _maxPeriod;
private decimal _prevBuyWeighted;
private decimal _prevBuySimple;
private bool _hasPrevBuyValues;
private decimal _prevSellWeighted;
private decimal _prevSellSimple;
private bool _hasPrevSellValues;
private decimal? _longStopPrice;
private decimal? _longTargetPrice;
private decimal? _shortStopPrice;
private decimal? _shortTargetPrice;
private decimal _priceStep;
/// <summary>
/// Entry buffer distance in price steps.
/// </summary>
public decimal EntryBufferSteps
{
get => _entryBufferSteps.Value;
set => _entryBufferSteps.Value = value;
}
/// <summary>
/// Enable buy logic.
/// </summary>
public bool EnableBuy
{
get => _enableBuy.Value;
set => _enableBuy.Value = value;
}
/// <summary>
/// LWMA/SMA length for buys.
/// </summary>
public int BuyLength
{
get => _buyLength.Value;
set => _buyLength.Value = value;
}
/// <summary>
/// Price smoothing factor for buys.
/// </summary>
public decimal BuyPriceFactor
{
get => _buyPriceFactor.Value;
set => _buyPriceFactor.Value = value;
}
/// <summary>
/// Trend smoothing factor for buys.
/// </summary>
public decimal BuyTrendFactor
{
get => _buyTrendFactor.Value;
set => _buyTrendFactor.Value = value;
}
/// <summary>
/// Stop-loss distance for long trades in price steps.
/// </summary>
public int BuyStopLoss
{
get => _buyStopLoss.Value;
set => _buyStopLoss.Value = value;
}
/// <summary>
/// Multiplier applied to the strategy volume for longs.
/// </summary>
public decimal BuyRiskMultiplier
{
get => _buyRiskMultiplier.Value;
set => _buyRiskMultiplier.Value = value;
}
/// <summary>
/// Enable sell logic.
/// </summary>
public bool EnableSell
{
get => _enableSell.Value;
set => _enableSell.Value = value;
}
/// <summary>
/// LWMA/SMA length for sells.
/// </summary>
public int SellLength
{
get => _sellLength.Value;
set => _sellLength.Value = value;
}
/// <summary>
/// Price smoothing factor for sells.
/// </summary>
public decimal SellPriceFactor
{
get => _sellPriceFactor.Value;
set => _sellPriceFactor.Value = value;
}
/// <summary>
/// Trend smoothing factor for sells.
/// </summary>
public decimal SellTrendFactor
{
get => _sellTrendFactor.Value;
set => _sellTrendFactor.Value = value;
}
/// <summary>
/// Stop-loss distance for short trades in price steps.
/// </summary>
public int SellStopLoss
{
get => _sellStopLoss.Value;
set => _sellStopLoss.Value = value;
}
/// <summary>
/// Multiplier applied to the strategy volume for shorts.
/// </summary>
public decimal SellRiskMultiplier
{
get => _sellRiskMultiplier.Value;
set => _sellRiskMultiplier.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public GandalfProStrategy()
{
_entryBufferSteps = Param(nameof(EntryBufferSteps), 150m)
.SetNotNegative()
.SetDisplay("Entry Buffer", "Buffer distance in price steps", "General");
_enableBuy = Param(nameof(EnableBuy), true)
.SetDisplay("Enable Buy", "Allow long trades", "General");
_buyLength = Param(nameof(BuyLength), 24)
.SetGreaterThanZero()
.SetDisplay("Buy Length", "LWMA/SMA length for longs", "General")
.SetOptimize(5, 60, 1);
_buyPriceFactor = Param(nameof(BuyPriceFactor), 0.18m)
.SetRange(0m, 1m)
.SetDisplay("Buy Price Factor", "Recursive smoothing weight for price", "General")
.SetOptimize(0.05m, 0.5m, 0.01m);
_buyTrendFactor = Param(nameof(BuyTrendFactor), 0.18m)
.SetRange(0m, 1m)
.SetDisplay("Buy Trend Factor", "Recursive smoothing weight for trend", "General")
.SetOptimize(0.05m, 0.5m, 0.01m);
_buyStopLoss = Param(nameof(BuyStopLoss), 62)
.SetNotNegative()
.SetDisplay("Buy Stop Loss", "Stop distance for longs in price steps", "Risk");
_buyRiskMultiplier = Param(nameof(BuyRiskMultiplier), 0m)
.SetNotNegative()
.SetDisplay("Buy Risk Multiplier", "Volume multiplier for longs (0 = use base volume)", "Risk");
_enableSell = Param(nameof(EnableSell), true)
.SetDisplay("Enable Sell", "Allow short trades", "General");
_sellLength = Param(nameof(SellLength), 24)
.SetGreaterThanZero()
.SetDisplay("Sell Length", "LWMA/SMA length for shorts", "General")
.SetOptimize(5, 60, 1);
_sellPriceFactor = Param(nameof(SellPriceFactor), 0.18m)
.SetRange(0m, 1m)
.SetDisplay("Sell Price Factor", "Recursive smoothing weight for price", "General")
.SetOptimize(0.05m, 0.5m, 0.01m);
_sellTrendFactor = Param(nameof(SellTrendFactor), 0.18m)
.SetRange(0m, 1m)
.SetDisplay("Sell Trend Factor", "Recursive smoothing weight for trend", "General")
.SetOptimize(0.05m, 0.5m, 0.01m);
_sellStopLoss = Param(nameof(SellStopLoss), 62)
.SetNotNegative()
.SetDisplay("Sell Stop Loss", "Stop distance for shorts in price steps", "Risk");
_sellRiskMultiplier = Param(nameof(SellRiskMultiplier), 0m)
.SetNotNegative()
.SetDisplay("Sell Risk Multiplier", "Volume multiplier for shorts (0 = use base volume)", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Data type used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_buyWeighted?.Reset();
_buySimple?.Reset();
_sellWeighted?.Reset();
_sellSimple?.Reset();
_closeHistory = Array.Empty<decimal>();
_availableHistory = 0;
_maxPeriod = 0;
_prevBuyWeighted = 0m;
_prevBuySimple = 0m;
_hasPrevBuyValues = false;
_prevSellWeighted = 0m;
_prevSellSimple = 0m;
_hasPrevSellValues = false;
_longStopPrice = null;
_longTargetPrice = null;
_shortStopPrice = null;
_shortTargetPrice = null;
_priceStep = 1m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 1m;
_maxPeriod = Math.Max(BuyLength, SellLength);
_closeHistory = new decimal[_maxPeriod + 2];
_availableHistory = 0;
_buyWeighted = new WeightedMovingAverage
{
Length = BuyLength
};
_buySimple = new SMA
{
Length = BuyLength
};
_sellWeighted = new WeightedMovingAverage
{
Length = SellLength
};
_sellSimple = new SMA
{
Length = SellLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_buyWeighted, _buySimple, _sellWeighted, _sellSimple, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal buyWeightedValue, decimal buySimpleValue, decimal sellWeightedValue, decimal sellSimpleValue)
{
if (candle.State != CandleStates.Finished)
return;
ManageOpenPositions(candle);
var buyReady = _hasPrevBuyValues && _availableHistory >= BuyLength;
var sellReady = _hasPrevSellValues && _availableHistory >= SellLength;
if (EnableBuy && buyReady && IsFormedAndOnlineAndAllowTrading())
{
var target = CalculateTarget(BuyLength, BuyPriceFactor, BuyTrendFactor, _prevBuyWeighted, _prevBuySimple);
var entryPrice = candle.ClosePrice;
if (target > entryPrice + EntryBufferSteps * _priceStep)
{
var volume = GetOrderVolume(BuyRiskMultiplier);
if (volume > 0)
{
BuyMarket(volume);
_longTargetPrice = target;
_longStopPrice = BuyStopLoss > 0 ? entryPrice - BuyStopLoss * _priceStep : null;
}
}
}
if (EnableSell && sellReady && IsFormedAndOnlineAndAllowTrading())
{
var target = CalculateTarget(SellLength, SellPriceFactor, SellTrendFactor, _prevSellWeighted, _prevSellSimple);
var entryPrice = candle.ClosePrice;
if (target < entryPrice - EntryBufferSteps * _priceStep)
{
var volume = GetOrderVolume(SellRiskMultiplier);
if (volume > 0)
{
SellMarket(volume);
_shortTargetPrice = target;
_shortStopPrice = SellStopLoss > 0 ? entryPrice + SellStopLoss * _priceStep : null;
}
}
}
if (_buyWeighted.IsFormed && _buySimple.IsFormed)
{
_prevBuyWeighted = buyWeightedValue;
_prevBuySimple = buySimpleValue;
_hasPrevBuyValues = true;
}
if (_sellWeighted.IsFormed && _sellSimple.IsFormed)
{
_prevSellWeighted = sellWeightedValue;
_prevSellSimple = sellSimpleValue;
_hasPrevSellValues = true;
}
UpdateCloseHistory(candle.ClosePrice);
}
private void ManageOpenPositions(ICandleMessage candle)
{
if (Position > 0)
{
if ((_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value) ||
(_longTargetPrice.HasValue && candle.HighPrice >= _longTargetPrice.Value))
{
SellMarket(Math.Abs(Position));
_longStopPrice = null;
_longTargetPrice = null;
}
}
else
{
_longStopPrice = null;
_longTargetPrice = null;
}
if (Position < 0)
{
if ((_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value) ||
(_shortTargetPrice.HasValue && candle.LowPrice <= _shortTargetPrice.Value))
{
BuyMarket(Math.Abs(Position));
_shortStopPrice = null;
_shortTargetPrice = null;
}
}
else
{
_shortStopPrice = null;
_shortTargetPrice = null;
}
}
private void UpdateCloseHistory(decimal close)
{
if (_closeHistory.Length <= 2)
return;
for (var i = _closeHistory.Length - 1; i > 1; i--)
_closeHistory[i] = _closeHistory[i - 1];
_closeHistory[1] = close;
if (_availableHistory < _closeHistory.Length - 1)
_availableHistory++;
}
private decimal CalculateTarget(int length, decimal priceFactor, decimal trendFactor, decimal weightedPrev, decimal simplePrev)
{
if (length <= 1)
return 0m;
var t = new decimal[length + 2];
var s = new decimal[length + 2];
var lengthMinusOne = length - 1m;
var trendComponent = (6m * weightedPrev - 6m * simplePrev) / lengthMinusOne;
t[length] = trendComponent;
s[length] = 4m * simplePrev - 3m * weightedPrev - trendComponent;
for (var k = length - 1; k > 0; k--)
{
var close = _closeHistory[k];
s[k] = priceFactor * close + (1m - priceFactor) * (s[k + 1] + t[k + 1]);
t[k] = trendFactor * (s[k] - s[k + 1]) + (1m - trendFactor) * t[k + 1];
}
return s[1] + t[1];
}
private decimal GetOrderVolume(decimal riskMultiplier)
{
var baseVolume = Volume;
if (riskMultiplier <= 0m)
return baseVolume;
return baseVolume * riskMultiplier;
}
}
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 WeightedMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class gandalf_pro_strategy(Strategy):
def __init__(self):
super(gandalf_pro_strategy, self).__init__()
self._entry_buffer_steps = self.Param("EntryBufferSteps", 150.0)
self._enable_buy = self.Param("EnableBuy", True)
self._buy_length = self.Param("BuyLength", 24)
self._buy_price_factor = self.Param("BuyPriceFactor", 0.18)
self._buy_trend_factor = self.Param("BuyTrendFactor", 0.18)
self._buy_stop_loss = self.Param("BuyStopLoss", 62)
self._buy_risk_multiplier = self.Param("BuyRiskMultiplier", 0.0)
self._enable_sell = self.Param("EnableSell", True)
self._sell_length = self.Param("SellLength", 24)
self._sell_price_factor = self.Param("SellPriceFactor", 0.18)
self._sell_trend_factor = self.Param("SellTrendFactor", 0.18)
self._sell_stop_loss = self.Param("SellStopLoss", 62)
self._sell_risk_multiplier = self.Param("SellRiskMultiplier", 0.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._close_history = []
self._available_history = 0
self._max_period = 0
self._prev_buy_w = 0.0
self._prev_buy_s = 0.0
self._has_prev_buy = False
self._prev_sell_w = 0.0
self._prev_sell_s = 0.0
self._has_prev_sell = False
self._long_stop = None
self._long_target = None
self._short_stop = None
self._short_target = None
self._price_step = 1.0
self._buy_w_ind = None
self._buy_s_ind = None
self._sell_w_ind = None
self._sell_s_ind = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(gandalf_pro_strategy, self).OnReseted()
self._close_history = []
self._available_history = 0
self._max_period = 0
self._prev_buy_w = 0.0
self._prev_buy_s = 0.0
self._has_prev_buy = False
self._prev_sell_w = 0.0
self._prev_sell_s = 0.0
self._has_prev_sell = False
self._long_stop = None
self._long_target = None
self._short_stop = None
self._short_target = None
self._price_step = 1.0
def OnStarted2(self, time):
super(gandalf_pro_strategy, self).OnStarted2(time)
ps = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps <= 0:
ps = 1.0
self._price_step = ps
buy_len = int(self._buy_length.Value)
sell_len = int(self._sell_length.Value)
self._max_period = max(buy_len, sell_len)
self._close_history = [0.0] * (self._max_period + 2)
self._available_history = 0
self._buy_w_ind = WeightedMovingAverage()
self._buy_w_ind.Length = buy_len
self._buy_s_ind = SimpleMovingAverage()
self._buy_s_ind.Length = buy_len
self._sell_w_ind = WeightedMovingAverage()
self._sell_w_ind.Length = sell_len
self._sell_s_ind = SimpleMovingAverage()
self._sell_s_ind.Length = sell_len
self._prev_buy_w = 0.0
self._prev_buy_s = 0.0
self._has_prev_buy = False
self._prev_sell_w = 0.0
self._prev_sell_s = 0.0
self._has_prev_sell = False
self._long_stop = None
self._long_target = None
self._short_stop = None
self._short_target = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._buy_w_ind, self._buy_s_ind, self._sell_w_ind, self._sell_s_ind, self._process_candle).Start()
def _process_candle(self, candle, buy_w_val, buy_s_val, sell_w_val, sell_s_val):
if candle.State != CandleStates.Finished:
return
buy_w = float(buy_w_val)
buy_s = float(buy_s_val)
sell_w = float(sell_w_val)
sell_s = float(sell_s_val)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# Manage open positions
pos = float(self.Position)
if pos > 0:
if ((self._long_stop is not None and low <= self._long_stop) or
(self._long_target is not None and high >= self._long_target)):
self.SellMarket(abs(pos))
self._long_stop = None
self._long_target = None
else:
self._long_stop = None
self._long_target = None
pos = float(self.Position)
if pos < 0:
if ((self._short_stop is not None and high >= self._short_stop) or
(self._short_target is not None and low <= self._short_target)):
self.BuyMarket(abs(pos))
self._short_stop = None
self._short_target = None
else:
self._short_stop = None
self._short_target = None
buy_length = int(self._buy_length.Value)
sell_length = int(self._sell_length.Value)
buf = float(self._entry_buffer_steps.Value)
buy_ready = self._has_prev_buy and self._available_history >= buy_length
sell_ready = self._has_prev_sell and self._available_history >= sell_length
if self._enable_buy.Value and buy_ready and self.IsFormedAndOnlineAndAllowTrading():
target = self._calc_target(buy_length, float(self._buy_price_factor.Value),
float(self._buy_trend_factor.Value), self._prev_buy_w, self._prev_buy_s)
if target > close + buf * self._price_step:
volume = self._get_order_volume(float(self._buy_risk_multiplier.Value))
if volume > 0:
self.BuyMarket(volume)
self._long_target = target
sl = int(self._buy_stop_loss.Value)
self._long_stop = close - sl * self._price_step if sl > 0 else None
if self._enable_sell.Value and sell_ready and self.IsFormedAndOnlineAndAllowTrading():
target = self._calc_target(sell_length, float(self._sell_price_factor.Value),
float(self._sell_trend_factor.Value), self._prev_sell_w, self._prev_sell_s)
if target < close - buf * self._price_step:
volume = self._get_order_volume(float(self._sell_risk_multiplier.Value))
if volume > 0:
self.SellMarket(volume)
self._short_target = target
sl = int(self._sell_stop_loss.Value)
self._short_stop = close + sl * self._price_step if sl > 0 else None
if self._buy_w_ind.IsFormed and self._buy_s_ind.IsFormed:
self._prev_buy_w = buy_w
self._prev_buy_s = buy_s
self._has_prev_buy = True
if self._sell_w_ind.IsFormed and self._sell_s_ind.IsFormed:
self._prev_sell_w = sell_w
self._prev_sell_s = sell_s
self._has_prev_sell = True
self._update_close_history(close)
def _update_close_history(self, close):
if len(self._close_history) <= 2:
return
i = len(self._close_history) - 1
while i > 1:
self._close_history[i] = self._close_history[i - 1]
i -= 1
self._close_history[1] = close
if self._available_history < len(self._close_history) - 1:
self._available_history += 1
def _calc_target(self, length, price_factor, trend_factor, w_prev, s_prev):
if length <= 1:
return 0.0
t = [0.0] * (length + 2)
s = [0.0] * (length + 2)
lm1 = float(length - 1)
trend_comp = (6.0 * w_prev - 6.0 * s_prev) / lm1
t[length] = trend_comp
s[length] = 4.0 * s_prev - 3.0 * w_prev - trend_comp
for k in range(length - 1, 0, -1):
c = self._close_history[k]
s[k] = price_factor * c + (1.0 - price_factor) * (s[k + 1] + t[k + 1])
t[k] = trend_factor * (s[k] - s[k + 1]) + (1.0 - trend_factor) * t[k + 1]
return s[1] + t[1]
def _get_order_volume(self, risk_multiplier):
base_vol = float(self.Volume)
if risk_multiplier <= 0:
return base_vol
return base_vol * risk_multiplier
def CreateClone(self):
return gandalf_pro_strategy()