GreenTrade Strategie
Überblick
Die GreenTrade-Strategie ist eine Konvertierung des ursprünglichen MQL5-Experten. Sie folgt mittelfristigen Trends durch Kombination eines geglätteten gleitenden Durchschnitt (SMMA)-Steigungsfilters mit Momentum-Bestätigung vom Relative Strength Index (RSI). Signale werden auf abgeschlossenen Kerzen des konfigurierten Zeitrahmens berechnet, und die Strategie kann bis zu einer konfigurierbaren Anzahl von Positionseinheiten pyramidisieren, während sie feste Risikokontrollen und einen schrittweisen Trailing-Stop anwendet.
Handelslogik
- Indikatorvorbereitung
- Die SMMA wird auf dem Median-Preis
((High + Low) / 2)mit demMaPeriod-Parameter berechnet. - Der RSI wird auf dem Schlusskurs mit dem
RsiPeriod-Rückblick berechnet.
- Die SMMA wird auf dem Median-Preis
- Trendformfilter
- Vier historische SMMA-Proben werden gemäß den Bar-Shift-Parametern (
ShiftBar,ShiftBar1,ShiftBar2,ShiftBar3) untersucht. - Ein bullischer Trend erfordert
SMMA(shift0) > SMMA(shift1) > SMMA(shift2) > SMMA(shift3). - Ein bärischer Trend erfordert
SMMA(shift0) < SMMA(shift1) < SMMA(shift2) < SMMA(shift3).
- Vier historische SMMA-Proben werden gemäß den Bar-Shift-Parametern (
- Momentum-Bestätigung
- Der RSI muss über
RsiBuyLevelfür Long-Einstiege und unterRsiSellLevelfür Short-Einstiege liegen. Der RSI-Wert wirdShiftBarBars zurück genommen, um die MQL5-Logik zu spiegeln, die die sich bildende Kerze ignoriert.
- Der RSI muss über
- Orderausführung
- Wenn ein Signal bestätigt ist und das Positionslimit nicht überschritten wird, sendet die Strategie eine Marktorder für
TradeVolume. - Wenn eine Position in der entgegengesetzten Richtung existiert, neutralisiert die Strategie sie zuerst und eröffnet dann eine neue Position mit dem konfigurierten Volumen.
- Wenn eine Position in dieselbe Richtung existiert, wird das Handelsvolumen bis zu
MaxPositions * TradeVolumezur Nettoexposition hinzugefügt.
- Wenn ein Signal bestätigt ist und das Positionslimit nicht überschritten wird, sendet die Strategie eine Marktorder für
Risikomanagement
- Initialer Stop-Loss / Take-Profit: Jeder neue Einstieg setzt Preisziele basierend auf
StopLossPipsundTakeProfitPips. Pip-Abstände werden in Preiseinheiten über denPriceStepdes Instruments umgerechnet. Instrumente mit fraktionalen Schritten (z. B. fünfstellige Forex-Symbole) erhalten einen zusätzlichen Faktor von 10, genau wie der ursprüngliche Experte. - Trailing-Stop: Wenn der Gewinn
TrailingStopPips + TrailingStepPipsüberschreitet, wird der Stop bewegt, um einen Abstand vonTrailingStopPipszu halten. Weitere Bewegungen erfordern eine weitereTrailingStepPips-Preisverbesserung, was das schrittweise Trailing-Verhalten aus dem MQL-Code reproduziert. - Positionslimit: Der
MaxPositions-Parameter begrenzt die maximale Anzahl von Volumeneinheiten. Signale, die dieses Limit überschreiten würden, werden ignoriert.
Parameter
| Parameter | Beschreibung | Standard |
|---|---|---|
MaPeriod |
Länge des geglätteten gleitenden Durchschnitts angewendet auf den Median-Preis. | 67 |
ShiftBar, ShiftBar1, ShiftBar2, ShiftBar3 |
Offsets (in Bars) zum Zugriff auf historische SMMA-Proben für den Trendformfilter. | 1, 1, 2, 3 |
RsiPeriod |
Rückblickperiode für den RSI-Indikator. | 57 |
RsiBuyLevel |
RSI-Schwellenwert, der bullische Setups bestätigt. | 60 |
RsiSellLevel |
RSI-Schwellenwert, der bärische Setups bestätigt. | 36 |
TradeVolume |
Volumen angewendet auf jeden Einstieg oder jede Ergänzung. | 0.1 |
StopLossPips |
Abstand für den initialen Stop-Loss in Pips (0 deaktiviert ihn). | 300 |
TakeProfitPips |
Abstand für den initialen Take-Profit in Pips (0 deaktiviert ihn). | 300 |
TrailingStopPips |
Abstand zwischen Preis und Trailing-Stop nach Aktivierung (0 deaktiviert Trailing). | 12 |
TrailingStepPips |
Zusätzlicher Fortschritt erforderlich, bevor der Trailing-Stop erneut bewegt wird. | 5 |
MaxPositions |
Maximale Anzahl von Volumeneinheiten (TradeVolume-Vielfachen), die aktiv sein können. |
7 |
CandleType |
Kerzendatenserie für Indikatoraktualisierungen. | 1-Stunden-Zeitrahmen |
Hinweise
- Alle Berechnungen werden nur auf abgeschlossenen Kerzen durchgeführt; unfertige Kerzen werden ignoriert, um rauschige Signale zu vermeiden.
- Der Positionsstatus wird intern verfolgt, damit Stop-Loss-, Take-Profit- und Trailing-Ausstiege gehandhabt werden, auch wenn keine Schutzorders an der Börse platziert sind.
- Die Konvertierung behält das ursprüngliche Verhalten für Pip-Konvertierung und Trailing-Schritt-Logik bei, während die StockSharp-High-Level-API für Abonnements und Orderausführung genutzt wird.
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()