Estratégia GreenTrade
Visão geral
A estratégia GreenTrade é uma conversão do expert advisor MQL5 original. Ela segue tendências de médio prazo combinando um filtro de inclinação de média móvel suavizada (SMMA) com confirmação de momentum do Índice de Força Relativa (RSI). Os sinais são calculados em velas concluídas do período configurado, e a estratégia pode fazer pirâmide até um número configurável de unidades de posição enquanto aplica controles de risco fixos e um stop trailing baseado em etapas.
Lógica de negociação
- Preparação de indicadores
- A SMMA é calculada no preço mediano
((High + Low) / 2)usando o parâmetroMaPeriod. - O RSI é calculado no preço de fechamento com o retrocesso
RsiPeriod.
- A SMMA é calculada no preço mediano
- Filtro de forma de tendência
- Quatro amostras históricas de SMMA são inspecionadas de acordo com os parâmetros de deslocamento de barra (
ShiftBar,ShiftBar1,ShiftBar2,ShiftBar3). - Uma tendência de alta requer
SMMA(shift0) > SMMA(shift1) > SMMA(shift2) > SMMA(shift3). - Uma tendência de baixa requer
SMMA(shift0) < SMMA(shift1) < SMMA(shift2) < SMMA(shift3).
- Quatro amostras históricas de SMMA são inspecionadas de acordo com os parâmetros de deslocamento de barra (
- Confirmação de momentum
- O RSI deve estar acima de
RsiBuyLevelpara entradas compradas e abaixo deRsiSellLevelpara entradas vendidas. O valor do RSI é retiradoShiftBarbarras atrás para espelhar a lógica MQL5 que ignora a vela em formação.
- O RSI deve estar acima de
- Execução de ordens
- Quando um sinal é confirmado e o limite de posição não é excedido, a estratégia envia uma ordem a mercado por
TradeVolume. - Se existir uma posição na direção oposta, a estratégia primeiro a neutraliza e depois abre uma nova posição com o volume configurado.
- Se existir uma posição na mesma direção, o volume de negociação é adicionado à exposição líquida até
MaxPositions * TradeVolume.
- Quando um sinal é confirmado e o limite de posição não é excedido, a estratégia envia uma ordem a mercado por
Gestão de riscos
- Stop Loss / Take Profit inicial: Cada nova entrada define alvos de preço baseados em
StopLossPipseTakeProfitPips. As distâncias de pip são convertidas em unidades de preço usando oPriceStepdo instrumento. Instrumentos com passos fracionários (p. ex., símbolos Forex de cinco dígitos) recebem um fator adicional de 10, assim como o expert original. - Stop Trailing: Quando o lucro excede
TrailingStopPips + TrailingStepPips, o stop é movido para manter uma distância deTrailingStopPips. Movimentos adicionais requerem maisTrailingStepPipsde melhoria de preço, reproduzindo o comportamento de trailing escalonado do código MQL. - Limite de posição: O parâmetro
MaxPositionslimita o número máximo de unidades de volume. Sinais que excederiam esse limite são ignorados.
Parâmetros
| Parâmetro | Descrição | Padrão |
|---|---|---|
MaPeriod |
Comprimento da média móvel suavizada aplicada ao preço mediano. | 67 |
ShiftBar, ShiftBar1, ShiftBar2, ShiftBar3 |
Deslocamentos (em barras) usados para acessar amostras históricas de SMMA para o filtro de forma de tendência. | 1, 1, 2, 3 |
RsiPeriod |
Período de retrocesso para o indicador RSI. | 57 |
RsiBuyLevel |
Limiar do RSI que confirma configurações de alta. | 60 |
RsiSellLevel |
Limiar do RSI que confirma configurações de baixa. | 36 |
TradeVolume |
Volume aplicado a cada entrada ou adição. | 0.1 |
StopLossPips |
Distância para o stop loss inicial em pips (0 desabilita). | 300 |
TakeProfitPips |
Distância para o take profit inicial em pips (0 desabilita). | 300 |
TrailingStopPips |
Distância entre o preço e o stop trailing uma vez ativado (0 desabilita o trailing). | 12 |
TrailingStepPips |
Progresso adicional necessário antes que o stop trailing se mova novamente. | 5 |
MaxPositions |
Número máximo de unidades de volume (múltiplos de TradeVolume) que podem estar ativas. |
7 |
CandleType |
Série de dados de velas usada para atualizações de indicadores. | Período de 1 hora |
Notas
- Todos os cálculos são realizados apenas em velas concluídas; velas inacabadas são ignoradas para evitar sinais ruidosos.
- O estado da posição é rastreado internamente para que saídas por stop-loss, take-profit e trailing sejam tratadas mesmo quando ordens protetoras não são colocadas na corretora.
- A conversão mantém o comportamento original para conversão de pip e lógica de passo de trailing, enquanto aproveita a API de alto nível do StockSharp para subscrições e execução de ordens.
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()