Lucky 策略
Lucky 策略是一种突破型剥头皮系统,专注于最优买价和卖价之间的快速跳动。当卖价(Ask)相对上一笔报价上跳超过设定的点数时做多;当买价(Bid)下跌超过同样的点数时做空。只要头寸转为盈利,或价格回撤超过保护阈值,仓位会立即平仓。
数据与执行
- 数据来源:需要 Level 1 行情,以持续获取最优买卖价。
- 订单类型:所有进出场均使用市价单,以保证对报价突变的快速响应。
- 持仓模式:原策略针对对冲账户设计,但在净头寸账户中同样适用,会自动累计净头寸。
参数
- Shift points – 触发交易所需的最小点数跳动。数值越大越能过滤噪音,数值越小反应越敏感。
- Limit points – 容许的最大不利波动(点数)。达到该阈值时将强制平仓,计算时会结合合约的最小报价步长。
- Reverse mode – 反向开仓模式。启用后,卖价上跳触发做空,买价下跌触发做多。
交易逻辑
- 初始化
- 根据合约报价步长,把点数参数转换为实际价格距离。
- 订阅 Level 1 数据,并清空上一笔 bid/ask 缓存。
- 入场规则
- Ask 相对上一笔 Ask 上涨超过阈值时买入(反向模式下改为卖出)。
- Bid 相对上一笔 Bid 下跌超过阈值时卖出(反向模式下改为买入)。
- 头寸规模
- 默认使用策略自身的
Volume作为下单数量。 - 如果能获取到组合资产价值,会模仿 MetaTrader 的做法:按照
FreeMargin / 10,000估算手数,并保留一位小数,使资金规模更大的账户自动放大下单量。
- 默认使用策略自身的
- 离场规则
- 多头在 Bid 超过平均开仓价时立即平仓;若 Ask 跌破开仓价并达到 Limit points,也会止损离场。
- 空头在 Ask 跌破开仓价时获利平仓;若 Bid 向上突破开仓价且超过 Limit points,则止损离场。
使用建议
- 最适合流动性充足且报价跳动明显的外汇或指数差价合约。
- 实盘前建议额外配置组合级别的风控,例如总权益回撤止损。
- 开启 Reverse mode 可以快速将策略转变为反向做单模式,无需修改其他参数。
- 策略会对每一次满足条件的报价更新作出响应,如行情噪音较大,可适当增大 shift 参数或过滤数据频率。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.Algo.Candles;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy that reacts to fast price shifts (candle-to-candle high/low jumps)
/// and closes trades on profit target or adverse move (stop loss).
/// </summary>
public class LuckyStrategy : Strategy
{
private readonly StrategyParam<decimal> _shiftPct;
private readonly StrategyParam<decimal> _profitPct;
private readonly StrategyParam<decimal> _stopPct;
private readonly StrategyParam<bool> _reverse;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal? _previousHigh;
private decimal? _previousLow;
private bool _isReady;
/// <summary>
/// Minimum percentage shift in high/low to trigger entry.
/// </summary>
public decimal ShiftPct
{
get => _shiftPct.Value;
set => _shiftPct.Value = value;
}
/// <summary>
/// Profit target as percentage of entry price.
/// </summary>
public decimal ProfitPct
{
get => _profitPct.Value;
set => _profitPct.Value = value;
}
/// <summary>
/// Stop loss as percentage of entry price.
/// </summary>
public decimal StopPct
{
get => _stopPct.Value;
set => _stopPct.Value = value;
}
/// <summary>
/// Switch to invert the trading direction.
/// </summary>
public bool Reverse
{
get => _reverse.Value;
set => _reverse.Value = value;
}
/// <summary>
/// Candle type and timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes the strategy parameters.
/// </summary>
public LuckyStrategy()
{
_shiftPct = Param(nameof(ShiftPct), 1.5m)
.SetDisplay("Shift %", "Minimum percentage shift in high/low to trigger entry", "Trading")
.SetOptimize(0.5m, 3.0m, 0.5m);
_profitPct = Param(nameof(ProfitPct), 2.0m)
.SetDisplay("Profit %", "Profit target as percentage of entry price", "Risk management")
.SetOptimize(1.0m, 5.0m, 0.5m);
_stopPct = Param(nameof(StopPct), 3.0m)
.SetDisplay("Stop %", "Stop loss as percentage of entry price", "Risk management")
.SetOptimize(1.0m, 5.0m, 0.5m);
_reverse = Param(nameof(Reverse), false)
.SetDisplay("Reverse mode", "Invert the direction of new trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_previousHigh = null;
_previousLow = null;
_entryPrice = 0m;
_isReady = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
if (!_isReady)
{
_previousHigh = high;
_previousLow = low;
_isReady = true;
return;
}
// Try to close existing position first
TryClosePosition(close);
// Only open new positions if flat
if (Position == 0 && _previousHigh.HasValue && _previousLow.HasValue)
{
var prevH = _previousHigh.Value;
var prevL = _previousLow.Value;
// Check for upward breakout: high moved up sharply relative to previous high
if (prevH > 0m && (high - prevH) / prevH * 100m >= ShiftPct)
{
if (Reverse)
OpenShort(close);
else
OpenLong(close);
}
// Check for downward breakdown: low moved down sharply relative to previous low
else if (prevL > 0m && (prevL - low) / prevL * 100m >= ShiftPct)
{
if (Reverse)
OpenLong(close);
else
OpenShort(close);
}
}
_previousHigh = high;
_previousLow = low;
}
private void OpenLong(decimal price)
{
BuyMarket(Volume);
_entryPrice = price;
}
private void OpenShort(decimal price)
{
SellMarket(Volume);
_entryPrice = price;
}
private void TryClosePosition(decimal currentPrice)
{
if (Position == 0 || _entryPrice <= 0m)
return;
if (Position > 0)
{
var pctChange = (currentPrice - _entryPrice) / _entryPrice * 100m;
if (pctChange >= ProfitPct || pctChange <= -StopPct)
SellMarket(Position);
}
else if (Position < 0)
{
var pctChange = (_entryPrice - currentPrice) / _entryPrice * 100m;
if (pctChange >= ProfitPct || pctChange <= -StopPct)
BuyMarket(Math.Abs(Position));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class lucky_strategy(Strategy):
"""Breakout strategy that reacts to fast price shifts (candle-to-candle high/low jumps)
and closes trades on profit target or adverse move (stop loss)."""
def __init__(self):
super(lucky_strategy, self).__init__()
self._shift_pct = self.Param("ShiftPct", 1.5) \
.SetDisplay("Shift %", "Minimum percentage shift in high/low to trigger entry", "Trading") \
.SetOptimize(0.5, 3.0, 0.5)
self._profit_pct = self.Param("ProfitPct", 2.0) \
.SetDisplay("Profit %", "Profit target as percentage of entry price", "Risk management") \
.SetOptimize(1.0, 5.0, 0.5)
self._stop_pct = self.Param("StopPct", 3.0) \
.SetDisplay("Stop %", "Stop loss as percentage of entry price", "Risk management") \
.SetOptimize(1.0, 5.0, 0.5)
self._reverse = self.Param("Reverse", False) \
.SetDisplay("Reverse mode", "Invert the direction of new trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle type", "Candle timeframe", "General")
self._entry_price = 0.0
self._previous_high = None
self._previous_low = None
self._is_ready = False
@property
def ShiftPct(self):
return self._shift_pct.Value
@property
def ProfitPct(self):
return self._profit_pct.Value
@property
def StopPct(self):
return self._stop_pct.Value
@property
def Reverse(self):
return self._reverse.Value
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(lucky_strategy, self).OnReseted()
self._previous_high = None
self._previous_low = None
self._entry_price = 0.0
self._is_ready = False
def OnStarted2(self, time):
super(lucky_strategy, self).OnStarted2(time)
self.SubscribeCandles(self.CandleType) \
.Bind(self.process_candle) \
.Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if not self._is_ready:
self._previous_high = high
self._previous_low = low
self._is_ready = True
return
# Try to close existing position first
self._try_close_position(close)
# Only open new positions if flat
if self.Position == 0 and self._previous_high is not None and self._previous_low is not None:
prev_h = self._previous_high
prev_l = self._previous_low
# Check for upward breakout: high moved up sharply relative to previous high
if prev_h > 0 and (high - prev_h) / prev_h * 100.0 >= float(self.ShiftPct):
if self.Reverse:
self._open_short(close)
else:
self._open_long(close)
# Check for downward breakdown: low moved down sharply relative to previous low
elif prev_l > 0 and (prev_l - low) / prev_l * 100.0 >= float(self.ShiftPct):
if self.Reverse:
self._open_long(close)
else:
self._open_short(close)
self._previous_high = high
self._previous_low = low
def _open_long(self, price):
self.BuyMarket(self.Volume)
self._entry_price = price
def _open_short(self, price):
self.SellMarket(self.Volume)
self._entry_price = price
def _try_close_position(self, current_price):
if self.Position == 0 or self._entry_price <= 0:
return
if self.Position > 0:
pct_change = (current_price - self._entry_price) / self._entry_price * 100.0
if pct_change >= float(self.ProfitPct) or pct_change <= -float(self.StopPct):
self.SellMarket(self.Position)
elif self.Position < 0:
pct_change = (self._entry_price - current_price) / self._entry_price * 100.0
if pct_change >= float(self.ProfitPct) or pct_change <= -float(self.StopPct):
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
return lucky_strategy()