Skip to content

Latest commit

 

History

History
227 lines (165 loc) · 7.26 KB

File metadata and controls

227 lines (165 loc) · 7.26 KB

Integration Guides

QuanTAlib is platform-agnostic. Any .NET environment that can reference a DLL can use it. The complexity lies not in the library but in understanding each platform's quirks.

Quantower

Quantower accepts custom indicators written in C#. Integration follows a wrapper pattern.

Setup

  1. Build QuanTAlib or grab the NuGet package
  2. Add reference to QuanTAlib.dll in the Quantower indicator project
  3. Create wrapper class inheriting from Indicator

Example: SMA Indicator

using Quantower.API.Indicators;
using QuanTAlib;

public class MySmaIndicator : Indicator
{
    private Sma _sma;

    [InputParameter("Period", 10, 1000, 1, 0)]
    public int Period = 14;

    public override void OnInit()
    {
        _sma = new Sma(Period);
        AddLineSeries("SMA", Color.Yellow, LineStyle.Solid, 2);
    }

    public override void OnUpdate(UpdateArgs args)
    {
        double price = ClosePrice;

        // Quantower handles bar lifecycle; check UpdateReason
        bool isNew = args.Reason == UpdateReason.NewBar;
        var result = _sma.Update(new TValue(DateTime.UtcNow, price), isNew);

        SetValue(result.Value);
    }
}

Available Quantower Bundles

Pre-built adapters exist for common indicators:

Bundle Indicators Notes
Trends (IIR) HemaIndicator, ZlemaIndicator, EmaIndicator, etc. Exponential family
Trends (FIR) SmaIndicator, WmaIndicator, HmaIndicator, etc. Finite response family
Volatility AtrIndicator, AdrIndicator Range-based volatility
Dynamics AdxIndicator, SuperTrendIndicator Trend strength

Quantower Gotchas

UpdateReason matters. Quantower calls OnUpdate for both new bars and intra-bar ticks. The args.Reason check determines isNew flag behavior. Getting this wrong causes state corruption that manifests as mysteriously wrong indicator values.

Historical data loads first. Quantower calls OnUpdate repeatedly during historical load before live data arrives. The indicator warms up during this phase.

NinjaTrader 8

NinjaTrader 8 runs on .NET Framework 4.8. QuanTAlib targets .NET Standard, enabling interop.

Setup

  1. Copy QuanTAlib.dll to Documents\NinjaTrader 8\bin\Custom
  2. In NinjaScript Editor: right-click ’ References ’ Add QuanTAlib.dll

Example: SMA Indicator

private QuanTAlib.Sma _sma;

[Range(1, int.MaxValue)]
[NinjaScriptProperty]
public int Period { get; set; } = 14;

protected override void OnStateChange()
{
    if (State == State.SetDefaults)
    {
        Name = "QuanTAlib SMA";
        Calculate = Calculate.OnBarClose;
    }
    else if (State == State.DataLoaded)
    {
        _sma = new QuanTAlib.Sma(Period);
    }
}

protected override void OnBarUpdate()
{
    // isNew depends on Calculate mode
    // OnBarClose: every call is a new bar
    // OnEachTick: use IsFirstTickOfBar
    bool isNew = Calculate == Calculate.OnBarClose || IsFirstTickOfBar;

    var result = _sma.Update(new TValue(Time[0], Close[0]), isNew);
    Value[0] = result.Value;
}

NinjaTrader Gotchas

Calculate mode affects isNew logic. With Calculate.OnBarClose, every OnBarUpdate call represents a completed bar. With Calculate.OnEachTick, only the first tick of each bar should use isNew = true. Mixing these concepts produces indicators that work in backtest but fail live.

Historical vs real-time. NinjaTrader processes historical bars differently from real-time bars. The State property indicates the current phase. Indicator warmup should complete during historical processing.

QuantConnect (LEAN)

LEAN supports custom libraries through NuGet integration.

Setup

  1. Add QuanTAlib to project dependencies
  2. Instantiate indicators in Initialize()
  3. Update in OnData()

Example: Algorithm with SMA

public class MyAlgorithm : QCAlgorithm
{
    private Sma _mySma;
    private Symbol _symbol;

    public override void Initialize()
    {
        SetStartDate(2020, 1, 1);
        SetEndDate(2023, 12, 31);
        SetCash(100000);

        _symbol = AddEquity("SPY", Resolution.Daily).Symbol;
        _mySma = new Sma(14);
    }

    public override void OnData(Slice data)
    {
        if (!data.Bars.ContainsKey(_symbol))
            return;

        var bar = data.Bars[_symbol];
        var result = _mySma.Update(new TValue(bar.EndTime, (double)bar.Close));

        if (_mySma.IsHot)
        {
            Plot("Indicators", "SMA", result.Value);

            // Trading logic here
            if (!Portfolio[_symbol].Invested && result.Value < (double)bar.Close)
            {
                SetHoldings(_symbol, 0.5);
            }
        }
    }
}

LEAN Gotchas

Decimal to double conversion. LEAN uses decimal for prices; QuanTAlib uses double. Cast on input, cast back on output if needed. The precision difference rarely matters for indicator calculations.

Resolution affects bar timing. Daily bars have different EndTime semantics than minute bars. UTC timestamps prevent timezone confusion.

Custom Platform Integration

For proprietary trading engines, Streaming Mode fits most use cases.

Integration Checklist

Consideration Requirement Consequence of Ignoring
Time handling UTC timestamps Timezone bugs in historical analysis
Numeric precision double input/output Cast from/to decimal if platform uses it
State persistence One instance per symbol Recreating indicators loses warmup state
Thread safety Separate instances per thread Concurrent access corrupts internal state
Bar correction Proper isNew flag usage State accumulation errors

Minimal Integration Pattern

public class MyTradingEngine
{
    // One indicator instance per symbol, persisted for session lifetime
    private readonly Dictionary<string, Sma> _indicators = new();

    public void OnSymbolAdded(string symbol, int smaPeriod)
    {
        _indicators[symbol] = new Sma(smaPeriod);
    }

    public void OnTick(string symbol, DateTime time, double price, bool isNewBar)
    {
        if (!_indicators.TryGetValue(symbol, out var sma))
            return;

        var result = sma.Update(new TValue(time, price), isNewBar);

        if (sma.IsHot)
        {
            // Use result.Value for trading logic
            ProcessSignal(symbol, result.Value, price);
        }
    }

    public void OnSymbolRemoved(string symbol)
    {
        _indicators.Remove(symbol);
    }
}

The isNew Flag: Getting It Right

The isNew flag determines whether Update() advances to the next bar or corrects the current one. Correct implementation depends on data source semantics:

Data Source Type isNew = true When isNew = false When
Bar-based feed New bar arrives Never (each bar final)
Tick-based, bar aggregation First tick after bar close Subsequent ticks within bar
Streaming with corrections Timestamp advances Same timestamp, updated price

Testing approach: Feed identical data through the indicator in streaming mode (tick by tick with correct isNew flags) and batch mode (complete series at once). Compare final values. Mismatch indicates isNew flag logic error.