FatlSatlOsma 策略
该示例在 StockSharp 高级 API 中复现了 MetaTrader 专家 Exp_FatlSatlOsma 的逻辑。
原始系统使用 Fatl/Satl 振荡器(类似 MACD)。
- 当振荡器连续两根柱上升并且最新值高于前值时,开多并平空。
- 当振荡器连续两根柱下降并且最新值低于前值时,开空并平多。
振荡器通过内置的 MovingAverageConvergenceDivergenceSignal 指标实现。
默认参数对应原始的 FATL/SATL 设置。
细节
- 入场条件:振荡器加速。
- 方向:双向。
- 出场条件:反向加速。
- 止损:无。
- 默认值:
Fast= 39Slow= 65CandleType= 12 小时时间框
- 过滤器:
- 类别: 动量
- 方向: 双向
- 指标: MACD
- 止损: 无
- 复杂度: 基础
- 周期: 中期
- 季节性: 否
- 神经网络: 否
- 背离: 是
- 风险等级: 中等
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simple MACD momentum strategy inspired by FatlSatlOsma.
/// </summary>
public class FatlSatlOsmaStrategy : Strategy
{
private readonly StrategyParam<int> _fast;
private readonly StrategyParam<int> _slow;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prev1, _prev2;
private bool _init;
private int _barsSinceTrade;
public int Fast { get => _fast.Value; set => _fast.Value = value; }
public int Slow { get => _slow.Value; set => _slow.Value = value; }
public bool BuyOpen { get => _buyOpen.Value; set => _buyOpen.Value = value; }
public bool SellOpen { get => _sellOpen.Value; set => _sellOpen.Value = value; }
public bool BuyClose { get => _buyClose.Value; set => _buyClose.Value = value; }
public bool SellClose { get => _sellClose.Value; set => _sellClose.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public FatlSatlOsmaStrategy()
{
_fast = Param(nameof(Fast), 39).SetGreaterThanZero();
_slow = Param(nameof(Slow), 65).SetGreaterThanZero();
_buyOpen = Param(nameof(BuyOpen), true);
_sellOpen = Param(nameof(SellOpen), true);
_buyClose = Param(nameof(BuyClose), true);
_sellClose = Param(nameof(SellClose), true);
_cooldownBars = Param(nameof(CooldownBars), 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_prev1 = _prev2 = 0m;
_init = false;
_barsSinceTrade = CooldownBars;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = Fast },
LongMa = { Length = Slow }
},
SignalMa = { Length = 9 }
};
SubscribeCandles(CandleType)
.BindEx(macd, OnProcess)
.Start();
}
private void OnProcess(ICandleMessage candle, IIndicatorValue macdVal)
{
if (candle.State != CandleStates.Finished)
return;
var macdNullable = ((MovingAverageConvergenceDivergenceSignalValue)macdVal).Macd;
if (macdNullable is not decimal val)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
if (!_init)
{
_prev1 = _prev2 = val;
_init = true;
return;
}
if (_prev1 < _prev2)
{
if (_barsSinceTrade >= CooldownBars && BuyOpen && val > _prev1 && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
else if (_prev1 > _prev2)
{
if (_barsSinceTrade >= CooldownBars && SellOpen && val < _prev1 && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
_prev2 = _prev1;
_prev1 = val;
}
}
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.Indicators import MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class fatl_satl_osma_strategy(Strategy):
def __init__(self):
super(fatl_satl_osma_strategy, self).__init__()
self._fast = self.Param("Fast", 39) \
.SetDisplay("Fast", "Fast MA period", "Indicator")
self._slow = self.Param("Slow", 65) \
.SetDisplay("Slow", "Slow MA period", "Indicator")
self._buy_open = self.Param("BuyOpen", True) \
.SetDisplay("Buy Open", "Allow opening long positions", "Trading")
self._sell_open = self.Param("SellOpen", True) \
.SetDisplay("Sell Open", "Allow opening short positions", "Trading")
self._buy_close = self.Param("BuyClose", True) \
.SetDisplay("Buy Close", "Allow closing long positions", "Trading")
self._sell_close = self.Param("SellClose", True) \
.SetDisplay("Sell Close", "Allow closing short positions", "Trading")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev1 = 0.0
self._prev2 = 0.0
self._init = False
self._bars_since_trade = 0
@property
def Fast(self):
return self._fast.Value
@Fast.setter
def Fast(self, value):
self._fast.Value = value
@property
def Slow(self):
return self._slow.Value
@Slow.setter
def Slow(self, value):
self._slow.Value = value
@property
def BuyOpen(self):
return self._buy_open.Value
@BuyOpen.setter
def BuyOpen(self, value):
self._buy_open.Value = value
@property
def SellOpen(self):
return self._sell_open.Value
@SellOpen.setter
def SellOpen(self, value):
self._sell_open.Value = value
@property
def BuyClose(self):
return self._buy_close.Value
@BuyClose.setter
def BuyClose(self, value):
self._buy_close.Value = value
@property
def SellClose(self):
return self._sell_close.Value
@SellClose.setter
def SellClose(self, value):
self._sell_close.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(fatl_satl_osma_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self.Fast
macd.Macd.LongMa.Length = self.Slow
macd.SignalMa.Length = 9
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.BindEx(macd, self.ProcessCandle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, macd_val):
if candle.State != CandleStates.Finished:
return
macd_raw = macd_val.Macd
if macd_raw is None:
return
val = float(macd_raw)
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
if not self._init:
self._prev1 = val
self._prev2 = val
self._init = True
return
if self._prev1 < self._prev2:
if (self._bars_since_trade >= self.CooldownBars
and self.BuyOpen and val > self._prev1
and self.Position <= 0):
self.BuyMarket(self.Volume + abs(self.Position))
self._bars_since_trade = 0
elif self._prev1 > self._prev2:
if (self._bars_since_trade >= self.CooldownBars
and self.SellOpen and val < self._prev1
and self.Position >= 0):
self.SellMarket(self.Volume + abs(self.Position))
self._bars_since_trade = 0
self._prev2 = self._prev1
self._prev1 = val
def OnReseted(self):
super(fatl_satl_osma_strategy, self).OnReseted()
self._prev1 = 0.0
self._prev2 = 0.0
self._init = False
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return fatl_satl_osma_strategy()