抛硬币策略
概述
抛硬币策略 在没有持仓时,会在每根新蜡烛上随机选择做多或做空。若上一笔交易亏损,下一笔交易的手数会根据马丁系数增加。策略通过以价格步长表示的固定止盈和止损来平仓,并可在价格移动一定距离后启用追踪止损。
参数
Volume– 初始下单手数。Martingale– 亏损后放大的马丁系数。MaxVolume– 马丁放大后的最大手数限制。TakeProfit– 以价格步长表示的止盈值。StopLoss– 以价格步长表示的止损值。TrailingStart– 激活追踪止损所需的价格步长。TrailingStop– 追踪止损的价格步长。CandleType– 用于决策的蜡烛周期。
工作流程
- 每根收盘蜡烛到来时,检查是否有持仓。
- 如果有持仓,根据收盘价计算盈亏;满足止盈、止损或追踪止损条件时平仓。
- 若没有持仓,则抛硬币决定方向:
- 正面开多。
- 反面开空。
- 若上一笔交易亏损,则按
Martingale系数放大下次手数,但不超过MaxVolume。 - 当价格朝有利方向移动达到
TrailingStart时启动追踪止损。
注意
该示例仅用于展示如何使用 StockSharp 高级 API 构建随机信号和仓位管理的策略。
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()