Ein direkter StockSharp-Port des MetaTrader-Experten "Nevalyashka". Die Strategie wechselt immer zwischen Long- und Short-Trades: Sie beginnt mit einer Marktverkaufsorder, wartet darauf, dass die Position durch Stop Loss oder Take Profit geschlossen wird, und eröffnet dann sofort eine Marktorder in entgegengesetzter Richtung. Schutzorders werden für jeden Einstieg mit denselben Pip-basierten Abständen wie im Originalcode neu erstellt.
Strategielogik
Initialisierung
Erkennt den Preisschritt des Instruments und die Dezimalstellen, um eine Pip-Größe identisch mit der MQL-Version zu berechnen (3/5-Dezimal-Paare werden mit 10 multipliziert).
Multipliziert das börsennotierte MinVolume mit dem Parameter LotMultiplier, um die Ordergröße zu erhalten, und rundet diese bei Bedarf auf den Volumenschritt.
Kursbearbeitung
Abonniert Auftragsbuch-Updates, um die neuesten besten Bid/Ask-Kurse zu erfassen und ahmt den RefreshRates()-Aufruf des Experten nach.
Orderfluss
Platziert eine anfängliche Marktverkaufsorder, sobald die besten Bid/Ask-Kurse verfügbar sind.
Nachdem eine Position geschlossen wurde, dreht es die Seite um (Kauf nach Verkauf, Verkauf nach Kauf) und gibt eine neue Marktorder mit demselben Volumen aus.
Für jeden ausgeführten Einstieg platziert die Strategie separate Stop-Loss- und Take-Profit-Orders mithilfe der Pip-Distanzparameter.
Risikomanagement
Stop Loss: Optional. Wenn StopLossPips größer als null ist, reicht die Strategie eine schützende Stop-Order ein (SellStop für Long-Positionen, BuyStop für Short-Positionen) bei Einstieg ± StopLossPips * Pip.
Take Profit: Optional. Wenn TakeProfitPips größer als null ist, reicht die Strategie eine schützende Limit-Order ein (SellLimit für Long-Positionen, BuyLimit für Short-Positionen) bei Einstieg ± TakeProfitPips * Pip.
Beide Schutzorders werden storniert, wenn die Position flach ist, um hängende Orders vor dem nächsten Flip zu vermeiden.
Parameter
Name
Beschreibung
Standard
LotMultiplier
Multiplikator, der auf das minimale Instrumentvolumen angewendet wird. Das Ergebnis wird auf den Börsenvolumsschritt gerundet.
1
StopLossPips
Stop-Loss-Abstand in Pips. Auf 0 setzen, um den Stop zu deaktivieren.
50
TakeProfitPips
Take-Profit-Abstand in Pips. Auf 0 setzen, um das Ziel zu deaktivieren.
50
Betriebshinweise
Der Ansatz wechselt kontinuierlich das Exposure und eignet sich daher für Mean-Reversion-Märkte, bei denen ein abgeschlossener Kurs wahrscheinlich umkehrt.
Funktioniert mit jedem Symbol, das Top-of-Book-Kurse bereitstellt; Pip-Berechnungen passen sich automatisch basierend auf der Kursgenauigkeit an.
Die Slippage-Behandlung wird an die Börse delegiert—Orders werden ohne zusätzliche Prüfungen am Markt gesendet, genau wie beim ursprünglichen Experten.
Die Strategie enthält keine Handelszeit-Filter, Nachrichten-Filter oder Trailing Stops. Solche Logik kann durch Erweiterung von TryOpenNextPosition oder RegisterProtectionOrders hinzugefügt werden.
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>
/// Alternating long-short strategy that mirrors the original Nevalyashka MQL logic.
/// Opens an initial sell position and flips direction each time the position is closed.
/// </summary>
public class NevalyashkaFlipStrategy : Strategy
{
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private Sides? _currentSide;
private Sides? _lastCompletedSide;
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type for monitoring price.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="NevalyashkaFlipStrategy"/> class.
/// </summary>
public NevalyashkaFlipStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 5)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pts)", "Stop loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 5)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pts)", "Take profit distance in price steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_currentSide = null;
_lastCompletedSide = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var step = Security?.PriceStep ?? 1m;
var stopDistance = StopLossPoints * step;
var takeDistance = TakeProfitPoints * step;
var price = candle.ClosePrice;
// Check SL/TP for current position
if (Position != 0 && _entryPrice > 0)
{
var hit = false;
if (_currentSide == Sides.Buy)
{
if (stopDistance > 0 && candle.LowPrice <= _entryPrice - stopDistance)
hit = true;
if (takeDistance > 0 && candle.HighPrice >= _entryPrice + takeDistance)
hit = true;
}
else if (_currentSide == Sides.Sell)
{
if (stopDistance > 0 && candle.HighPrice >= _entryPrice + stopDistance)
hit = true;
if (takeDistance > 0 && candle.LowPrice <= _entryPrice - takeDistance)
hit = true;
}
if (hit)
{
// Close position
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_lastCompletedSide = _currentSide;
_currentSide = null;
_entryPrice = 0m;
}
}
// If flat, open next position
if (Position == 0 && _currentSide == null)
{
// Alternate direction: start with sell, then flip
var sideToOpen = _lastCompletedSide switch
{
Sides.Buy => Sides.Sell,
Sides.Sell => Sides.Buy,
_ => Sides.Sell,
};
if (sideToOpen == Sides.Buy)
BuyMarket();
else
SellMarket();
_currentSide = sideToOpen;
_entryPrice = price;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class nevalyashka_flip_strategy(Strategy):
"""Alternating long-short strategy that flips direction each time position is closed."""
def __init__(self):
super(nevalyashka_flip_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 5) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pts)", "Stop loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 5) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pts)", "Take profit distance in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._entry_price = 0.0
self._current_side = 0 # 0=none, 1=buy, -1=sell
self._last_completed_side = 0
@property
def StopLossPoints(self):
return int(self._stop_loss_points.Value)
@property
def TakeProfitPoints(self):
return int(self._take_profit_points.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(nevalyashka_flip_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._current_side = 0
self._last_completed_side = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
stop_dist = self.StopLossPoints * step
take_dist = self.TakeProfitPoints * step
price = float(candle.ClosePrice)
# Check SL/TP for current position
if self.Position != 0 and self._entry_price > 0:
hit = False
if self._current_side == 1:
if stop_dist > 0 and float(candle.LowPrice) <= self._entry_price - stop_dist:
hit = True
if take_dist > 0 and float(candle.HighPrice) >= self._entry_price + take_dist:
hit = True
elif self._current_side == -1:
if stop_dist > 0 and float(candle.HighPrice) >= self._entry_price + stop_dist:
hit = True
if take_dist > 0 and float(candle.LowPrice) <= self._entry_price - take_dist:
hit = True
if hit:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._last_completed_side = self._current_side
self._current_side = 0
self._entry_price = 0.0
# If flat, open next position
if self.Position == 0 and self._current_side == 0:
# Alternate: start with sell, then flip
if self._last_completed_side == 1:
side_to_open = -1
elif self._last_completed_side == -1:
side_to_open = 1
else:
side_to_open = -1
if side_to_open == 1:
self.BuyMarket()
else:
self.SellMarket()
self._current_side = side_to_open
self._entry_price = price
def OnReseted(self):
super(nevalyashka_flip_strategy, self).OnReseted()
self._entry_price = 0.0
self._current_side = 0
self._last_completed_side = 0
def CreateClone(self):
return nevalyashka_flip_strategy()