La Estrategia SwingTrader es una StockSharp versión del MetaTrader 4 asesores expertos SwingTrader.mq4. El EA original busca
Bollinger Inversiones de banda: cuando el precio rebota desde la banda exterior y la siguiente barra cruza la línea media, el asesor abre una
posición y comienza a construir una cuadrícula de promedio estilo martingala. La estrategia traducida reproduce el mismo comportamiento de alto nivel.
usando StockSharp velas, Bollinger bandas de StockSharp.Algo.Indicators y los ayudantes de pedidos del marco (BuyMarket,
SellMarket). El escalado de volumen, el ancho de la cuadrícula y las reglas de liquidación reflejan el código MT4 respetando el intercambio.
límites proporcionados por los metadatos Security.
Lógica comercial
Suscríbase al período de tiempo configurado (CandleType) y alimente un indicador de Bollinger Bandas con BollingerPeriod longitud y un
multiplicador de desviación estándar fijo de 2.
Trabaje sólo con velas terminadas; la devolución de llamada del indicador ignora las barras parcialmente formadas para replicar el MT4 IsNewCandle()
guardia.
Realice un seguimiento de si la vela anterior tocó la banda superior o inferior. El par booleano _upTouch / _downTouch sigue al
Lógica de alternancia original que mantiene solo un lado activo hasta que se toca la banda opuesta.
Cuando no hay ninguna cesta abierta:
abra una posición larga si la última barra completa cruzó por encima de la banda media después de tocar previamente la banda inferior;
abra una posición corta si la barra cruzó por debajo de la banda media después de tocar la banda superior.
El volumen del primer pedido es igual a InitialVolume (después del redondeo de intercambio) y el ancho inicial de la cuadrícula es igual a la última distancia
entre las bandas superior e inferior Bollinger.
Cuando exista una canasta, esté atento a movimientos adversos de un ancho de banda completo desde el primer llenado:
para posiciones largas, si el mínimo de la vela está al menos un ancho de banda por debajo del precio del ancla, compre otra porción cuyo tamaño se multiplique
por Multiplier con cada nuevo nivel;
para cortos, si el máximo de la vela está un ancho de banda por encima del precio ancla, venda una porción adicional usando el mismo
lógica multiplicadora.
Continúe agregando nuevos pedidos hasta alcanzar el objetivo de ganancias o de pérdida máxima tolerada.
Gestión del dinero y salidas.
El asistente CalculateUnrealizedProfit reproduce el cálculo de PnL flotante de MT4 convirtiendo las diferencias de precios en precios.
pasos (Security.PriceStep) y valor del paso (Security.StepPrice).
El indicador de capital invertido utiliza la fórmula original Lots * Price / TickSize * TickValue / 30, donde Lots se convierte en la suma
de los volúmenes de la cuadrícula y los parámetros de tick provienen de Security.
Cierra toda la cesta una vez que el beneficio flotante supere TakeProfitFactor * invested capital.
Forzar una liquidación de emergencia cuando la pérdida flotante alcance 10 * TakeProfitFactor * invested capital (la misma proporción que la
código MT4).
Todas las salidas se ejecutan con órdenes de mercado en dirección opuesta; Una vez plano, el estado de la cuadrícula se restablece y se deben realizar nuevos toques.
detectado antes de que se pueda activar otra entrada.
Parámetros
Nombre
Tipo
Predeterminado
Descripción
TakeProfitFactor
decimal
0.05
Multiplicador aplicado al capital invertido para definir el objetivo de beneficio.
Multiplier
decimal
1.5
Multiplicador de volumen por cada pedido promedio adicional.
BollingerPeriod
int
20
Número de velas utilizadas por el indicador Bollinger Bandas.
InitialVolume
decimal
1
Volumen base de la primera operación en una nueva cesta (redondeado a los límites del lugar).
CandleType
DataType
plazo de 15 minutos
Plazo utilizado para la generación de señales.
Diferencias con el EA original
StockSharp trabaja con posiciones netas; la estrategia mantiene listas explícitas de entradas de la cuadrícula para emular el orden basado en tickets de MT4
manipulación.
En su lugar, los filtros de volumen de Exchange (Security.MinVolume, Security.VolumeStep, Security.MaxVolume) se aplican automáticamente
de llamar manualmente a CheckVolumeValue.
Las señales se evalúan sobre velas cerradas; Los disparadores intrabar de la versión MT4 se aproximan mediante el uso de máximos y mínimos de velas.
para promediar decisiones.
Las órdenes siempre se envían como instrucciones de mercado, mientras que MT4 usaba OrderSend con parámetros de oferta/demanda explícitos.
Notas de uso
Proporcionar metadatos realistas para el instrumento negociado: PriceStep, StepPrice, MinVolume, VolumeStep y MaxVolume deben
se completará para que los cálculos de ganancias, pérdidas y volumen coincidan con el comportamiento de MT4.
Debido a que la cuadrícula promedio escala geométricamente, pruebe la configuración con datos históricos y considere el margen del corredor.
requisitos antes de ejecutarlo en vivo.
El ancho de la cuadrícula es igual al ancho de banda Bollinger actual; cambiar BollingerPeriod afecta directamente tanto el tiempo de entrada como la grilla
espaciado. Valide la sensibilidad durante la optimización.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "SwingTrader" MetaTrader expert.
/// Uses Bollinger Band touches to detect swing direction, then enters on middle-band cross.
/// </summary>
public class SwingTraderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private BollingerBands _bollinger;
private bool _upTouch;
private bool _downTouch;
private decimal? _prevClose;
private decimal? _prevMiddle;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
public SwingTraderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signals", "General");
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 2m)
.SetGreaterThanZero()
.SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerWidth };
_upTouch = false;
_downTouch = false;
_prevClose = null;
_prevMiddle = null;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollinger, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bbValue.IsFinal)
return;
if (bbValue is not BollingerBandsValue bbVal)
return;
if (bbVal.UpBand is not decimal upper || bbVal.LowBand is not decimal lower || bbVal.MovingAverage is not decimal middle)
return;
if (!_bollinger.IsFormed)
{
_prevClose = candle.ClosePrice;
_prevMiddle = middle;
return;
}
var close = candle.ClosePrice;
// Track Bollinger touches
if (candle.HighPrice > upper)
{
_upTouch = true;
_downTouch = false;
}
if (candle.LowPrice < lower)
{
_downTouch = true;
_upTouch = false;
}
if (_prevClose is null || _prevMiddle is null)
{
_prevClose = close;
_prevMiddle = middle;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Buy: had a lower band touch, now price crosses above middle
var buySignal = _downTouch && _prevClose.Value < _prevMiddle.Value && close > middle;
// Sell: had an upper band touch, now price crosses below middle
var sellSignal = _upTouch && _prevClose.Value > _prevMiddle.Value && close < middle;
if (buySignal)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
if (Position <= 0)
BuyMarket(volume);
_downTouch = false;
}
else if (sellSignal)
{
if (Position > 0)
SellMarket(Position);
if (Position >= 0)
SellMarket(volume);
_upTouch = false;
}
_prevClose = close;
_prevMiddle = middle;
}
/// <inheritdoc />
protected override void OnReseted()
{
_bollinger = null;
_upTouch = false;
_downTouch = false;
_prevClose = null;
_prevMiddle = null;
base.OnReseted();
}
}
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 BollingerBands
from StockSharp.Algo.Strategies import Strategy
class swing_trader_strategy(Strategy):
"""Bollinger Band touch swing: buy on lower touch + middle cross up, sell on upper touch + middle cross down."""
def __init__(self):
super(swing_trader_strategy, self).__init__()
self._bb_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("BB Period", "Bollinger Bands period", "Indicators")
self._bb_width = self.Param("BollingerWidth", 2.0).SetGreaterThanZero().SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe for signals", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(swing_trader_strategy, self).OnReseted()
self._up_touch = False
self._down_touch = False
self._prev_close = 0
self._prev_middle = 0
def OnStarted2(self, time):
super(swing_trader_strategy, self).OnStarted2(time)
self._up_touch = False
self._down_touch = False
self._prev_close = 0
self._prev_middle = 0
self._bb = BollingerBands()
self._bb.Length = self._bb_period.Value
self._bb.Width = self._bb_width.Value
sub = self.SubscribeCandles(self.CandleType)
sub.BindEx(self._bb, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, self._bb)
self.DrawOwnTrades(area)
def OnProcess(self, candle, bb_val):
if candle.State != CandleStates.Finished:
return
if not self._bb.IsFormed:
return
upper = float(bb_val.UpBand)
lower = float(bb_val.LowBand)
middle = float(bb_val.MovingAverage)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# Track Bollinger touches
if high > upper:
self._up_touch = True
self._down_touch = False
if low < lower:
self._down_touch = True
self._up_touch = False
if self._prev_close == 0 or self._prev_middle == 0:
self._prev_close = close
self._prev_middle = middle
return
# Buy: had lower band touch, now price crosses above middle
buy_signal = self._down_touch and self._prev_close < self._prev_middle and close > middle
# Sell: had upper band touch, now price crosses below middle
sell_signal = self._up_touch and self._prev_close > self._prev_middle and close < middle
if buy_signal:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
self._down_touch = False
elif sell_signal:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._up_touch = False
self._prev_close = close
self._prev_middle = middle
def CreateClone(self):
return swing_trader_strategy()