Exp MAMA 策略
该策略基于 MESA 自适应移动平均线 (MAMA) 指标进行交易。
指标包含两条线:
- MAMA – 自适应移动平均线。
- FAMA – 用作信号线的跟随平均线。
交易逻辑:
- 当 MAMA 向下穿越 FAMA 时,策略平掉空头并开多。
- 当 MAMA 向上穿越 FAMA 时,策略平掉多头并开空。
参数
FastLimit– 自适应系数的上限。SlowLimit– 自适应系数的下限。CandleType– 使用的蜡烛图时间框架。BuyOpen/SellOpen– 允许开多或开空。BuyClose/SellClose– 允许平多或平空。
该策略只处理已完成的蜡烛,并使用市价单进行进出场。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on MESA Adaptive Moving Average (MAMA/FAMA) crossing.
/// Buys when MAMA crosses above FAMA and sells on opposite crossing.
/// </summary>
public class ExpMamaStrategy : Strategy
{
private readonly StrategyParam<decimal> _fastLimit;
private readonly StrategyParam<decimal> _slowLimit;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevMama;
private decimal? _prevFama;
// MAMA calculator state
private decimal _p1, _p2, _p3;
private decimal _s1, _s2, _s3;
private decimal _d1, _d2, _d3;
private decimal _q1v, _q2v, _q3v;
private decimal _i1v, _i2v, _i3v;
private decimal _i21, _q21;
private decimal _re1, _im1;
private decimal _phase1;
private decimal _period;
private decimal? _mamaVal;
private decimal? _famaVal;
private int _count;
public decimal FastLimit { get => _fastLimit.Value; set => _fastLimit.Value = value; }
public decimal SlowLimit { get => _slowLimit.Value; set => _slowLimit.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ExpMamaStrategy()
{
_fastLimit = Param(nameof(FastLimit), 0.5m)
.SetDisplay("Fast Limit", "Fast alpha limit", "Indicators");
_slowLimit = Param(nameof(SlowLimit), 0.05m)
.SetDisplay("Slow Limit", "Slow alpha limit", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMama = null;
_prevFama = null;
_p1 = _p2 = _p3 = 0;
_s1 = _s2 = _s3 = 0;
_d1 = _d2 = _d3 = 0;
_q1v = _q2v = _q3v = 0;
_i1v = _i2v = _i3v = 0;
_i21 = _q21 = 0;
_re1 = _im1 = 0;
_phase1 = 0;
_period = 0;
_mamaVal = null;
_famaVal = null;
_count = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
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;
var price = candle.ClosePrice;
var result = CalcMama(price, FastLimit, SlowLimit);
if (result is null)
return;
var (mama, fama) = result.Value;
if (_prevMama.HasValue && _prevFama.HasValue)
{
var wasAbove = _prevMama > _prevFama;
var isAbove = mama > fama;
// MAMA crosses below FAMA -> Buy signal
if (wasAbove && !isAbove && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// MAMA crosses above FAMA -> Sell signal
else if (!wasAbove && isAbove && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
_prevMama = mama;
_prevFama = fama;
}
private (decimal mama, decimal fama)? CalcMama(decimal price, decimal fast, decimal slow)
{
_count++;
var c0 = 0.0962m;
var c1 = 0.5769m;
var c2 = -0.5769m;
var c3 = -0.0962m;
var smooth = (4m * price + 3m * _p1 + 2m * _p2 + _p3) / 10m;
var detrender = c0 * smooth + c1 * _s1 + c2 * _s2 + c3 * _s3;
var q1 = c0 * detrender + c1 * _d1 + c2 * _d2 + c3 * _d3;
var i1 = _d1;
var jI = c0 * i1 + c1 * _i1v + c2 * _i2v + c3 * _i3v;
var jQ = c0 * q1 + c1 * _q1v + c2 * _q2v + c3 * _q3v;
var i2 = i1 - jQ;
var q2 = q1 + jI;
i2 = 0.2m * i2 + 0.8m * _i21;
q2 = 0.2m * q2 + 0.8m * _q21;
var re = i2 * _i21 + q2 * _q21;
var im = i2 * _q21 - q2 * _i21;
re = 0.2m * re + 0.8m * _re1;
im = 0.2m * im + 0.8m * _im1;
var period = _period;
if (re != 0m && im != 0m)
{
var ang = (decimal)Math.Atan((double)(im / re));
if (ang != 0m)
period = 2m * (decimal)Math.PI / ang;
}
period = Math.Min(Math.Max(period, 6m), 50m);
var phase = 0m;
if (i1 != 0m)
phase = (decimal)Math.Atan((double)(q1 / i1));
var delta = phase - _phase1;
if (delta < 1m)
delta = 1m;
if (delta > 1.5m)
delta = 1.5m;
var alpha = fast / delta;
if (alpha < slow)
alpha = slow;
var mama = _mamaVal is null ? price : alpha * price + (1m - alpha) * _mamaVal.Value;
var fama = _famaVal is null ? price : 0.5m * alpha * mama + (1m - 0.5m * alpha) * _famaVal.Value;
_p3 = _p2; _p2 = _p1; _p1 = price;
_s3 = _s2; _s2 = _s1; _s1 = smooth;
_d3 = _d2; _d2 = _d1; _d1 = detrender;
_q3v = _q2v; _q2v = _q1v; _q1v = q1;
_i3v = _i2v; _i2v = _i1v; _i1v = i1;
_i21 = i2; _q21 = q2;
_re1 = re; _im1 = im;
_phase1 = phase;
_period = period;
_mamaVal = mama;
_famaVal = fama;
if (_count < 7)
return null;
return (mama, fama);
}
}
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
import math
class exp_mama_strategy(Strategy):
def __init__(self):
super(exp_mama_strategy, self).__init__()
self._fast_limit = self.Param("FastLimit", 0.5) \
.SetDisplay("Fast Limit", "Fast alpha limit", "Indicators")
self._slow_limit = self.Param("SlowLimit", 0.05) \
.SetDisplay("Slow Limit", "Slow alpha limit", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._prev_mama = None
self._prev_fama = None
# MAMA calculator state
self._p1 = 0.0; self._p2 = 0.0; self._p3 = 0.0
self._s1 = 0.0; self._s2 = 0.0; self._s3 = 0.0
self._d1 = 0.0; self._d2 = 0.0; self._d3 = 0.0
self._q1v = 0.0; self._q2v = 0.0; self._q3v = 0.0
self._i1v = 0.0; self._i2v = 0.0; self._i3v = 0.0
self._i21 = 0.0; self._q21 = 0.0
self._re1 = 0.0; self._im1 = 0.0
self._phase1 = 0.0; self._period = 0.0
self._mama_val = None; self._fama_val = None
self._count = 0
@property
def fast_limit(self):
return self._fast_limit.Value
@property
def slow_limit(self):
return self._slow_limit.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_mama_strategy, self).OnReseted()
self._prev_mama = None
self._prev_fama = None
self._p1 = 0.0; self._p2 = 0.0; self._p3 = 0.0
self._s1 = 0.0; self._s2 = 0.0; self._s3 = 0.0
self._d1 = 0.0; self._d2 = 0.0; self._d3 = 0.0
self._q1v = 0.0; self._q2v = 0.0; self._q3v = 0.0
self._i1v = 0.0; self._i2v = 0.0; self._i3v = 0.0
self._i21 = 0.0; self._q21 = 0.0
self._re1 = 0.0; self._im1 = 0.0
self._phase1 = 0.0; self._period = 0.0
self._mama_val = None; self._fama_val = None
self._count = 0
def OnStarted2(self, time):
super(exp_mama_strategy, self).OnStarted2(time)
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 process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
result = self._calc_mama(price, float(self.fast_limit), float(self.slow_limit))
if result is None:
return
mama, fama = result
if self._prev_mama is not None and self._prev_fama is not None:
was_above = self._prev_mama > self._prev_fama
is_above = mama > fama
if was_above and not is_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif not was_above and is_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_mama = mama
self._prev_fama = fama
def _calc_mama(self, price, fast, slow):
self._count += 1
c0 = 0.0962; c1 = 0.5769; c2 = -0.5769; c3 = -0.0962
smooth = (4.0 * price + 3.0 * self._p1 + 2.0 * self._p2 + self._p3) / 10.0
detrender = c0 * smooth + c1 * self._s1 + c2 * self._s2 + c3 * self._s3
q1 = c0 * detrender + c1 * self._d1 + c2 * self._d2 + c3 * self._d3
i1 = self._d1
jI = c0 * i1 + c1 * self._i1v + c2 * self._i2v + c3 * self._i3v
jQ = c0 * q1 + c1 * self._q1v + c2 * self._q2v + c3 * self._q3v
i2 = i1 - jQ
q2 = q1 + jI
i2 = 0.2 * i2 + 0.8 * self._i21
q2 = 0.2 * q2 + 0.8 * self._q21
re = i2 * self._i21 + q2 * self._q21
im = i2 * self._q21 - q2 * self._i21
re = 0.2 * re + 0.8 * self._re1
im = 0.2 * im + 0.8 * self._im1
period = self._period
if re != 0.0 and im != 0.0:
ang = math.atan(im / re)
if ang != 0.0:
period = 2.0 * math.pi / ang
period = min(max(period, 6.0), 50.0)
phase = 0.0
if i1 != 0.0:
phase = math.atan(q1 / i1)
delta = phase - self._phase1
if delta < 1.0:
delta = 1.0
if delta > 1.5:
delta = 1.5
alpha = fast / delta
if alpha < slow:
alpha = slow
mama = price if self._mama_val is None else alpha * price + (1.0 - alpha) * self._mama_val
fama = price if self._fama_val is None else 0.5 * alpha * mama + (1.0 - 0.5 * alpha) * self._fama_val
self._p3 = self._p2; self._p2 = self._p1; self._p1 = price
self._s3 = self._s2; self._s2 = self._s1; self._s1 = smooth
self._d3 = self._d2; self._d2 = self._d1; self._d1 = detrender
self._q3v = self._q2v; self._q2v = self._q1v; self._q1v = q1
self._i3v = self._i2v; self._i2v = self._i1v; self._i1v = i1
self._i21 = i2; self._q21 = q2
self._re1 = re; self._im1 = im
self._phase1 = phase; self._period = period
self._mama_val = mama; self._fama_val = fama
if self._count < 7:
return None
return (mama, fama)
def CreateClone(self):
return exp_mama_strategy()