using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bill Williams Fractal Breakout strategy filtered by Alligator alignment.
/// Buys on up-fractal breakout when price is above alligator teeth,
/// sells on down-fractal breakout when price is below alligator teeth.
/// Exits on opposite signal (reverse position).
/// </summary>
public class FTBillWillamsTraderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _jawPeriod;
private readonly StrategyParam<int> _teethPeriod;
private readonly StrategyParam<int> _lipsPeriod;
private readonly StrategyParam<int> _fractalLen;
private SMA _jaw = null!;
private SMA _teeth = null!;
private SMA _lips = null!;
private decimal[] _highBuf = Array.Empty<decimal>();
private decimal[] _lowBuf = Array.Empty<decimal>();
private int _bufCount;
private decimal? _pendingBuyLevel;
private decimal? _pendingSellLevel;
private decimal _prevJaw;
private decimal _prevTeeth;
private decimal _prevLips;
private decimal _entryPrice;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int JawPeriod
{
get => _jawPeriod.Value;
set => _jawPeriod.Value = value;
}
public int TeethPeriod
{
get => _teethPeriod.Value;
set => _teethPeriod.Value = value;
}
public int LipsPeriod
{
get => _lipsPeriod.Value;
set => _lipsPeriod.Value = value;
}
public int FractalLen
{
get => _fractalLen.Value;
set => _fractalLen.Value = value;
}
public FTBillWillamsTraderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "General");
_jawPeriod = Param(nameof(JawPeriod), 13)
.SetDisplay("Jaw Period", "Alligator jaw SMA period", "Alligator");
_teethPeriod = Param(nameof(TeethPeriod), 8)
.SetDisplay("Teeth Period", "Alligator teeth SMA period", "Alligator");
_lipsPeriod = Param(nameof(LipsPeriod), 5)
.SetDisplay("Lips Period", "Alligator lips SMA period", "Alligator");
_fractalLen = Param(nameof(FractalLen), 5)
.SetDisplay("Fractal Length", "Number of bars for fractal detection", "Signals");
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_jaw = new SMA { Length = JawPeriod };
_teeth = new SMA { Length = TeethPeriod };
_lips = new SMA { Length = LipsPeriod };
_highBuf = new decimal[FractalLen];
_lowBuf = new decimal[FractalLen];
_bufCount = 0;
_pendingBuyLevel = null;
_pendingSellLevel = null;
_prevJaw = 0;
_prevTeeth = 0;
_prevLips = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_jaw = new SMA { Length = JawPeriod };
_teeth = new SMA { Length = TeethPeriod };
_lips = new SMA { Length = LipsPeriod };
_highBuf = new decimal[FractalLen];
_lowBuf = new decimal[FractalLen];
_bufCount = 0;
_pendingBuyLevel = null;
_pendingSellLevel = null;
_prevJaw = 0;
_prevTeeth = 0;
_prevLips = 0;
_entryPrice = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_jaw, _teeth, _lips, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _jaw);
DrawIndicator(area, _teeth);
DrawIndicator(area, _lips);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal jawVal, decimal teethVal, decimal lipsVal)
{
if (candle.State != CandleStates.Finished)
return;
// Update fractal buffers
UpdateFractals(candle);
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
// Manage existing positions - exit on opposite signal
if (Position > 0)
{
// Close long if price breaks below pending sell level or close drops below teeth
if (_pendingSellLevel is decimal sellLvl && low < sellLvl)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
// Close short if price breaks above pending buy level or close rises above teeth
if (_pendingBuyLevel is decimal buyLvl && high > buyLvl)
{
BuyMarket();
_entryPrice = 0;
}
}
// Enter long: price breaks above up-fractal level, close above teeth (bullish)
if (Position <= 0 && _pendingBuyLevel is decimal pendBuy)
{
if (high > pendBuy && close > teethVal)
{
if (Position < 0)
BuyMarket(); // close short first
BuyMarket();
_entryPrice = close;
}
}
// Enter short: price breaks below down-fractal level, close below teeth (bearish)
if (Position >= 0 && _pendingSellLevel is decimal pendSell)
{
if (low < pendSell && close < teethVal)
{
if (Position > 0)
SellMarket(); // close long first
SellMarket();
_entryPrice = close;
}
}
_prevJaw = jawVal;
_prevTeeth = teethVal;
_prevLips = lipsVal;
}
private void UpdateFractals(ICandleMessage candle)
{
var len = _highBuf.Length;
if (len < 3)
return;
// Shift buffers
Array.Copy(_highBuf, 1, _highBuf, 0, len - 1);
_highBuf[len - 1] = candle.HighPrice;
Array.Copy(_lowBuf, 1, _lowBuf, 0, len - 1);
_lowBuf[len - 1] = candle.LowPrice;
_bufCount++;
if (_bufCount < len)
return;
var wing = (len - 1) / 2;
var center = len - 1 - wing;
// Check up fractal
var centerHigh = _highBuf[center];
var isUp = true;
for (var i = 0; i < len; i++)
{
if (i != center && _highBuf[i] >= centerHigh)
{
isUp = false;
break;
}
}
if (isUp)
_pendingBuyLevel = centerHigh;
// Check down fractal
var centerLow = _lowBuf[center];
var isDown = true;
for (var i = 0; i < len; i++)
{
if (i != center && _lowBuf[i] <= centerLow)
{
isDown = false;
break;
}
}
if (isDown)
_pendingSellLevel = centerLow;
}
}
import clr
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
from StockSharp.Algo.Indicators import SimpleMovingAverage
class ft_bill_willams_trader_strategy(Strategy):
def __init__(self):
super(ft_bill_willams_trader_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Candle type for strategy", "General")
self._jaw_period = self.Param("JawPeriod", 13) \
.SetDisplay("Jaw Period", "Alligator jaw SMA period", "Alligator")
self._teeth_period = self.Param("TeethPeriod", 8) \
.SetDisplay("Teeth Period", "Alligator teeth SMA period", "Alligator")
self._lips_period = self.Param("LipsPeriod", 5) \
.SetDisplay("Lips Period", "Alligator lips SMA period", "Alligator")
self._fractal_len = self.Param("FractalLen", 5) \
.SetDisplay("Fractal Length", "Number of bars for fractal detection", "Signals")
self._high_buf = []
self._low_buf = []
self._buf_count = 0
self._pending_buy_level = None
self._pending_sell_level = None
self._prev_jaw = 0.0
self._prev_teeth = 0.0
self._prev_lips = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def JawPeriod(self):
return self._jaw_period.Value
@property
def TeethPeriod(self):
return self._teeth_period.Value
@property
def LipsPeriod(self):
return self._lips_period.Value
@property
def FractalLen(self):
return self._fractal_len.Value
def OnStarted2(self, time):
super(ft_bill_willams_trader_strategy, self).OnStarted2(time)
self._jaw = SimpleMovingAverage()
self._jaw.Length = self.JawPeriod
self._teeth = SimpleMovingAverage()
self._teeth.Length = self.TeethPeriod
self._lips = SimpleMovingAverage()
self._lips.Length = self.LipsPeriod
fractal_len = self.FractalLen
self._high_buf = [0.0] * fractal_len
self._low_buf = [0.0] * fractal_len
self._buf_count = 0
self._pending_buy_level = None
self._pending_sell_level = None
self._prev_jaw = 0.0
self._prev_teeth = 0.0
self._prev_lips = 0.0
self._entry_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._jaw, self._teeth, self._lips, self.ProcessCandle).Start()
def ProcessCandle(self, candle, jaw_val, teeth_val, lips_val):
if candle.State != CandleStates.Finished:
return
jaw_val = float(jaw_val)
teeth_val = float(teeth_val)
lips_val = float(lips_val)
self._update_fractals(candle)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._pending_sell_level is not None and low < self._pending_sell_level:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if self._pending_buy_level is not None and high > self._pending_buy_level:
self.BuyMarket()
self._entry_price = 0.0
if self.Position <= 0 and self._pending_buy_level is not None:
if high > self._pending_buy_level and close > teeth_val:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
if self.Position >= 0 and self._pending_sell_level is not None:
if low < self._pending_sell_level and close < teeth_val:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._prev_jaw = jaw_val
self._prev_teeth = teeth_val
self._prev_lips = lips_val
def _update_fractals(self, candle):
flen = self.FractalLen
if flen < 3:
return
self._high_buf.pop(0)
self._high_buf.append(float(candle.HighPrice))
self._low_buf.pop(0)
self._low_buf.append(float(candle.LowPrice))
self._buf_count += 1
if self._buf_count < flen:
return
wing = (flen - 1) // 2
center = flen - 1 - wing
center_high = self._high_buf[center]
is_up = True
for i in range(flen):
if i != center and self._high_buf[i] >= center_high:
is_up = False
break
if is_up:
self._pending_buy_level = center_high
center_low = self._low_buf[center]
is_down = True
for i in range(flen):
if i != center and self._low_buf[i] <= center_low:
is_down = False
break
if is_down:
self._pending_sell_level = center_low
def OnReseted(self):
super(ft_bill_willams_trader_strategy, self).OnReseted()
self._high_buf = []
self._low_buf = []
self._buf_count = 0
self._pending_buy_level = None
self._pending_sell_level = None
self._prev_jaw = 0.0
self._prev_teeth = 0.0
self._prev_lips = 0.0
self._entry_price = 0.0
def CreateClone(self):
return ft_bill_willams_trader_strategy()