Münzwurf-Strategie
Übersicht
Die Münzwurf-Strategie wählt zufällig Long oder Short bei jeder neuen Kerze, wenn keine Position offen ist. Nach dem Schließen einer Position wird die nächste Handelsgröße bei einem Verlusthandel mithilfe eines Martingale-Multiplikators erhöht. Die Strategie schließt Positionen mit festen Take-Profit- und Stop-Loss-Niveaus, die in Preisschritten definiert sind, und kann optional Gewinne nach einer bestimmten Distanz verfolgen.
Parameter
Volume– Basisauftragsgröße für den ersten Versuch.Martingale– Multiplikator, der nach einem Verlusthandel auf das Volumen angewendet wird.MaxVolume– Obergrenze für die Positionsgröße nach Martingale-Erhöhungen.TakeProfit– Gewinnziel in Preisschritten.StopLoss– Verlustlimit in Preisschritten.TrailingStart– Abstand in Preisschritten, ab dem das Trailing aktiv wird.TrailingStop– Trailing-Stop-Abstand in Preisschritten.CandleType– Zeitrahmen der Kerzen für die Entscheidungsfindung.
Funktionsweise
- Bei jeder abgeschlossenen Kerze prüft die Strategie, ob eine offene Position vorhanden ist.
- Wenn eine Position besteht, wird Gewinn oder Verlust anhand des aktuellen Schlusskurses überwacht. Sobald Take-Profit-, Stop-Loss- oder Trailing-Stop-Bedingungen erfüllt sind, wird die Position geschlossen.
- Wenn keine Position offen ist, wird eine virtuelle Münze geworfen:
- Kopf öffnet eine Long-Position.
- Zahl öffnet eine Short-Position.
- Wenn der vorherige Trade ein Verlust war, wird das Volumen mit
Martingalemultipliziert, begrenzt durchMaxVolume. - Der Trailing Stop wird aktiviert, sobald sich der Preis um
TrailingStartin die günstige Richtung bewegt.
Hinweise
Dieses Beispiel dient zu Lehrzwecken und zeigt, wie man mit Zufallssignalen und Positionsgrößen mithilfe der StockSharp High-Level API arbeitet.
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that randomly opens long or short positions.
/// Uses a martingale multiplier after losses and manages
/// take profit, stop loss and optional trailing stop in price steps.
/// </summary>
public class CoinFlipStrategy : Strategy
{
private readonly StrategyParam<decimal> _martingale;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<int> _trailingStart;
private readonly StrategyParam<int> _trailingStop;
private readonly StrategyParam<DataType> _candleType;
private static readonly Random _random = new();
private decimal _entryPrice;
private decimal _currentVolume;
private decimal _trailingLevel;
private bool _isLong;
private bool _lastTradeLoss;
/// <summary>
/// Multiplier applied to volume after a losing trade.
/// </summary>
public decimal Martingale
{
get => _martingale.Value;
set => _martingale.Value = value;
}
/// <summary>
/// Maximum allowed volume after applying martingale.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public int TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public int StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Distance in price steps to activate trailing stop.
/// </summary>
public int TrailingStart
{
get => _trailingStart.Value;
set => _trailingStart.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public int TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CoinFlipStrategy()
{
_martingale = Param(nameof(Martingale), 1.8m)
.SetGreaterThanZero()
.SetDisplay("Martingale", "Volume multiplier after loss", "General");
_maxVolume = Param(nameof(MaxVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Max Volume", "Upper limit for volume", "General");
_takeProfit = Param(nameof(TakeProfit), 50)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Profit target in steps", "Risk");
_stopLoss = Param(nameof(StopLoss), 25)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Loss limit in steps", "Risk");
_trailingStart = Param(nameof(TrailingStart), 14)
.SetNotNegative()
.SetDisplay("Trailing Start", "Steps to activate trailing", "Risk");
_trailingStop = Param(nameof(TrailingStop), 3)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing distance in steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_currentVolume = 0;
_trailingLevel = 0m;
_isLong = false;
_lastTradeLoss = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_currentVolume = Volume;
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;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0)
{
if (_entryPrice == 0)
return;
// Manage open position using percent of entry
var profitPct = _isLong
? (candle.ClosePrice - _entryPrice) / _entryPrice * 100m
: (_entryPrice - candle.ClosePrice) / _entryPrice * 100m;
// Update trailing stop if in profit
if (TrailingStart > 0 && profitPct >= TrailingStart * 0.1m)
{
var newLevel = _isLong
? candle.ClosePrice * (1m - TrailingStop * 0.1m / 100m)
: candle.ClosePrice * (1m + TrailingStop * 0.1m / 100m);
if (_trailingLevel == 0m)
_trailingLevel = newLevel;
else if (_isLong && newLevel > _trailingLevel)
_trailingLevel = newLevel;
else if (!_isLong && newLevel < _trailingLevel)
_trailingLevel = newLevel;
}
// Check exits - TP/SL as percentage (TakeProfit/10 %)
if (profitPct >= TakeProfit * 0.1m)
{
ExitPosition(candle.ClosePrice, false);
return;
}
if (profitPct <= -StopLoss * 0.1m)
{
ExitPosition(candle.ClosePrice, true);
return;
}
if (_trailingLevel != 0m)
{
if ((_isLong && candle.ClosePrice <= _trailingLevel) ||
(!_isLong && candle.ClosePrice >= _trailingLevel))
{
ExitPosition(candle.ClosePrice, candle.ClosePrice < _entryPrice);
}
}
return;
}
// No open position -> decide direction
var flip = _random.Next(0, 2);
_isLong = flip == 0;
_currentVolume = _lastTradeLoss
? Math.Min(_currentVolume * Martingale, MaxVolume)
: Volume;
_entryPrice = candle.ClosePrice;
_trailingLevel = 0m;
if (_isLong)
BuyMarket();
else
SellMarket();
}
private void ExitPosition(decimal closePrice, bool isLoss)
{
if (_isLong)
{
SellMarket();
}
else
{
BuyMarket();
}
_lastTradeLoss = isLoss;
_entryPrice = 0m;
_trailingLevel = 0m;
}
}
import clr
import random
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class coin_flip_strategy(Strategy):
def __init__(self):
super(coin_flip_strategy, self).__init__()
self._martingale = self.Param("Martingale", 1.8) \
.SetDisplay("Martingale", "Volume multiplier after loss", "General")
self._max_volume = self.Param("MaxVolume", 1.0) \
.SetDisplay("Max Volume", "Upper limit for volume", "General")
self._take_profit = self.Param("TakeProfit", 50) \
.SetDisplay("Take Profit", "Profit target in steps", "Risk")
self._stop_loss = self.Param("StopLoss", 25) \
.SetDisplay("Stop Loss", "Loss limit in steps", "Risk")
self._trailing_start = self.Param("TrailingStart", 14) \
.SetDisplay("Trailing Start", "Steps to activate trailing", "Risk")
self._trailing_stop = self.Param("TrailingStop", 3) \
.SetDisplay("Trailing Stop", "Trailing distance in steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for analysis", "General")
self._entry_price = 0.0
self._current_volume = 0.0
self._trailing_level = 0.0
self._is_long = False
self._last_trade_loss = False
@property
def martingale(self):
return self._martingale.Value
@property
def max_volume(self):
return self._max_volume.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def trailing_start(self):
return self._trailing_start.Value
@property
def trailing_stop(self):
return self._trailing_stop.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(coin_flip_strategy, self).OnReseted()
self._entry_price = 0.0
self._current_volume = 0.0
self._trailing_level = 0.0
self._is_long = False
self._last_trade_loss = False
def OnStarted2(self, time):
super(coin_flip_strategy, self).OnStarted2(time)
self._current_volume = float(self.Volume)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _exit_position(self, close_price, is_loss):
if self._is_long:
self.SellMarket()
else:
self.BuyMarket()
self._last_trade_loss = is_loss
self._entry_price = 0.0
self._trailing_level = 0.0
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
tp = int(self.take_profit)
sl = int(self.stop_loss)
ts_start = int(self.trailing_start)
ts_stop = int(self.trailing_stop)
if self.Position != 0:
if self._entry_price == 0.0:
return
if self._is_long:
profit_pct = (close - self._entry_price) / self._entry_price * 100.0
else:
profit_pct = (self._entry_price - close) / self._entry_price * 100.0
# Update trailing stop if in profit
if ts_start > 0 and profit_pct >= ts_start * 0.1:
if self._is_long:
new_level = close * (1.0 - ts_stop * 0.1 / 100.0)
else:
new_level = close * (1.0 + ts_stop * 0.1 / 100.0)
if self._trailing_level == 0.0:
self._trailing_level = new_level
elif self._is_long and new_level > self._trailing_level:
self._trailing_level = new_level
elif not self._is_long and new_level < self._trailing_level:
self._trailing_level = new_level
# Check TP
if profit_pct >= tp * 0.1:
self._exit_position(close, False)
return
# Check SL
if profit_pct <= -sl * 0.1:
self._exit_position(close, True)
return
# Check trailing
if self._trailing_level != 0.0:
if (self._is_long and close <= self._trailing_level) or \
(not self._is_long and close >= self._trailing_level):
self._exit_position(close, close < self._entry_price)
return
# No open position - decide direction
flip = random.randint(0, 1)
self._is_long = (flip == 0)
if self._last_trade_loss:
self._current_volume = min(self._current_volume * float(self.martingale), float(self.max_volume))
else:
self._current_volume = float(self.Volume)
self._entry_price = close
self._trailing_level = 0.0
if self._is_long:
self.BuyMarket()
else:
self.SellMarket()
def CreateClone(self):
return coin_flip_strategy()