Este port replica el asesor experto MetaTrader «Stop Loss Take Profit». La estrategia lanza una moneda cada vez que la cuenta está plana y abre una orden de mercado en la dirección elegida. Cada posición recibe inmediatamente órdenes de stop-loss y take-profit basadas en pips. Si se toca el stop, la siguiente operación duplica su tamaño (limitado por los límites de volumen del valor). Un take-profit reinicia el volumen a la cantidad inicial. El comportamiento refleja el dimensionamiento de posición estilo martingala original mientras usa la API de alto nivel de StockSharp.
Lógica de Trading
Datos de Mercado: Usa el parámetro CandleType (por defecto marco temporal de 1 minuto) para impulsar los puntos de decisión.
Reglas de Entrada:
Cuando Position == 0 y no hay orden de entrada pendiente, la estrategia genera un booleano pseudoaleatorio.
true abre una posición larga con BuyMarket(volume); false abre una corta con SellMarket(volume).
Reglas de Salida:
Las órdenes de stop-loss y take-profit protectores se colocan tan pronto como se recibe el fill de entrada.
Una salida por stop duplica el tamaño para la siguiente operación, mientras que un take-profit lo reinicia.
Si la distancia de stop o take-profit se establece en 0, se omite la orden protectora respectiva.
Gestión de Dinero:
InitialVolume define el tamaño base de la orden.
Después de una operación perdedora, el tamaño se duplica pero se recorta a Security.MaxVolume cuando ese valor está disponible.
El volumen se normaliza al VolumeStep, MinVolume y MaxVolume del instrumento para que las órdenes sean válidas.
Manejo de Pips:
Por defecto, la estrategia infiere un pip del PriceStep y Decimals del instrumento (los símbolos FX de 5 dígitos se asignan a 0.0001).
Establezca PipSize a un valor positivo para anular la detección automática del tamaño de pip.
Parámetros
Nombre
Por defecto
Descripción
CandleType
Velas de 1 minuto
Marco temporal usado para activar lanzamientos de moneda y entradas.
StopLossPips
1
Distancia de stop-loss expresada en pips. 0 deshabilita el stop.
TakeProfitPips
1
Distancia de take-profit expresada en pips. 0 deshabilita el take-profit.
InitialVolume
0.01
Volumen de operación inicial. Duplicado después de eventos de stop-loss y reiniciado después de ganancias.
PipSize
0 (auto)
Anulación opcional del tamaño de pip en unidades de precio absolutas.
Notas de Uso
Funciona tanto en el lado largo como corto y es intencionalmente neutral en dirección.
Las órdenes protectoras se cancelan siempre que la posición se cierra para evitar órdenes obsoletas.
El generador aleatorio se siembra con Environment.TickCount, lo que significa que cada sesión produce diferentes secuencias de operaciones.
Adecuado para demostrar la estratificación de riesgo y el comportamiento martingala en lugar de para trading en producción sin controles de riesgo adicionales.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Random direction strategy with pip-based stop loss and take profit that doubles the volume after losses.
/// </summary>
public class StopLossTakeProfitStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossDistance;
private readonly StrategyParam<decimal> _takeProfitDistance;
private readonly StrategyParam<decimal> _initialVolume;
private decimal _currentVolume;
private decimal _entryPrice;
private int _tradeCount;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal StopLossDistance
{
get => _stopLossDistance.Value;
set => _stopLossDistance.Value = value;
}
public decimal TakeProfitDistance
{
get => _takeProfitDistance.Value;
set => _takeProfitDistance.Value = value;
}
public decimal InitialVolume
{
get => _initialVolume.Value;
set => _initialVolume.Value = value;
}
public StopLossTakeProfitStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for evaluating entries", "General");
_stopLossDistance = Param(nameof(StopLossDistance), 5m)
.SetDisplay("Stop Loss Distance", "Stop loss distance in price units", "Risk");
_takeProfitDistance = Param(nameof(TakeProfitDistance), 5m)
.SetDisplay("Take Profit Distance", "Take profit distance in price units", "Risk");
_initialVolume = Param(nameof(InitialVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Initial Volume", "Starting order volume", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentVolume = 0;
_entryPrice = 0;
_tradeCount = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_currentVolume = InitialVolume;
_entryPrice = 0m;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Check SL/TP for open position
if (Position != 0 && _entryPrice > 0)
{
if (Position > 0)
{
var hitStop = StopLossDistance > 0 && candle.LowPrice <= _entryPrice - StopLossDistance;
var hitTake = TakeProfitDistance > 0 && candle.HighPrice >= _entryPrice + TakeProfitDistance;
if (hitStop)
{
SellMarket();
HandleStopLoss();
return;
}
if (hitTake)
{
SellMarket();
HandleTakeProfit();
return;
}
}
else if (Position < 0)
{
var hitStop = StopLossDistance > 0 && candle.HighPrice >= _entryPrice + StopLossDistance;
var hitTake = TakeProfitDistance > 0 && candle.LowPrice <= _entryPrice - TakeProfitDistance;
if (hitStop)
{
BuyMarket();
HandleStopLoss();
return;
}
if (hitTake)
{
BuyMarket();
HandleTakeProfit();
return;
}
}
}
// Enter new position when flat
if (Position == 0)
{
_tradeCount++;
// Use candle direction as a deterministic signal
if (candle.ClosePrice < candle.OpenPrice)
{
SellMarket();
}
else
{
BuyMarket();
}
_entryPrice = candle.ClosePrice;
}
}
private void HandleStopLoss()
{
// Double volume on loss (martingale)
_currentVolume *= 2m;
var maxVol = Security?.MaxVolume;
if (maxVol.HasValue && maxVol.Value > 0 && _currentVolume > maxVol.Value)
_currentVolume = maxVol.Value;
Volume = _currentVolume;
_entryPrice = 0m;
}
private void HandleTakeProfit()
{
// Reset volume on profit
_currentVolume = InitialVolume;
Volume = _currentVolume;
_entryPrice = 0m;
}
}
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.Strategies import Strategy
class stop_loss_take_profit_strategy(Strategy):
"""Candle-direction entries with pip-based SL/TP and martingale volume doubling on losses."""
def __init__(self):
super(stop_loss_take_profit_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for evaluating entries", "General")
self._stop_loss_distance = self.Param("StopLossDistance", 5.0) \
.SetDisplay("Stop Loss Distance", "Stop loss distance in price units", "Risk")
self._take_profit_distance = self.Param("TakeProfitDistance", 5.0) \
.SetDisplay("Take Profit Distance", "Take profit distance in price units", "Risk")
self._initial_volume = self.Param("InitialVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Initial Volume", "Starting order volume", "Risk")
self._current_volume = 0.0
self._entry_price = 0.0
self._trade_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def StopLossDistance(self):
return self._stop_loss_distance.Value
@property
def TakeProfitDistance(self):
return self._take_profit_distance.Value
@property
def InitialVolume(self):
return self._initial_volume.Value
def OnStarted2(self, time):
super(stop_loss_take_profit_strategy, self).OnStarted2(time)
self._current_volume = float(self.InitialVolume)
self._entry_price = 0.0
self._trade_count = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
sl = float(self.StopLossDistance)
tp = float(self.TakeProfitDistance)
# Check SL/TP for open position
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
hit_stop = sl > 0 and float(candle.LowPrice) <= self._entry_price - sl
hit_take = tp > 0 and float(candle.HighPrice) >= self._entry_price + tp
if hit_stop:
self.SellMarket()
self._handle_stop_loss()
return
if hit_take:
self.SellMarket()
self._handle_take_profit()
return
elif self.Position < 0:
hit_stop = sl > 0 and float(candle.HighPrice) >= self._entry_price + sl
hit_take = tp > 0 and float(candle.LowPrice) <= self._entry_price - tp
if hit_stop:
self.BuyMarket()
self._handle_stop_loss()
return
if hit_take:
self.BuyMarket()
self._handle_take_profit()
return
# Enter new position when flat
if self.Position == 0:
self._trade_count += 1
close = float(candle.ClosePrice)
o = float(candle.OpenPrice)
if close < o:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = close
def _handle_stop_loss(self):
self._current_volume *= 2.0
self._entry_price = 0.0
def _handle_take_profit(self):
self._current_volume = float(self.InitialVolume)
self._entry_price = 0.0
def OnReseted(self):
super(stop_loss_take_profit_strategy, self).OnReseted()
self._current_volume = 0.0
self._entry_price = 0.0
self._trade_count = 0
def CreateClone(self):
return stop_loss_take_profit_strategy()