Estrategia AFL Winner V2
Descripción general
Esta estrategia de ejemplo replica la lógica del indicador AFL Winner V2 utilizando la API de alto nivel de StockSharp. El indicador es aproximado por un oscilador estocástico y las señales se derivan de su posición relativa y niveles de umbral predefinidos.
Lógica de la estrategia
- Usa un
StochasticOscillatorpara emular el comportamiento de AFL Winner. - Abre una posición larga cuando el oscilador indica fuerte impulso alcista.
- Abre una posición corta cuando el oscilador señala fuerte impulso bajista.
- Cierra largos cuando el estado de color cae por debajo de la zona neutral.
- Cierra cortos cuando el estado de color sube por encima de la zona neutral.
- Los parámetros permiten optimizar los períodos del oscilador y los niveles de umbral.
Parámetros
| Parámetro | Descripción |
|---|---|
KPeriod |
Período %K del oscilador estocástico. |
DPeriod |
Período %D del oscilador estocástico. |
HighLevel |
Umbral superior para señales alcistas. |
LowLevel |
Umbral inferior para señales bajistas. |
Archivos
CS/AflWinnerV2Strategy.cs– implementación principal de la estrategia.
Notas
La estrategia opera solo en velas completadas y usa protección automática de posiciones para evitar exposición no deseada.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on the AFL Winner indicator approximation using a stochastic oscillator.
/// </summary>
public class AflWinnerV2Strategy : Strategy
{
private const int BufferSize = 64;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<DataType> _candleType;
private readonly decimal[] _highs = new decimal[BufferSize];
private readonly decimal[] _lows = new decimal[BufferSize];
private readonly decimal[] _rawK = new decimal[BufferSize];
private int _priceIndex;
private int _priceCount;
private int _kIndex;
private int _kCount;
private int _prevColor;
public int KPeriod { get => _kPeriod.Value; set => _kPeriod.Value = value; }
public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AflWinnerV2Strategy()
{
_kPeriod = Param<int>(nameof(KPeriod), 5).SetDisplay("%K Period", "%K Period", "General");
_dPeriod = Param<int>(nameof(DPeriod), 3).SetDisplay("%D Period", "%D Period", "General");
_highLevel = Param<decimal>(nameof(HighLevel), 40m).SetDisplay("High Level", "High Level", "General");
_lowLevel = Param<decimal>(nameof(LowLevel), -40m).SetDisplay("Low Level", "Low Level", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Array.Clear(_highs);
Array.Clear(_lows);
Array.Clear(_rawK);
_priceIndex = 0;
_priceCount = 0;
_kIndex = 0;
_kCount = 0;
_prevColor = -1;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Array.Clear(_highs);
Array.Clear(_lows);
Array.Clear(_rawK);
_priceIndex = 0;
_priceCount = 0;
_kIndex = 0;
_kCount = 0;
_prevColor = -1;
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;
PushPrice(candle.HighPrice, candle.LowPrice);
if (_priceCount < KPeriod)
return;
var highest = GetHighest(KPeriod);
var lowest = GetLowest(KPeriod);
var range = highest - lowest;
var rawK = range > 0 ? (candle.ClosePrice - lowest) / range * 100m : 50m;
PushRawK(rawK);
if (_kCount < DPeriod)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var k = rawK - 50m;
var d = GetRawKAverage(DPeriod) - 50m;
int color;
if (k > d)
color = (k > HighLevel || (k > LowLevel && d <= LowLevel)) ? 3 : 2;
else
color = (k < LowLevel || (d > HighLevel && k <= HighLevel)) ? 0 : 1;
if (color == 3 && _prevColor != 3 && Position <= 0)
{
BuyMarket();
}
else if (color == 0 && _prevColor != 0 && Position >= 0)
{
SellMarket();
}
else if (color < 2 && Position > 0)
{
SellMarket();
}
else if (color > 1 && Position < 0)
{
BuyMarket();
}
_prevColor = color;
}
private void PushPrice(decimal high, decimal low)
{
_highs[_priceIndex] = high;
_lows[_priceIndex] = low;
_priceIndex = (_priceIndex + 1) % BufferSize;
if (_priceCount < BufferSize)
_priceCount++;
}
private void PushRawK(decimal value)
{
_rawK[_kIndex] = value;
_kIndex = (_kIndex + 1) % BufferSize;
if (_kCount < BufferSize)
_kCount++;
}
private decimal GetHighest(int period)
{
var highest = decimal.MinValue;
var count = Math.Min(period, _priceCount);
for (var i = 0; i < count; i++)
{
var idx = (_priceIndex - 1 - i + BufferSize) % BufferSize;
if (_highs[idx] > highest)
highest = _highs[idx];
}
return highest;
}
private decimal GetLowest(int period)
{
var lowest = decimal.MaxValue;
var count = Math.Min(period, _priceCount);
for (var i = 0; i < count; i++)
{
var idx = (_priceIndex - 1 - i + BufferSize) % BufferSize;
if (_lows[idx] < lowest)
lowest = _lows[idx];
}
return lowest;
}
private decimal GetRawKAverage(int period)
{
var count = Math.Min(period, _kCount);
var sum = 0m;
for (var i = 0; i < count; i++)
{
var idx = (_kIndex - 1 - i + BufferSize) % BufferSize;
sum += _rawK[idx];
}
return sum / count;
}
}
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 afl_winner_v2_strategy(Strategy):
BUFFER_SIZE = 64
def __init__(self):
super(afl_winner_v2_strategy, self).__init__()
self._k_period = self.Param("KPeriod", 5) \
.SetDisplay("%K Period", "%K Period", "General")
self._d_period = self.Param("DPeriod", 3) \
.SetDisplay("%D Period", "%D Period", "General")
self._high_level = self.Param("HighLevel", 40.0) \
.SetDisplay("High Level", "High Level", "General")
self._low_level = self.Param("LowLevel", -40.0) \
.SetDisplay("Low Level", "Low Level", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._highs = [0.0] * self.BUFFER_SIZE
self._lows = [0.0] * self.BUFFER_SIZE
self._raw_k = [0.0] * self.BUFFER_SIZE
self._price_index = 0
self._price_count = 0
self._k_index = 0
self._k_count = 0
self._prev_color = -1
@property
def k_period(self):
return self._k_period.Value
@property
def d_period(self):
return self._d_period.Value
@property
def high_level(self):
return self._high_level.Value
@property
def low_level(self):
return self._low_level.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(afl_winner_v2_strategy, self).OnReseted()
self._highs = [0.0] * self.BUFFER_SIZE
self._lows = [0.0] * self.BUFFER_SIZE
self._raw_k = [0.0] * self.BUFFER_SIZE
self._price_index = 0
self._price_count = 0
self._k_index = 0
self._k_count = 0
self._prev_color = -1
def OnStarted2(self, time):
super(afl_winner_v2_strategy, self).OnStarted2(time)
self._highs = [0.0] * self.BUFFER_SIZE
self._lows = [0.0] * self.BUFFER_SIZE
self._raw_k = [0.0] * self.BUFFER_SIZE
self._price_index = 0
self._price_count = 0
self._k_index = 0
self._k_count = 0
self._prev_color = -1
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 _push_price(self, high, low):
self._highs[self._price_index] = high
self._lows[self._price_index] = low
self._price_index = (self._price_index + 1) % self.BUFFER_SIZE
if self._price_count < self.BUFFER_SIZE:
self._price_count += 1
def _push_raw_k(self, value):
self._raw_k[self._k_index] = value
self._k_index = (self._k_index + 1) % self.BUFFER_SIZE
if self._k_count < self.BUFFER_SIZE:
self._k_count += 1
def _get_highest(self, period):
highest = -1e18
count = min(period, self._price_count)
for i in range(count):
idx = (self._price_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
if self._highs[idx] > highest:
highest = self._highs[idx]
return highest
def _get_lowest(self, period):
lowest = 1e18
count = min(period, self._price_count)
for i in range(count):
idx = (self._price_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
if self._lows[idx] < lowest:
lowest = self._lows[idx]
return lowest
def _get_raw_k_average(self, period):
count = min(period, self._k_count)
s = 0.0
for i in range(count):
idx = (self._k_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
s += self._raw_k[idx]
return s / count if count > 0 else 0.0
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._push_price(high, low)
kp = int(self.k_period)
dp = int(self.d_period)
if self._price_count < kp:
return
highest = self._get_highest(kp)
lowest = self._get_lowest(kp)
rng = highest - lowest
raw_k = (close - lowest) / rng * 100.0 if rng > 0 else 50.0
self._push_raw_k(raw_k)
if self._k_count < dp:
return
k = raw_k - 50.0
d = self._get_raw_k_average(dp) - 50.0
hl = float(self.high_level)
ll = float(self.low_level)
if k > d:
color = 3 if (k > hl or (k > ll and d <= ll)) else 2
else:
color = 0 if (k < ll or (d > hl and k <= hl)) else 1
if color == 3 and self._prev_color != 3 and self.Position <= 0:
self.BuyMarket()
elif color == 0 and self._prev_color != 0 and self.Position >= 0:
self.SellMarket()
elif color < 2 and self.Position > 0:
self.SellMarket()
elif color > 1 and self.Position < 0:
self.BuyMarket()
self._prev_color = color
def CreateClone(self):
return afl_winner_v2_strategy()