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;nullmeans 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,falsewhen 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: theMultiplieris 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
Multiplieralone 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);
}
}
Related articles
- Candles and footprint: the price levels of a bar.
- Performance: iterating the price levels without allocations.