Your first strategy

Strategies

Your first strategy

A complete, working strategy: declare parameters and indicators, then go long or flat on a moving-average crossover.

SIMPLE Moving-average crossover

The example declares two moving averages whose periods come from parameters, goes long while the fast average is above the slow one, and exits to flat when the fast average crosses back below. It uses only the SDK packages, so it has two files: a small moving-average indicator and the strategy that declares it twice.

The IsFlat and IsLong guards make the logic edge-triggered: EnterLong fires only while flat and ExitLong only while long, so the strategy sends one order per crossover instead of one per bar. The parameter declarations, the DeclareIndicator factories and the OnBar body are each covered in the pages that follow.

MovingAverage.csusing SabrTrader.Pipeline.Indicators;
using SabrTrader.Pipeline.Plots;
using SabrTrader.Pipeline.Series;

[IndicatorDescription("Simple moving average of the close.")]
public sealed class MovingAverage : IndicatorBase
{
    private readonly ChunkedSeries<double> _values = new();

    [IndicatorParameter(DisplayName = "Period", MinValue = 1, MaxValue = 1000, Step = 1)]
    public int Period { get; set; } = 20;

    /// <summary>Latest value; NaN until Period bars exist.</summary>
    public double Value => _values.Count > 0 ? _values[0] : double.NaN;

    public MovingAverage(IIndicator? parent = null, ISeries<double>? input = null, int period = 20)
        : base(parent, input)
    {
        Period = period;
        AddPlot(new Plot("MA", _values, PlotStyle.Line, new ChartColor(33, 150, 243), 1.5));
    }

    public override void OnDataLoaded(IIndicatorContext ctx)
    {
        for (int barsAgo = Input.Count - 1; barsAgo >= 0; barsAgo--)
            _values.Append(Average(barsAgo));
    }

    public override void OnBarUpdate(IIndicatorContext ctx) => _values.Append(Average(0));

    private double Average(int barsAgo)
    {
        if (Input.Count - barsAgo < Period) return double.NaN;
        double sum = 0;
        for (int k = 0; k < Period; k++) sum += Input[barsAgo + k];
        return sum / Period;
    }
}
MaCrossStrategy.csusing SabrTrader.Pipeline.Strategies;
using SabrTrader.Pipeline.Strategies.Catalog;

[StrategyMetadata(
    DisplayName = "MA Cross",
    Description = "Long while a fast moving average is above a slow one; flat when it crosses back below.")]
public sealed class MaCrossStrategy : Strategy
{
    private StrategyParameter<int> _fastPeriod = null!;
    private StrategyParameter<int> _slowPeriod = null!;
    private StrategyParameter<int> _quantity   = null!;
    private StrategyIndicator<MovingAverage> _fast = null!;
    private StrategyIndicator<MovingAverage> _slow = null!;

    protected override void OnInitialize()
    {
        // (name, default, min, max, step): the optimizable form.
        _fastPeriod = IntParameter("FastPeriod", 10, 2, 100, 1);
        _slowPeriod = IntParameter("SlowPeriod", 30, 5, 400, 1);
        _quantity   = IntParameter("Quantity",   1, 1, 100, 1);

        // The factories run after parameter overrides, so each period is this run's value.
        _fast = DeclareIndicator(() => new MovingAverage { Period = _fastPeriod.Value });
        _slow = DeclareIndicator(() => new MovingAverage { Period = _slowPeriod.Value });
    }

    protected override void OnBar()
    {
        double fast = _fast.Instance.Value;
        double slow = _slow.Instance.Value;
        if (double.IsNaN(fast) || double.IsNaN(slow)) return;   // still warming up

        if (fast > slow && IsFlat) EnterLong(_quantity.Value);
        else if (fast < slow && IsLong) ExitLong();
    }
}
Drop it in. Compile both files into your plugin DLL. The strategy appears in the strategy catalog and the indicator in the indicator list, with no registration code. See Discovery & metadata.

Anatomy of the example

Three pieces, each covered in depth later in the chapter:

Parameters

The IntParameter(name, default, min, max, step) overload declares an optimizable input. Hold the returned handle and read .Value. More →

Indicators

DeclareIndicator(factory) feeds an indicator every bar the strategy sees. Read it through .Instance from OnStart onward. More →

Managed intent

EnterLong and ExitLong state the position you want; the framework computes the order and handles reversals. More →