自定义指标
要创建您自己的指标,您需要实现 IIndicator 接口。作为示例,您可以查看位于 GitHub/StockSharp 仓库中的其他指标源代码。下面是简单移动平均 SimpleMovingAverage 的实现示例:
/// <summary>
/// 简单移动平均。
/// </summary>
[DisplayName("SMA")]
[Description("简单移动平均线。")]
public class SimpleMovingAverage : LengthIndicator<decimal>
{
/// <summary>
/// 创建 <see cref="SimpleMovingAverage"/>。
/// </summary>
public SimpleMovingAverage()
{
Length = 32;
}
/// <summary>
/// 处理输入值。
/// </summary>
/// <param name="input">输入值。</param>
/// <returns>结果值。</returns>
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var newValue = input.GetValue<decimal>();
if (input.IsFinal)
{
Buffer.Add(newValue);
if (Buffer.Count > Length)
Buffer.RemoveAt(0);
}
if (input.IsFinal)
return new DecimalIndicatorValue(this, Buffer.Sum() / Length);
return new DecimalIndicatorValue(this, (Buffer.Skip(1).Sum() + newValue) / Length);
}
}
SimpleMovingAverage 继承自 LengthIndicator<TResult>,所有具有周期长度参数的指标都必须继承自它。
重要指标属性和方法
在创建自定义指标时,应该特别注意以下属性和方法:
要初始化的值数量
NumValuesToInitialize 属性表示指标初始化(形成或“预热”)所需的值数量。该值用于确定何时可以认为指标已形成并可以使用:
/// <inheritdoc />
public override int NumValuesToInitialize => Length;
对于由多个组成部分构成的更复杂的指标,该值通常确定为所有组成部分的最大值:
/// <inheritdoc />
public override int NumValuesToInitialize => _shortEma.NumValuesToInitialize.Max(_longEma.NumValuesToInitialize);
测量
Measure 属性定义了指标提供的测量类型和维度:
/// <inheritdoc />
public override IndicatorMeasures Measure => IndicatorMeasures.Percent;
可用的测量类型:
IndicatorMeasures.Price- 指标衡量价格(例如, 移动平均线)IndicatorMeasures.Percent- 指标使用从 0 到 100 的百分比刻度 (例如, RSI)IndicatorMeasures.MinusOnePlusOne- 指标使用从 -1 到 +1 的刻度IndicatorMeasures.Volume- 指标用于测量交易量 (例如, OBV)
该属性对于在图表上正确显示指标至关重要。当在同一面板上叠加具有不同维度的多个指标时,会为具有不同 Measure 类型的指标创建独立的 Y 轴。这可以让所有指标以其自然的比例进行可视化显示,即使一个指标的数值以千为单位(e.g,价格),而另一个指标以单位的小数部分为单位(e.g,振荡器)。
保存与加载
/// <inheritdoc />
public override void Save(SettingsStorage storage)
{
base.Save(storage);
storage.SetValue(nameof(Short周期), Short周期);
storage.SetValue(nameof(Long周期), Long周期);
}
/// <inheritdoc />
public override void Load(SettingsStorage storage)
{
base.Load(storage);
Short周期 = storage.GetValue<int>(nameof(Short周期));
Long周期 = storage.GetValue<int>(nameof(Long周期));
}
综合指标
有些指标是复合指标,并在其计算中使用其他指标。因此,指标可以相互重用,如Chaikin波动率指标 Chaikin波动率 的示例实现中所示:
/// <summary>
/// Chaikin 波动率。
/// </summary>
[DisplayName("波动率")]
[Description("Chaikin 波动率。")]
public class Chaikin波动率 : BaseIndicator<IIndicatorValue>
{
/// <summary>
/// 创建 <see cref="Chaikin波动率"/>。
/// </summary>
public Chaikin波动率()
{
Ema = new ExponentialMovingAverage();
Roc = new RateOfChange();
}
/// <summary>
/// 移动平均。
/// </summary>
[ExpandableObject]
[DisplayName("MA")]
[Description("移动平均。")]
[Category("主要")]
public ExponentialMovingAverage Ema { get; private set; }
/// <summary>
/// 变化率。
/// </summary>
[ExpandableObject]
[DisplayName("ROC")]
[Description("变动率。")]
[Category("主要")]
public RateOfChange Roc { get; private set; }
/// <summary>
/// 指标是否已形成。
/// </summary>
public override bool IsFormed
{
get { return Roc.IsFormed; }
}
/// <summary>
/// 处理输入值。
/// </summary>
/// <param name="input">输入值。</param>
/// <returns>结果值。</returns>
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var candle = input.GetValue<Candle>();
var emaValue = Ema.Process(input.SetValue(this, candle.HighPrice - candle.LowPrice));
if (Ema.IsFormed)
{
return Roc.Process(emaValue);
}
return input;
}
}
多线指标
最后一种指标是那些不仅由其他指标组成,而且还可以同时以多种状态(多条线)图形显示的指标。例如,平均趋向指数:
/// <summary>
/// Welles Wilder 平均趋向指数。
/// </summary>
[DisplayName("ADX")]
[Description("Welles Wilder 平均趋向指数。")]
public class AverageDirectionalIndex : BaseComplexIndicator
{
/// <summary>
/// 创建 <see cref="AverageDirectionalIndex"/>。
/// </summary>
public AverageDirectionalIndex()
: this(new DirectionalIndex { Length = 14 }, new WilderMovingAverage { Length = 14 })
{
}
/// <summary>
/// 创建 <see cref="AverageDirectionalIndex"/>。
/// </summary>
/// <param name="dx">Welles Wilder's Directional Movement Index.</param>
/// <param name="movingAverage">移动平均。</param>
public AverageDirectionalIndex(DirectionalIndex dx, LengthIndicator<decimal> movingAverage)
{
if (dx == null)
throw new ArgumentNullException(nameof(dx));
if (movingAverage == null)
throw new ArgumentNullException(nameof(movingAverage));
InnerIndicators.Add(Dx = dx);
InnerIndicators.Add(MovingAverage = movingAverage);
Mode = ComplexIndicatorModes.Sequence;
}
/// <summary>
/// Welles Wilder 趋向运动指数。
/// </summary>
[Browsable(false)]
public DirectionalIndex Dx { get; private set; }
/// <summary>
/// 移动平均。
/// </summary>
[Browsable(false)]
public LengthIndicator<decimal> MovingAverage { get; private set; }
/// <summary>
/// 周期长度。
/// </summary>
[DisplayName("周期")]
[Description("指标周期。")]
[Category("主要")]
public virtual int Length
{
get { return MovingAverage.Length; }
set
{
MovingAverage.Length = Dx.Length = value;
Reset();
}
}
}
此类指标应继承自 BaseComplexIndicator 类,并将指标的组件传递给 BaseComplexIndicator.InnerIndicators。此外,每个复杂指标必须声明其自己派生自 ComplexIndicatorValue 的值类型。
具有保存加载实现的复杂指标示例
以下是实现百分比成交量振荡器(PVO)的示例,演示了 NumValuesToInitialize、Measure、Save 和 Load 方法的实现:
/// <summary>
/// Percentage Volume Oscillator (PVO).
/// </summary>
[Display(
ResourceType = typeof(LocalizedStrings),
Name = LocalizedStrings.PVOKey,
Description = LocalizedStrings.PercentageVolumeOscillatorKey)]
[IndicatorIn(typeof(CandleIndicatorValue))]
[Doc("topics/api/indicators/list_of_indicators/percentage_volume_oscillator.html")]
[IndicatorOut(typeof(PercentageVolumeOscillatorValue))]
public class PercentageVolumeOscillator : BaseComplexIndicator<PercentageVolumeOscillatorValue>
{
private readonly ExponentialMovingAverage _shortEma;
private readonly ExponentialMovingAverage _longEma;
/// <summary>
/// Initializes a new instance of the <see cref="PercentageVolumeOscillator"/>.
/// </summary>
public PercentageVolumeOscillator()
: this(new(), new())
{
Short周期 = 12;
Long周期 = 26;
}
/// <summary>
/// Initializes a new instance of the <see cref="PercentageVolumeOscillator"/>.
/// </summary>
/// <param name="shortEma">The short-term EMA.</param>
/// <param name="longEma">The long-term EMA.</param>
public PercentageVolumeOscillator(ExponentialMovingAverage shortEma, ExponentialMovingAverage longEma)
: base(shortEma, longEma)
{
_shortEma = shortEma;
_longEma = longEma;
}
/// <summary>
/// 短周期。
/// </summary>
[Display(
ResourceType = typeof(LocalizedStrings),
Name = LocalizedStrings.Short周期Key,
Description = LocalizedStrings.ShortMaDescKey,
GroupName = LocalizedStrings.GeneralKey)]
public int Short周期
{
get => _shortEma.Length;
set => _shortEma.Length = value;
}
/// <summary>
/// 长周期。
/// </summary>
[Display(
ResourceType = typeof(LocalizedStrings),
Name = LocalizedStrings.Long周期Key,
Description = LocalizedStrings.LongMaDescKey,
GroupName = LocalizedStrings.GeneralKey)]
public int Long周期
{
get => _longEma.Length;
set => _longEma.Length = value;
}
/// <inheritdoc />
public override IndicatorMeasures Measure => IndicatorMeasures.Volume;
/// <inheritdoc />
public override int NumValuesToInitialize => _shortEma.NumValuesToInitialize.Max(_longEma.NumValuesToInitialize);
/// <inheritdoc />
protected override bool CalcIsFormed() => _shortEma.IsFormed && _longEma.IsFormed;
/// <inheritdoc />
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var volume = input.ToCandle().TotalVolume;
var result = new PercentageVolumeOscillatorValue(this, input.Time);
var shortValue = _shortEma.Process(input, volume);
var longValue = _longEma.Process(input, volume);
result.Add(_shortEma, shortValue);
result.Add(_longEma, longValue);
if (_longEma.IsFormed)
{
var den = longValue.ToDecimal();
var pvo = den == 0 ? 0 : ((shortValue.ToDecimal() - den) / den) * 100;
result.Add(this, new DecimalIndicatorValue(this, pvo, input.Time));
}
return result;
}
/// <inheritdoc />
public override void Save(SettingsStorage storage)
{
base.Save(storage);
storage.SetValue(nameof(Short周期), Short周期);
storage.SetValue(nameof(Long周期), Long周期);
}
/// <inheritdoc />
public override void Load(SettingsStorage storage)
{
base.Load(storage);
Short周期 = storage.GetValue<int>(nameof(Short周期));
Long周期 = storage.GetValue<int>(nameof(Long周期));
}
/// <inheritdoc />
public override string ToString() => base.ToString() + $" S={Short周期},L={Long周期}";
/// <inheritdoc />
protected override PercentageVolumeOscillatorValue CreateValue(DateTimeOffset time)
=> new(this, time);
}
/// <summary>
/// <see cref="PercentageVolumeOscillator"/> indicator value.
/// </summary>
public class PercentageVolumeOscillatorValue : ComplexIndicatorValue<PercentageVolumeOscillator>
{
/// <summary>
/// Initializes a new instance of the <see cref="PercentageVolumeOscillatorValue"/> class.
/// </summary>
/// <param name="indicator">Indicator.</param>
/// <param name="time">Value time.</param>
public PercentageVolumeOscillatorValue(PercentageVolumeOscillator indicator, DateTimeOffset time)
: base(indicator, time)
{
}
}
这个例子演示了:
- 复杂指标的
NumValuesToInitialize实施 - 通过
Measure属性指定测量类型 - 为复合指标实现专用值类型
Save和Load方法用于保存和加载参数的正确实现- 覆盖
ToString()以便于指示器配置的显示