Table of Contents

Volume in money

Note

Not released yet: the valuation API (IVolumeValuation, VolumeValueFactor) comes in the ATAS version after 8.0.15.

Volumes in ATAS are counted in the units of the instrument: contracts, lots, shares or coins. To show a volume in money (the traded value of a bar, a big trade of a million dollars, a volume filter set in euros), an indicator converts it with the valuation of the instrument that the platform provides. The platform values volumes with the same object, so your numbers match its own money values.

The valuation

InstrumentInfo.Valuation is an IVolumeValuation, or null when the platform cannot value the instrument:

  • InstrumentCurrency: the currency of the instrument's value (USDT for BTCUSDT, USD for ES, EUR for FDAX);
  • DisplayCurrency: the currency the user selected in the platform settings for money values; null means the instrument currency;
  • ReferencePrice: the last known price, for converting a threshold between money and volume;
  • TryGetFactor(currency, out factor): the factor that converts a volume into money of a currency, false when the instrument cannot be valued or no exchange rate to that currency is known.

The extension method TryGetDisplayFactor takes the display currency and falls back to the instrument currency when its exchange rate is unknown:

protected override void OnRecalculate()
{
    // The display currency of the platform settings, or the instrument currency when no rate to it is known
    _hasFactor = InstrumentInfo?.Valuation.TryGetDisplayFactor(out _factor) == true;
}

The factor

A VolumeValueFactor converts with one formula:

money = volume × Multiplier × (PriceDependent ? price : 1)
  • Linear contracts (spot, USDT-margined futures, stocks, CME futures) are PriceDependent: the Multiplier is the contract size (the value of one point) times the exchange rate, and the price of every trade matters.
  • Inverse contracts have a fixed money value per contract: the Multiplier alone gives the value and the price is ignored.

ToMoney(volume, price) and ToVolume(money, price) do the conversion both ways; Currency is the currency of the result. Do not compute the value from the tick size and the tick cost yourself: the contract size, inverse contracts and the exchange rates are already in the factor.

For a price-dependent factor, the exact value of a bar is the sum over its price levels. The volume of the bar times an average price is an approximation that is often good enough on a time chart and worse on bars with a wide range:

protected override void OnCalculate(int bar, decimal value)
{
    if (!_hasFactor)
        return;

    var candle = GetCandle(bar);

    if (!_factor.PriceDependent)
    {
        // Inverse contracts: every contract is worth a fixed amount whatever the price
        this[bar] = _factor.ToMoney(candle.Volume, 0);
        return;
    }

    // Linear contracts: the value depends on the price of every trade, so sum the price levels of the bar
    decimal money = 0;

    foreach (var level in candle.GetAllPriceLevels(_level))
        money += _factor.ToMoney(level.Volume, level.Price);

    this[bar] = money;
}

When the factor changes

The factor changes when an exchange rate moves, when the user selects another display currency and when the model of the instrument changes. The platform then calls OnValuationChanged, on any thread; recalculate there through DoActionInGuiThread, or just redraw when the values are converted only for display. An exception thrown there is written to the log. IVolumeValuation.Version grows with every change, which lets you cache a factor and check it cheaply.

protected override void OnValuationChanged()
{
    // An exchange rate, the display currency or the contract model changed; may come on any thread
    DoActionInGuiThread(RecalculateValues);
}

Example

The traded value of every bar as a histogram in a separate panel, in the currency of the platform settings:

using System.ComponentModel;

using ATAS.Indicators;

namespace ATAS.Docs.Samples.ChartIndicators.MarketData;

[DisplayName("Volume in money")]
public class MoneyVolumeIndicator : Indicator
{
    private readonly PriceVolumeInfo _level = new();
    private VolumeValueFactor _factor;
    private bool _hasFactor;

    public MoneyVolumeIndicator()
        : base(true)
    {
        Panel = IndicatorDataProvider.NewPanel;
        ((ValueDataSeries)DataSeries[0]).VisualType = VisualMode.Histogram;
    }

    protected override void OnRecalculate()
    {
        // The display currency of the platform settings, or the instrument currency when no rate to it is known
        _hasFactor = InstrumentInfo?.Valuation.TryGetDisplayFactor(out _factor) == true;
    }

    protected override void OnCalculate(int bar, decimal value)
    {
        if (!_hasFactor)
            return;

        var candle = GetCandle(bar);

        if (!_factor.PriceDependent)
        {
            // Inverse contracts: every contract is worth a fixed amount whatever the price
            this[bar] = _factor.ToMoney(candle.Volume, 0);
            return;
        }

        // Linear contracts: the value depends on the price of every trade, so sum the price levels of the bar
        decimal money = 0;

        foreach (var level in candle.GetAllPriceLevels(_level))
            money += _factor.ToMoney(level.Volume, level.Price);

        this[bar] = money;
    }

    protected override void OnValuationChanged()
    {
        // An exchange rate, the display currency or the contract model changed; may come on any thread
        DoActionInGuiThread(RecalculateValues);
    }
}