Estrategia GreenTrade
Descripción general
La estrategia GreenTrade es una conversión del asesor experto MQL5 original. Sigue tendencias de medio plazo combinando un filtro de pendiente de media móvil suavizada (SMMA) con confirmación de impulso del Índice de Fuerza Relativa (RSI). Las señales se calculan en velas completadas del marco temporal configurado, y la estrategia puede realizar pirámide hasta un número configurable de unidades de posición mientras aplica controles de riesgo fijos y un stop trailing basado en pasos.
Lógica de trading
- Preparación de indicadores
- La SMMA se calcula en el precio mediano
((High + Low) / 2)usando el parámetroMaPeriod. - El RSI se calcula en el precio de cierre con el retroceso
RsiPeriod.
- La SMMA se calcula en el precio mediano
- Filtro de forma de tendencia
- Se inspeccionan cuatro muestras históricas de SMMA según los parámetros de desplazamiento de barra (
ShiftBar,ShiftBar1,ShiftBar2,ShiftBar3). - Una tendencia alcista requiere
SMMA(shift0) > SMMA(shift1) > SMMA(shift2) > SMMA(shift3). - Una tendencia bajista requiere
SMMA(shift0) < SMMA(shift1) < SMMA(shift2) < SMMA(shift3).
- Se inspeccionan cuatro muestras históricas de SMMA según los parámetros de desplazamiento de barra (
- Confirmación de impulso
- El RSI debe estar por encima de
RsiBuyLevelpara entradas largas y por debajo deRsiSellLevelpara entradas cortas. El valor de RSI se toma enShiftBarbarras hacia atrás para reflejar la lógica MQL5 que ignora la vela en formación.
- El RSI debe estar por encima de
- Ejecución de órdenes
- Cuando se confirma una señal y no se supera el límite de posición, la estrategia envía una orden de mercado por
TradeVolume. - Si existe una posición en la dirección opuesta, la estrategia primero la neutraliza y luego abre una nueva posición con el volumen configurado.
- Si existe una posición en la misma dirección, el volumen de la operación se agrega a la exposición neta hasta
MaxPositions * TradeVolume.
- Cuando se confirma una señal y no se supera el límite de posición, la estrategia envía una orden de mercado por
Gestión de riesgos
- Stop Loss / Take Profit inicial: Cada nueva entrada establece objetivos de precio basados en
StopLossPipsyTakeProfitPips. Las distancias de pip se convierten en unidades de precio usando elPriceStepdel instrumento. Los instrumentos con pasos fraccionarios (p. ej., símbolos Forex de cinco dígitos) reciben un factor adicional de 10 igual que el asesor original. - Stop Trailing: Cuando la ganancia supera
TrailingStopPips + TrailingStepPips, el stop se mueve para mantener una distancia deTrailingStopPips. Los movimientos adicionales requieren otroTrailingStepPipsde mejora de precio, reproduciendo el comportamiento de trailing escalonado del código MQL. - Límite de posición: El parámetro
MaxPositionslimita el número máximo de unidades de volumen. Las señales que excedan este límite se ignoran.
Parámetros
| Parámetro | Descripción | Predeterminado |
|---|---|---|
MaPeriod |
Longitud de la media móvil suavizada aplicada al precio mediano. | 67 |
ShiftBar, ShiftBar1, ShiftBar2, ShiftBar3 |
Desplazamientos (en barras) usados para acceder a muestras históricas de SMMA para el filtro de forma de tendencia. | 1, 1, 2, 3 |
RsiPeriod |
Período de retroceso para el indicador RSI. | 57 |
RsiBuyLevel |
Umbral de RSI que confirma configuraciones alcistas. | 60 |
RsiSellLevel |
Umbral de RSI que confirma configuraciones bajistas. | 36 |
TradeVolume |
Volumen aplicado a cada entrada o adición. | 0.1 |
StopLossPips |
Distancia para el stop loss inicial en pips (0 lo deshabilita). | 300 |
TakeProfitPips |
Distancia para el take profit inicial en pips (0 lo deshabilita). | 300 |
TrailingStopPips |
Distancia entre el precio y el stop trailing una vez activado (0 deshabilita el trailing). | 12 |
TrailingStepPips |
Progreso adicional requerido antes de que el stop trailing se mueva nuevamente. | 5 |
MaxPositions |
Número máximo de unidades de volumen (TradeVolume múltiplos) que pueden estar activas. |
7 |
CandleType |
Serie de datos de velas usada para actualizaciones de indicadores. | Marco temporal de 1 hora |
Notas
- Todos los cálculos se realizan solo en velas completadas; las velas sin terminar se ignoran para evitar señales ruidosas.
- El estado de posición se rastrea internamente para que las salidas por stop-loss, take-profit y trailing se manejen incluso cuando las órdenes protectoras no se colocan en el exchange.
- La conversión conserva el comportamiento original para la conversión de pip y la lógica de paso de trailing, mientras aprovecha la API de alto nivel de StockSharp para suscripciones y ejecución de órdenes.
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>
/// GreenTrade strategy converted from the MQL implementation.
/// Combines a smoothed moving average slope filter with RSI momentum confirmation.
/// </summary>
public class GreenTradeStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _shiftBar;
private readonly StrategyParam<int> _shiftBar1;
private readonly StrategyParam<int> _shiftBar2;
private readonly StrategyParam<int> _shiftBar3;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiBuyLevel;
private readonly StrategyParam<decimal> _rsiSellLevel;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _smma;
private RelativeStrengthIndex _rsi;
private readonly List<decimal> _maHistory = new();
private readonly List<decimal> _rsiHistory = new();
private decimal _pipSize = 1m;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Period for the smoothed moving average.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Shift (in bars) used for the most recent MA/RSI sample.
/// </summary>
public int ShiftBar
{
get => _shiftBar.Value;
set => _shiftBar.Value = value;
}
/// <summary>
/// Additional shift between the first and second MA comparison.
/// </summary>
public int ShiftBar1
{
get => _shiftBar1.Value;
set => _shiftBar1.Value = value;
}
/// <summary>
/// Additional shift between the second and third MA comparison.
/// </summary>
public int ShiftBar2
{
get => _shiftBar2.Value;
set => _shiftBar2.Value = value;
}
/// <summary>
/// Additional shift between the third and fourth MA comparison.
/// </summary>
public int ShiftBar3
{
get => _shiftBar3.Value;
set => _shiftBar3.Value = value;
}
/// <summary>
/// RSI lookback period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI threshold to confirm bullish entries.
/// </summary>
public decimal RsiBuyLevel
{
get => _rsiBuyLevel.Value;
set => _rsiBuyLevel.Value = value;
}
/// <summary>
/// RSI threshold to confirm bearish entries.
/// </summary>
public decimal RsiSellLevel
{
get => _rsiSellLevel.Value;
set => _rsiSellLevel.Value = value;
}
/// <summary>
/// Trade volume for each new position add-on.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimum price improvement (in pips) before trailing stop is moved again.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Maximum number of position units (in TradeVolume steps) allowed.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Candle type used for backtesting/live trading.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public GreenTradeStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 67)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Length of the smoothed moving average", "Indicators");
_shiftBar = Param(nameof(ShiftBar), 1)
.SetGreaterThanZero()
.SetDisplay("Shift #0", "Index of the most recent evaluated bar", "Signals");
_shiftBar1 = Param(nameof(ShiftBar1), 1)
.SetGreaterThanZero()
.SetDisplay("Shift #1", "Offset from bar #0 to bar #1", "Signals");
_shiftBar2 = Param(nameof(ShiftBar2), 2)
.SetGreaterThanZero()
.SetDisplay("Shift #2", "Offset from bar #1 to bar #2", "Signals");
_shiftBar3 = Param(nameof(ShiftBar3), 3)
.SetGreaterThanZero()
.SetDisplay("Shift #3", "Offset from bar #2 to bar #3", "Signals");
_rsiPeriod = Param(nameof(RsiPeriod), 57)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of the RSI indicator", "Indicators");
_rsiBuyLevel = Param(nameof(RsiBuyLevel), 60m)
.SetDisplay("RSI Buy Level", "RSI threshold for bullish entries", "Signals");
_rsiSellLevel = Param(nameof(RsiSellLevel), 36m)
.SetDisplay("RSI Sell Level", "RSI threshold for bearish entries", "Signals");
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Volume used for each new order", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 300m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Initial stop-loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 300m)
.SetNotNegative()
.SetDisplay("Take Profit", "Initial take-profit distance in pips", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 12m)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step", "Required progress before trailing adjusts", "Risk");
_maxPositions = Param(nameof(MaxPositions), 7)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum number of volume units allowed", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle subscription", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_smma = null;
_rsi = null;
_maHistory.Clear();
_rsiHistory.Clear();
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_smma = new SmoothedMovingAverage { Length = MaPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_pipSize = CalculatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _smma);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_smma == null || _rsi == null)
return;
var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
var maResult = _smma.Process(new DecimalIndicatorValue(_smma, medianPrice, candle.OpenTime) { IsFinal = true });
var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
if (!_smma.IsFormed || !_rsi.IsFormed)
{
_maHistory.Add(0m);
_rsiHistory.Add(0m);
TrimHistory();
return;
}
var maValue = maResult.ToDecimal();
var rsiValue = rsiResult.ToDecimal();
_maHistory.Add(maValue);
_rsiHistory.Add(rsiValue);
TrimHistory();
// Indicators checked above.
var shift0 = ShiftBar;
var shift1 = shift0 + ShiftBar1;
var shift2 = shift1 + ShiftBar2;
var shift3 = shift2 + ShiftBar3;
var ma0 = GetHistoryValue(_maHistory, shift0);
var ma1 = GetHistoryValue(_maHistory, shift1);
var ma2 = GetHistoryValue(_maHistory, shift2);
var ma3 = GetHistoryValue(_maHistory, shift3);
var rsiSample = GetHistoryValue(_rsiHistory, ShiftBar);
if (ma0 is null || ma1 is null || ma2 is null || ma3 is null || rsiSample is null)
return;
var buySignal = ma0 > ma1 && ma1 > ma2 && ma2 > ma3 && rsiSample > RsiBuyLevel;
var sellSignal = ma0 < ma1 && ma1 < ma2 && ma2 < ma3 && rsiSample < RsiSellLevel;
if (buySignal && CanIncreasePosition(true))
OpenPosition(true, candle);
else if (sellSignal && CanIncreasePosition(false))
OpenPosition(false, candle);
UpdateTrailing(candle);
ManageExits(candle);
}
private void OpenPosition(bool isLong, ICandleMessage candle)
{
var additionalVolume = TradeVolume;
var currentPosition = Position;
if (isLong && currentPosition < 0)
additionalVolume += Math.Abs(currentPosition);
else if (!isLong && currentPosition > 0)
additionalVolume += currentPosition;
if (additionalVolume <= 0)
return;
if (isLong)
BuyMarket();
else
SellMarket();
if (isLong)
{
if (currentPosition > 0)
{
var total = currentPosition + TradeVolume;
_entryPrice = total > 0 ? ((currentPosition * _entryPrice) + (TradeVolume * candle.ClosePrice)) / total : candle.ClosePrice;
}
else
{
_entryPrice = candle.ClosePrice;
}
}
else
{
if (currentPosition < 0)
{
var total = Math.Abs(currentPosition) + TradeVolume;
_entryPrice = total > 0 ? ((Math.Abs(currentPosition) * _entryPrice) + (TradeVolume * candle.ClosePrice)) / total : candle.ClosePrice;
}
else
{
_entryPrice = candle.ClosePrice;
}
}
var stopDistance = StopLossPips * _pipSize;
var takeDistance = TakeProfitPips * _pipSize;
_stopPrice = stopDistance > 0 ? (isLong ? _entryPrice - stopDistance : _entryPrice + stopDistance) : null;
_takePrice = takeDistance > 0 ? (isLong ? _entryPrice + takeDistance : _entryPrice - takeDistance) : null;
}
private void UpdateTrailing(ICandleMessage candle)
{
if (TrailingStopPips <= 0)
return;
var trailingDistance = TrailingStopPips * _pipSize;
var stepDistance = TrailingStepPips * _pipSize;
if (Position > 0 && _entryPrice > 0)
{
var profit = candle.ClosePrice - _entryPrice;
if (profit > trailingDistance + stepDistance)
{
var threshold = candle.ClosePrice - (trailingDistance + stepDistance);
if (!_stopPrice.HasValue || _stopPrice.Value < threshold)
_stopPrice = candle.ClosePrice - trailingDistance;
}
}
else if (Position < 0 && _entryPrice > 0)
{
var profit = _entryPrice - candle.ClosePrice;
if (profit > trailingDistance + stepDistance)
{
var threshold = candle.ClosePrice + trailingDistance + stepDistance;
if (!_stopPrice.HasValue || _stopPrice.Value > threshold)
_stopPrice = candle.ClosePrice + trailingDistance;
}
}
}
private void ManageExits(ICandleMessage candle)
{
if (Position > 0)
{
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket();
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket();
ResetPositionState();
return;
}
}
else if (Position < 0)
{
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket();
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket();
ResetPositionState();
return;
}
}
else
{
ResetPositionState();
}
}
private bool CanIncreasePosition(bool isLong)
{
if (TradeVolume <= 0)
return false;
if (MaxPositions <= 0)
return true;
var maxVolume = MaxPositions * TradeVolume;
var absolutePosition = Math.Abs(Position);
if (isLong && Position < 0)
return true;
if (!isLong && Position > 0)
return true;
var tolerance = Security?.VolumeStep ?? 0.0000001m;
return absolutePosition + TradeVolume <= maxVolume + tolerance;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 1m;
if (step < 0.01m)
step *= 10m;
return step;
}
private static decimal? GetHistoryValue(List<decimal> values, int shift)
{
if (shift <= 0)
return null;
var index = values.Count - shift;
if (index < 0)
return null;
return values[index];
}
private void TrimHistory()
{
var maxShift = ShiftBar + ShiftBar1 + ShiftBar2 + ShiftBar3;
var maxCount = Math.Max(maxShift + 5, 10);
if (_maHistory.Count > maxCount)
_maHistory.RemoveRange(0, _maHistory.Count - maxCount);
if (_rsiHistory.Count > maxCount)
_rsiHistory.RemoveRange(0, _rsiHistory.Count - maxCount);
}
private void ResetPositionState()
{
if (Math.Abs(Position) < (Security?.VolumeStep ?? 0.0000001m))
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
SmoothedMovingAverage,
RelativeStrengthIndex,
)
from indicator_extensions import *
class green_trade_strategy(Strategy):
"""GreenTrade: smoothed MA slope filter with RSI momentum confirmation."""
def __init__(self):
super(green_trade_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 67) \
.SetGreaterThanZero() \
.SetDisplay("MA Period", "Length of the smoothed moving average", "Indicators")
self._shift_bar = self.Param("ShiftBar", 1) \
.SetGreaterThanZero() \
.SetDisplay("Shift #0", "Index of the most recent evaluated bar", "Signals")
self._shift_bar1 = self.Param("ShiftBar1", 1) \
.SetGreaterThanZero() \
.SetDisplay("Shift #1", "Offset from bar #0 to bar #1", "Signals")
self._shift_bar2 = self.Param("ShiftBar2", 2) \
.SetGreaterThanZero() \
.SetDisplay("Shift #2", "Offset from bar #1 to bar #2", "Signals")
self._shift_bar3 = self.Param("ShiftBar3", 3) \
.SetGreaterThanZero() \
.SetDisplay("Shift #3", "Offset from bar #2 to bar #3", "Signals")
self._rsi_period = self.Param("RsiPeriod", 57) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "Length of the RSI indicator", "Indicators")
self._rsi_buy_level = self.Param("RsiBuyLevel", 60.0) \
.SetDisplay("RSI Buy Level", "RSI threshold for bullish entries", "Signals")
self._rsi_sell_level = self.Param("RsiSellLevel", 36.0) \
.SetDisplay("RSI Sell Level", "RSI threshold for bearish entries", "Signals")
self._trade_volume = self.Param("TradeVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Trade Volume", "Volume used for each new order", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", 300.0) \
.SetDisplay("Stop Loss", "Initial stop-loss distance in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 300.0) \
.SetDisplay("Take Profit", "Initial take-profit distance in pips", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 12.0) \
.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", 5.0) \
.SetDisplay("Trailing Step", "Required progress before trailing adjusts", "Risk")
self._max_positions = self.Param("MaxPositions", 7) \
.SetGreaterThanZero() \
.SetDisplay("Max Positions", "Maximum number of volume units allowed", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary candle subscription", "Data")
self._ma_history = []
self._rsi_history = []
self._pip_size = 1.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
@property
def MaPeriod(self):
return int(self._ma_period.Value)
@property
def ShiftBar(self):
return int(self._shift_bar.Value)
@property
def ShiftBar1(self):
return int(self._shift_bar1.Value)
@property
def ShiftBar2(self):
return int(self._shift_bar2.Value)
@property
def ShiftBar3(self):
return int(self._shift_bar3.Value)
@property
def RsiPeriod(self):
return int(self._rsi_period.Value)
@property
def RsiBuyLevel(self):
return float(self._rsi_buy_level.Value)
@property
def RsiSellLevel(self):
return float(self._rsi_sell_level.Value)
@property
def TradeVolume(self):
return float(self._trade_volume.Value)
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def TrailingStopPips(self):
return float(self._trailing_stop_pips.Value)
@property
def TrailingStepPips(self):
return float(self._trailing_step_pips.Value)
@property
def MaxPositions(self):
return int(self._max_positions.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _calc_pip_size(self):
sec = self.Security
if sec is None or sec.PriceStep is None:
return 1.0
step = float(sec.PriceStep)
if step <= 0:
return 1.0
if step < 0.01:
step *= 10.0
return step
def OnStarted2(self, time):
super(green_trade_strategy, self).OnStarted2(time)
self._smma = SmoothedMovingAverage()
self._smma.Length = self.MaPeriod
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
self._pip_size = self._calc_pip_size()
self._ma_history = []
self._rsi_history = []
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._smma)
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
median = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
close = float(candle.ClosePrice)
t = candle.OpenTime
ma_result = process_float(self._smma, Decimal(median), candle.ServerTime, True)
rsi_result = process_float(self._rsi, Decimal(close), candle.ServerTime, True)
if not self._smma.IsFormed or not self._rsi.IsFormed:
self._ma_history.append(0.0)
self._rsi_history.append(0.0)
self._trim_history()
return
ma_val = float(ma_result.Value)
rsi_val = float(rsi_result.Value)
self._ma_history.append(ma_val)
self._rsi_history.append(rsi_val)
self._trim_history()
shift0 = self.ShiftBar
shift1 = shift0 + self.ShiftBar1
shift2 = shift1 + self.ShiftBar2
shift3 = shift2 + self.ShiftBar3
ma0 = self._get_hist(self._ma_history, shift0)
ma1 = self._get_hist(self._ma_history, shift1)
ma2 = self._get_hist(self._ma_history, shift2)
ma3 = self._get_hist(self._ma_history, shift3)
rsi_sample = self._get_hist(self._rsi_history, self.ShiftBar)
if ma0 is None or ma1 is None or ma2 is None or ma3 is None or rsi_sample is None:
return
buy_signal = ma0 > ma1 and ma1 > ma2 and ma2 > ma3 and rsi_sample > self.RsiBuyLevel
sell_signal = ma0 < ma1 and ma1 < ma2 and ma2 < ma3 and rsi_sample < self.RsiSellLevel
if buy_signal and self._can_increase(True):
self._open_position(True, candle)
elif sell_signal and self._can_increase(False):
self._open_position(False, candle)
self._update_trailing(candle)
self._manage_exits(candle)
def _open_position(self, is_long, candle):
close = float(candle.ClosePrice)
cur_pos = self.Position
if is_long:
self.BuyMarket()
if cur_pos > 0:
total = cur_pos + self.TradeVolume
self._entry_price = ((cur_pos * self._entry_price) + (self.TradeVolume * close)) / total if total > 0 else close
else:
self._entry_price = close
else:
self.SellMarket()
if cur_pos < 0:
total = abs(cur_pos) + self.TradeVolume
self._entry_price = ((abs(cur_pos) * self._entry_price) + (self.TradeVolume * close)) / total if total > 0 else close
else:
self._entry_price = close
stop_dist = self.StopLossPips * self._pip_size
take_dist = self.TakeProfitPips * self._pip_size
if is_long:
self._stop_price = self._entry_price - stop_dist if stop_dist > 0 else None
self._take_price = self._entry_price + take_dist if take_dist > 0 else None
else:
self._stop_price = self._entry_price + stop_dist if stop_dist > 0 else None
self._take_price = self._entry_price - take_dist if take_dist > 0 else None
def _update_trailing(self, candle):
if self.TrailingStopPips <= 0:
return
trail_dist = self.TrailingStopPips * self._pip_size
step_dist = self.TrailingStepPips * self._pip_size
close = float(candle.ClosePrice)
if self.Position > 0 and self._entry_price > 0:
profit = close - self._entry_price
if profit > trail_dist + step_dist:
threshold = close - (trail_dist + step_dist)
if self._stop_price is None or self._stop_price < threshold:
self._stop_price = close - trail_dist
elif self.Position < 0 and self._entry_price > 0:
profit = self._entry_price - close
if profit > trail_dist + step_dist:
threshold = close + trail_dist + step_dist
if self._stop_price is None or self._stop_price > threshold:
self._stop_price = close + trail_dist
def _manage_exits(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self.Position > 0:
if self._take_price is not None and h >= self._take_price:
self.SellMarket()
self._reset_state()
return
if self._stop_price is not None and lo <= self._stop_price:
self.SellMarket()
self._reset_state()
return
elif self.Position < 0:
if self._take_price is not None and lo <= self._take_price:
self.BuyMarket()
self._reset_state()
return
if self._stop_price is not None and h >= self._stop_price:
self.BuyMarket()
self._reset_state()
return
else:
self._reset_state()
def _can_increase(self, is_long):
if self.TradeVolume <= 0:
return False
if self.MaxPositions <= 0:
return True
max_vol = self.MaxPositions * self.TradeVolume
abs_pos = abs(self.Position)
if is_long and self.Position < 0:
return True
if not is_long and self.Position > 0:
return True
return abs_pos + self.TradeVolume <= max_vol + 0.0000001
def _get_hist(self, values, shift):
if shift <= 0:
return None
index = len(values) - shift
if index < 0:
return None
return values[index]
def _trim_history(self):
max_shift = self.ShiftBar + self.ShiftBar1 + self.ShiftBar2 + self.ShiftBar3
max_count = max(max_shift + 5, 10)
while len(self._ma_history) > max_count:
self._ma_history.pop(0)
while len(self._rsi_history) > max_count:
self._rsi_history.pop(0)
def _reset_state(self):
if abs(self.Position) < 0.0000001:
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(green_trade_strategy, self).OnReseted()
self._ma_history = []
self._rsi_history = []
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def CreateClone(self):
return green_trade_strategy()