Omni Trend Strategie
Überblick
Die Omni Trend Strategie ist ein direkter Port des MetaTrader-Experten "Exp_Omni_Trend". Sie kombiniert einen gleitenden Durchschnitt mit einem ATR-basierten Kanal, um den dominanten Trend zu erkennen und zwischen Long- und Short-Exposition zu wechseln. Die StockSharp-Version behält das ursprüngliche Verhalten bei, einschließlich der Verzögerung zwischen Signalerkennung und Orderausführung sowie der Möglichkeit, individuelle Einstiegs- oder Ausstiegs-Beine zu deaktivieren.
Die Strategie abonniert die konfigurierte Kerzenserie und speist jeden abgeschlossenen Balken in die Omni-Trend-Logik ein. Der gleitende Durchschnitt dient als Schätzung der zentralen Tendenz, während ATR-Multiplikatoren Volatilitätshüllen aufbauen. Die Hüllen verhalten sich wie Trailing-Stops: Ein Preis, der über die vorherige Hüllengrenze hinaus schließt, dreht den Trend um, generiert ein neues Einstiegssignal in die neue Richtung und schließt sofort jede entgegengesetzte Exposition.
Wenn die optionalen Stop-Loss- und Take-Profit-Schwellenwerte aktiviert sind, wirken sie auf der Broker-Seite in Preisschritten und ergänzen die indikatorbasierten Ausstiege. Die Positionsgröße wird über die integrierte Volume-Eigenschaft der Strategie gesteuert (Standard 1).
Handelslogik
- Den gewählten gleitenden Durchschnitt (
MaType, MaLength, AppliedPrice) auf dem Kerzenstrom berechnen.
- ATR (
AtrLength) berechnen und zwei adaptive Bänder mit VolatilityFactor und MoneyRisk ableiten. Das obere Band schützt Short-Positionen, das untere Band schützt Long-Positionen.
- Wenn der Preis das Schutzband des vorherigen Balkens überschreitet, ändert sich der Trend:
- Ein bullischer Ausbruch (
HighPrice über dem vorherigen oberen Band) dreht den Trend auf "hoch", schließt jede Short-Position wenn erlaubt, und öffnet nach SignalBar abgeschlossenen Kerzen eine Long-Position.
- Ein bärischer Ausbruch (
LowPrice unter dem vorherigen unteren Band) dreht den Trend auf "runter", schließt jede Long-Position wenn erlaubt, und öffnet nach der konfigurierten Verzögerung eine Short-Position.
- Solange der Trend bullisch bleibt, fordert die Strategie weiterhin Short-Ausstiege; die symmetrische Regel gilt für einen bärischen Trend und Long-Ausstiege. Dies spiegelt das Verhalten des MetaTrader-Experten wider, wo das entgegengesetzte Band konstant flache Exposition gegen die vorherrschende Richtung erzwingt.
- Das optionale Risikomanagement überwacht jede abgeschlossene Kerze. Wenn der aktuelle Balken den Stop- oder Zielpreis (ausgedrückt in Preisschritten) erreicht, wird die Position sofort geschlossen und der gespeicherte Einstiegspreis wird zurückgesetzt.
Signale werden über eine FIFO-Warteschlange geplant. Wenn SignalBar null ist, werden sie beim Schluss derselben Kerze ausgeführt. Andernfalls werden sie auf der Eröffnung der Kerze ausgelöst, die die Verzögerung abschließt, was den "vorherigen Balken"-Ausführungsstil des Quellexperten repliziert.
Parameter
| Name |
Beschreibung |
Standard |
CandleType |
Kerzentyp (Zeitrahmen) für Berechnungen. |
4-Stunden-Zeitrahmen |
MaLength |
Periode des gleitenden Durchschnitts. |
13 |
MaType |
Methode des gleitenden Durchschnitts: einfach, exponentiell, geglättet oder linear gewichtet. |
Exponentiell |
AppliedPrice |
Preisfeld für den gleitenden Durchschnitt (Schluss, Eröffnung, Hoch, Tief, Median, Typisch, Gewichtet). |
Schluss |
AtrLength |
ATR-Periode für den Volatilitätskanal. |
11 |
VolatilityFactor |
Multiplikator für ATR beim Aufbau des Rohkanals. |
1.3 |
MoneyRisk |
Versatzfaktor, der den Kanal vom gleitenden Durchschnitt wegschiebt, identisch mit dem MQL-Input. |
0.15 |
SignalBar |
Anzahl abgeschlossener Kerzen vor der Signalausführung. |
1 |
EnableBuyOpen |
Long-Positionen öffnen erlauben. |
true |
EnableSellOpen |
Short-Positionen öffnen erlauben. |
true |
EnableBuyClose |
Long-Positionen bei bärischem Trend schließen erlauben. |
true |
EnableSellClose |
Short-Positionen bei bullischem Trend schließen erlauben. |
true |
StopLossPoints |
Optionaler Schutz-Stop-Abstand in Preisschritten. 0 deaktiviert. |
1000 |
TakeProfitPoints |
Optionaler Gewinnziel-Abstand in Preisschritten. 0 deaktiviert. |
2000 |
Volume |
Strategie-Eigenschaft zur Steuerung der Handelsgröße. |
1 |
Hinweise und Empfehlungen
- Die StockSharp-Implementierung speist dieselben Indikatorwerte wie das Original ein und reproduziert seine Trendwechsel. Präzise Ausführungen hängen jedoch von der Datenquelle und der Ausführungslatenz ab.
- Setzen Sie
SignalBar = 1, um den Standardwert des Expertenberaters zu imitieren, bei dem Orders auf der Eröffnung der nächsten Kerze nach Verfügbarkeit eines Signals ausgeführt werden. Größere Werte verzögern die Ausführung weiter; 0 führt beim aktuellen Schluss aus.
- Stop-Loss- und Take-Profit-Schwellenwerte werden in Punkten (Preisschritte) ausgedrückt. Stellen Sie sicher, dass das verbundene Wertpapier einen gültigen
PriceStep bereitstellt.
- Der integrierte Chart zeichnet die Kerzenserie, den gewählten gleitenden Durchschnitt und die eigenen Trades der Strategie für schnelle visuelle Validierung.
- Deaktivieren Sie spezifische Einstiegs- oder Ausstiegs-Schalter, um die Strategie auf einseitigen Betrieb zu beschränken oder Ausstiege manuell zu handhaben.
- Die Strategie erstellt keine Pending-Orders; sie gibt Market-Orders über
BuyMarket und SellMarket aus, genau wie die direkte Order-Platzierung des Quellexperten.
Dateien
CS/OmniTrendStrategy.cs — C#-Implementierung der Strategie.
README.md, README_ru.md, README_zh.md — Dokumentation in Englisch, Russisch und Chinesisch.
Python-Unterstützung wurde auf Anfrage bewusst weggelassen.
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>
/// Trend-following strategy that replicates the Omni Trend MetaTrader expert.
/// </summary>
public class OmniTrendStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<MovingAverageMethods> _maType;
private readonly StrategyParam<AppliedPriceTypes> _appliedPrice;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _volatilityFactor;
private readonly StrategyParam<decimal> _moneyRisk;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<bool> _enableBuyOpen;
private readonly StrategyParam<bool> _enableSellOpen;
private readonly StrategyParam<bool> _enableBuyClose;
private readonly StrategyParam<bool> _enableSellClose;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly List<SignalInfo> _pendingSignals = new();
private IIndicator _ma;
private AverageTrueRange _atr;
private decimal _previousSmin;
private decimal _previousSmax;
private decimal _previousTrendUp;
private decimal _previousTrendDown;
private int _previousTrend;
private bool _isInitialized;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
public OmniTrendStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to build Omni Trend signals", "General")
;
_maLength = Param(nameof(MaLength), 13)
.SetDisplay("MA Length", "Moving average period", "Indicators")
.SetGreaterThanZero()
;
_maType = Param(nameof(MaType), MovingAverageMethods.Exponential)
.SetDisplay("MA Type", "Moving average calculation method", "Indicators")
;
_appliedPrice = Param(nameof(AppliedPrice), AppliedPriceTypes.Close)
.SetDisplay("Applied Price", "Price field used by the moving average", "Indicators")
;
_atrLength = Param(nameof(AtrLength), 11)
.SetDisplay("ATR Length", "ATR period for volatility bands", "Indicators")
.SetGreaterThanZero()
;
_volatilityFactor = Param(nameof(VolatilityFactor), 1.3m)
.SetDisplay("Volatility Factor", "Multiplier applied to ATR", "Indicators")
.SetGreaterThanZero()
;
_moneyRisk = Param(nameof(MoneyRisk), 0.15m)
.SetDisplay("Money Risk", "Offset factor used to position trend bands", "Indicators")
.SetGreaterThanZero()
;
_signalBar = Param(nameof(SignalBar), 0)
.SetDisplay("Signal Bar", "Delay in bars before acting on a signal", "Trading")
;
_enableBuyOpen = Param(nameof(EnableBuyOpen), true)
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");
_enableSellOpen = Param(nameof(EnableSellOpen), true)
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");
_enableBuyClose = Param(nameof(EnableBuyClose), true)
.SetDisplay("Enable Long Exits", "Allow closing long positions", "Trading");
_enableSellClose = Param(nameof(EnableSellClose), true)
.SetDisplay("Enable Short Exits", "Allow closing short positions", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0)
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0)
.SetDisplay("Take Profit (points)", "Profit target distance expressed in price steps", "Risk");
Volume = 1m;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = Math.Max(1, value);
}
public MovingAverageMethods MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
public AppliedPriceTypes AppliedPrice
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = Math.Max(1, value);
}
public decimal VolatilityFactor
{
get => _volatilityFactor.Value;
set => _volatilityFactor.Value = value;
}
public decimal MoneyRisk
{
get => _moneyRisk.Value;
set => _moneyRisk.Value = value;
}
public int SignalBar
{
get => Math.Max(0, _signalBar.Value);
set => _signalBar.Value = Math.Max(0, value);
}
public bool EnableBuyOpen
{
get => _enableBuyOpen.Value;
set => _enableBuyOpen.Value = value;
}
public bool EnableSellOpen
{
get => _enableSellOpen.Value;
set => _enableSellOpen.Value = value;
}
public bool EnableBuyClose
{
get => _enableBuyClose.Value;
set => _enableBuyClose.Value = value;
}
public bool EnableSellClose
{
get => _enableSellClose.Value;
set => _enableSellClose.Value = value;
}
public int StopLossPoints
{
get => Math.Max(0, _stopLossPoints.Value);
set => _stopLossPoints.Value = Math.Max(0, value);
}
public int TakeProfitPoints
{
get => Math.Max(0, _takeProfitPoints.Value);
set => _takeProfitPoints.Value = Math.Max(0, value);
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pendingSignals.Clear();
_ma = null;
_atr = null;
_previousSmin = 0m;
_previousSmax = 0m;
_previousTrendUp = 0m;
_previousTrendDown = 0m;
_previousTrend = 0;
_isInitialized = false;
_longEntryPrice = null;
_shortEntryPrice = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = CreateMovingAverage(MaType, MaLength);
_atr = new AverageTrueRange
{
Length = AtrLength,
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
if (_ma is not null)
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_ma is null || _atr is null)
return;
var atrValue = _atr.Process(new CandleIndicatorValue(_atr, candle));
var appliedPrice = GetAppliedPrice(candle, AppliedPrice);
var maValue = _ma.Process(new DecimalIndicatorValue(_ma, appliedPrice, candle.OpenTime) { IsFinal = true });
if (!atrValue.IsFinal || !maValue.IsFinal)
return;
CheckRiskManagement(candle);
var atr = atrValue.GetValue<decimal>();
var ma = maValue.GetValue<decimal>();
var signal = CalculateSignal(candle, ma, atr);
_pendingSignals.Add(signal);
while (_pendingSignals.Count > SignalBar)
{
var pending = _pendingSignals[0];
try { _pendingSignals.RemoveAt(0); } catch { break; }
ExecuteSignal(candle, pending);
}
}
private SignalInfo CalculateSignal(ICandleMessage candle, decimal ma, decimal atr)
{
var smax = ma + VolatilityFactor * atr;
var smin = ma - VolatilityFactor * atr;
if (!_isInitialized)
{
_previousSmax = smax;
_previousSmin = smin;
_previousTrendUp = 0m;
_previousTrendDown = 0m;
_previousTrend = 0;
_isInitialized = true;
return SignalInfo.Empty;
}
var trend = _previousTrend;
if (candle.HighPrice > _previousSmax)
trend = 1;
else if (candle.LowPrice < _previousSmin)
trend = -1;
decimal? trendUp = null;
decimal? trendDown = null;
if (trend > 0)
{
if (smin < _previousSmin)
smin = _previousSmin;
var candidate = smin - (MoneyRisk - 1m) * atr;
if (_previousTrend > 0 && _previousTrendUp > 0m && candidate < _previousTrendUp)
candidate = _previousTrendUp;
trendUp = candidate;
}
else if (trend < 0)
{
if (smax > _previousSmax)
smax = _previousSmax;
var candidate = smax + (MoneyRisk - 1m) * atr;
if (_previousTrend < 0 && _previousTrendDown > 0m && candidate > _previousTrendDown)
candidate = _previousTrendDown;
trendDown = candidate;
}
var signal = SignalInfo.Empty;
if (trend > 0)
{
if (_previousTrend <= 0 && trendUp.HasValue && EnableBuyOpen)
signal.BuyOpen = true;
if (trendUp.HasValue && EnableSellClose)
signal.SellClose = true;
}
else if (trend < 0)
{
if (_previousTrend >= 0 && trendDown.HasValue && EnableSellOpen)
signal.SellOpen = true;
if (trendDown.HasValue && EnableBuyClose)
signal.BuyClose = true;
}
_previousTrend = trend;
_previousSmax = smax;
_previousSmin = smin;
_previousTrendUp = trendUp ?? 0m;
_previousTrendDown = trendDown ?? 0m;
return signal;
}
private void ExecuteSignal(ICandleMessage candle, SignalInfo signal)
{
if (signal.BuyClose && Position > 0)
{
var volume = Math.Abs(Position);
if (volume > 0)
SellMarket();
_longEntryPrice = null;
}
if (signal.SellClose && Position < 0)
{
var volume = Math.Abs(Position);
if (volume > 0)
BuyMarket();
_shortEntryPrice = null;
}
var executionPrice = SignalBar == 0 ? candle.ClosePrice : candle.OpenPrice;
if (signal.BuyOpen && Position <= 0)
{
if (Position < 0)
{
var volume = Math.Abs(Position);
BuyMarket();
_shortEntryPrice = null;
}
BuyMarket();
_longEntryPrice = executionPrice;
}
if (signal.SellOpen && Position >= 0)
{
if (Position > 0)
{
var volume = Math.Abs(Position);
SellMarket();
_longEntryPrice = null;
}
SellMarket();
_shortEntryPrice = executionPrice;
}
}
private void CheckRiskManagement(ICandleMessage candle)
{
if (Security is null)
return;
var step = Security?.PriceStep ?? 0.01m;
if (step <= 0m)
return;
if (Position > 0)
{
if (StopLossPoints > 0 && _longEntryPrice.HasValue)
{
var stopPrice = _longEntryPrice.Value - StopLossPoints * step;
if (candle.LowPrice <= stopPrice || candle.ClosePrice <= stopPrice)
{
SellMarket();
_longEntryPrice = null;
return;
}
}
if (TakeProfitPoints > 0 && _longEntryPrice.HasValue)
{
var targetPrice = _longEntryPrice.Value + TakeProfitPoints * step;
if (candle.HighPrice >= targetPrice || candle.ClosePrice >= targetPrice)
{
SellMarket();
_longEntryPrice = null;
return;
}
}
}
else if (Position < 0)
{
if (StopLossPoints > 0 && _shortEntryPrice.HasValue)
{
var stopPrice = _shortEntryPrice.Value + StopLossPoints * step;
if (candle.HighPrice >= stopPrice || candle.ClosePrice >= stopPrice)
{
BuyMarket();
_shortEntryPrice = null;
return;
}
}
if (TakeProfitPoints > 0 && _shortEntryPrice.HasValue)
{
var targetPrice = _shortEntryPrice.Value - TakeProfitPoints * step;
if (candle.LowPrice <= targetPrice || candle.ClosePrice <= targetPrice)
{
BuyMarket();
_shortEntryPrice = null;
return;
}
}
}
}
private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPriceTypes type)
{
return type switch
{
AppliedPriceTypes.Open => candle.OpenPrice,
AppliedPriceTypes.High => candle.HighPrice,
AppliedPriceTypes.Low => candle.LowPrice,
AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
_ => candle.ClosePrice
};
}
private static IIndicator CreateMovingAverage(MovingAverageMethods type, int length)
{
return type switch
{
MovingAverageMethods.Simple => new SMA { Length = length },
MovingAverageMethods.Exponential => new EMA { Length = length },
MovingAverageMethods.Smoothed => new EMA { Length = length },
MovingAverageMethods.LinearWeighted => new SMA { Length = length },
_ => new EMA { Length = length }
};
}
private struct SignalInfo
{
public static readonly SignalInfo Empty = new();
public bool BuyOpen;
public bool BuyClose;
public bool SellOpen;
public bool SellClose;
}
public enum MovingAverageMethods
{
Simple,
Exponential,
Smoothed,
LinearWeighted
}
public enum AppliedPriceTypes
{
Close,
Open,
High,
Low,
Median,
Typical,
Weighted
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
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.Indicators import ExponentialMovingAverage, AverageTrueRange, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class omni_trend_strategy(Strategy):
"""Trend-following strategy replicating the Omni Trend MetaTrader expert."""
def __init__(self):
super(omni_trend_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe used to build Omni Trend signals", "General")
self._ma_length = self.Param("MaLength", 13) \
.SetGreaterThanZero() \
.SetDisplay("MA Length", "Moving average period", "Indicators")
self._atr_length = self.Param("AtrLength", 11) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR period for volatility bands", "Indicators")
self._volatility_factor = self.Param("VolatilityFactor", 1.3) \
.SetGreaterThanZero() \
.SetDisplay("Volatility Factor", "Multiplier applied to ATR", "Indicators")
self._money_risk = self.Param("MoneyRisk", 0.15) \
.SetGreaterThanZero() \
.SetDisplay("Money Risk", "Offset factor used to position trend bands", "Indicators")
self._signal_bar = self.Param("SignalBar", 0) \
.SetDisplay("Signal Bar", "Delay in bars before acting on a signal", "Trading")
self._enable_buy_open = self.Param("EnableBuyOpen", True) \
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading")
self._enable_sell_open = self.Param("EnableSellOpen", True) \
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading")
self._enable_buy_close = self.Param("EnableBuyClose", True) \
.SetDisplay("Enable Long Exits", "Allow closing long positions", "Trading")
self._enable_sell_close = self.Param("EnableSellClose", True) \
.SetDisplay("Enable Short Exits", "Allow closing short positions", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 0) \
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 0) \
.SetDisplay("Take Profit (points)", "Profit target distance expressed in price steps", "Risk")
self.Volume = 1
self._ma = None
self._atr = None
self._prev_smin = 0.0
self._prev_smax = 0.0
self._prev_trend_up = 0.0
self._prev_trend_down = 0.0
self._prev_trend = 0
self._is_initialized = False
self._long_entry_price = None
self._short_entry_price = None
self._pending_signals = []
@property
def CandleType(self):
return self._candle_type.Value
@property
def MaLength(self):
return self._ma_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def VolatilityFactor(self):
return self._volatility_factor.Value
@property
def MoneyRisk(self):
return self._money_risk.Value
@property
def SignalBar(self):
return max(0, int(self._signal_bar.Value))
@property
def EnableBuyOpen(self):
return self._enable_buy_open.Value
@property
def EnableSellOpen(self):
return self._enable_sell_open.Value
@property
def EnableBuyClose(self):
return self._enable_buy_close.Value
@property
def EnableSellClose(self):
return self._enable_sell_close.Value
@property
def StopLossPoints(self):
return max(0, int(self._stop_loss_points.Value))
@property
def TakeProfitPoints(self):
return max(0, int(self._take_profit_points.Value))
def OnStarted2(self, time):
super(omni_trend_strategy, self).OnStarted2(time)
self._prev_smin = 0.0
self._prev_smax = 0.0
self._prev_trend_up = 0.0
self._prev_trend_down = 0.0
self._prev_trend = 0
self._is_initialized = False
self._long_entry_price = None
self._short_entry_price = None
self._pending_signals = []
self._ma = ExponentialMovingAverage()
self._ma.Length = self.MaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
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._ma)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._ma is None or self._atr is None:
return
atr_result = self._atr.Process(CandleIndicatorValue(self._atr, candle))
applied_price = Decimal(float(candle.ClosePrice))
ma_result = process_float(self._ma, applied_price, candle.OpenTime, True)
if not atr_result.IsFinal or not ma_result.IsFinal:
return
self._check_risk(candle)
atr_v = float(atr_result)
ma_v = float(ma_result)
sig = self._calculate_signal(candle, ma_v, atr_v)
self._pending_signals.append(sig)
while len(self._pending_signals) > self.SignalBar:
pending = self._pending_signals.pop(0)
self._execute_signal(candle, pending)
def _calculate_signal(self, candle, ma_v, atr_v):
vf = float(self.VolatilityFactor)
mr = float(self.MoneyRisk)
smax = ma_v + vf * atr_v
smin = ma_v - vf * atr_v
if not self._is_initialized:
self._prev_smax = smax
self._prev_smin = smin
self._prev_trend_up = 0.0
self._prev_trend_down = 0.0
self._prev_trend = 0
self._is_initialized = True
return [False, False, False, False]
trend = self._prev_trend
if float(candle.HighPrice) > self._prev_smax:
trend = 1
elif float(candle.LowPrice) < self._prev_smin:
trend = -1
trend_up = None
trend_down = None
if trend > 0:
if smin < self._prev_smin:
smin = self._prev_smin
candidate = smin - (mr - 1.0) * atr_v
if self._prev_trend > 0 and self._prev_trend_up > 0.0 and candidate < self._prev_trend_up:
candidate = self._prev_trend_up
trend_up = candidate
elif trend < 0:
if smax > self._prev_smax:
smax = self._prev_smax
candidate = smax + (mr - 1.0) * atr_v
if self._prev_trend < 0 and self._prev_trend_down > 0.0 and candidate > self._prev_trend_down:
candidate = self._prev_trend_down
trend_down = candidate
sig = [False, False, False, False] # buy_open, buy_close, sell_open, sell_close
if trend > 0:
if self._prev_trend <= 0 and trend_up is not None and self.EnableBuyOpen:
sig[0] = True
if trend_up is not None and self.EnableSellClose:
sig[3] = True
elif trend < 0:
if self._prev_trend >= 0 and trend_down is not None and self.EnableSellOpen:
sig[2] = True
if trend_down is not None and self.EnableBuyClose:
sig[1] = True
self._prev_trend = trend
self._prev_smax = smax
self._prev_smin = smin
self._prev_trend_up = trend_up if trend_up is not None else 0.0
self._prev_trend_down = trend_down if trend_down is not None else 0.0
return sig
def _execute_signal(self, candle, sig):
buy_open, buy_close, sell_open, sell_close = sig
if buy_close and self.Position > 0:
self.SellMarket()
self._long_entry_price = None
if sell_close and self.Position < 0:
self.BuyMarket()
self._short_entry_price = None
exec_price = float(candle.ClosePrice) if self.SignalBar == 0 else float(candle.OpenPrice)
if buy_open and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self._short_entry_price = None
self.BuyMarket()
self._long_entry_price = exec_price
if sell_open and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self._long_entry_price = None
self.SellMarket()
self._short_entry_price = exec_price
def _check_risk(self, candle):
sec = self.Security
if sec is None:
return
step = float(sec.PriceStep) if sec.PriceStep is not None else 0.01
if step <= 0:
return
if self.Position > 0:
if self.StopLossPoints > 0 and self._long_entry_price is not None:
stop_price = self._long_entry_price - self.StopLossPoints * step
if float(candle.LowPrice) <= stop_price or float(candle.ClosePrice) <= stop_price:
self.SellMarket()
self._long_entry_price = None
return
if self.TakeProfitPoints > 0 and self._long_entry_price is not None:
target = self._long_entry_price + self.TakeProfitPoints * step
if float(candle.HighPrice) >= target or float(candle.ClosePrice) >= target:
self.SellMarket()
self._long_entry_price = None
return
elif self.Position < 0:
if self.StopLossPoints > 0 and self._short_entry_price is not None:
stop_price = self._short_entry_price + self.StopLossPoints * step
if float(candle.HighPrice) >= stop_price or float(candle.ClosePrice) >= stop_price:
self.BuyMarket()
self._short_entry_price = None
return
if self.TakeProfitPoints > 0 and self._short_entry_price is not None:
target = self._short_entry_price - self.TakeProfitPoints * step
if float(candle.LowPrice) <= target or float(candle.ClosePrice) <= target:
self.BuyMarket()
self._short_entry_price = None
return
def OnReseted(self):
super(omni_trend_strategy, self).OnReseted()
self._pending_signals = []
self._ma = None
self._atr = None
self._prev_smin = 0.0
self._prev_smax = 0.0
self._prev_trend_up = 0.0
self._prev_trend_down = 0.0
self._prev_trend = 0
self._is_initialized = False
self._long_entry_price = None
self._short_entry_price = None
def CreateClone(self):
return omni_trend_strategy()