Asset Classes

DocumentationMarkets

Asset Classes

Forex, indices, crypto and equities do not behave the same, and a run that applies forex assumptions to a stock produces a number that is wrong and looks fine. This page says how the engine decides what an instrument is, what it does per asset class, and what it does not do at all.

Read the Not implemented section before you trust an index, crypto or equity result.

How an instrument is described

The app carries a table of instrument specifications: one row for each listed instrument, plus a default row for each asset class. Every money figure in a run comes out of it.

Three numbers describe an instrument:

  • Contract size, units of the instrument per one lot
  • Pip size, the price increment a stop distance, a target and the spread are quoted in
  • Tick size, the smallest price move the instrument makes. It is carried on the instrument and read by nothing in the money model today

A row also carries the instrument's quote currency, which names the currency the result is reported in. It is a label, not a conversion.

Pip is a forex word. It stays because the strategy surface, the stops and the spread are all written in pips. What it means depends on the class: a pip for forex, a point for an index, a cent for a share, one unit of the quote currency for a coin.

This is why an unknown symbol is refused rather than guessed at. Priced as a currency pair, AAPL at 190 would get a four decimal pip and a hundred thousand unit lot, which is 1.9 million of notional on a 10,000 account, and the run would report that as a result.

The six classes

ClassContract size per lotPip sizeTick size
forex100,0000.0001, or 0.01 quoted against JPYa tenth of the pip
metal1000.010.01
index10.10.1
crypto11.00.01
equity10.010.01
cfd10.010.01

Those are the class defaults. A listed instrument carries its own row and overrides them. Every instrument in OrionQuant's archive has one, with Dukascopy's contract size and price digits; among them:

  • XAUUSD is 100 ounces with a pip of 0.01, XAGUSD is 5,000 ounces with a pip of 0.001
  • US30, JP225 and HK50 use a pip of 1.0; the other listed indices use 0.1
  • WTIUSD and BCOUSD are 1,000 barrels with a pip of 0.01, NGCUSD is 10,000 units with a pip of 0.001
  • the archive's crude oil, LIGHT.CMD/USD and BRENT.CMD/USD, is 100 barrels with a pip of 0.01; GAS.CMD/USD is 100 MMBtu and COPPER.CMD/USD 100 pounds, each with a pip of 0.001; DIESEL.CMD/USD is 4 tonnes
  • an archive share such as AAPL.US/USD is one share with a pip of 0.01

There is no options class and no futures class. Nothing in the engine models an expiry or a roll.

How the engine places a symbol

In order, and the first answer wins:

  1. A custom symbol saved for that ticker under Data, Instruments.
  2. The per symbol table: every instrument in OrionQuant's archive under its code (see Symbol codes below), which includes the two metals and the cash indices, and three energy contracts the archive does not hold (WTIUSD, BCOUSD, NGCUSD).
  3. The shape of the symbol, which reads exactly six letters as three and three: two of twenty four ISO currency codes are a forex pair, and a three letter coin code against USD, EUR, GBP, BTC or ETH is a crypto pair. BTCUSD and ETHBTC place themselves this way; BTCUSDT and AVAXUSD do not, because the quote or the code is longer than three letters. Six of the twenty listed coin codes (DOGE, AVAX, LINK, MATIC, ATOM, NEAR) cannot be reached by shape at all.
  4. The class the run declared in assetClass, which supplies that class's default row.

The declared class is last rather than first because an unambiguous shape is better evidence than a field somebody set once. EURUSD is a currency pair whatever the configuration says.

When a symbol is refused

A symbol none of those four steps can place stops the run before it starts, and says what to add:

AAPL is not an instrument this engine knows how to price. It is not a
currency pair, and there is no entry for it and no custom symbol. Define
it under Data, Instruments with its contract size and its price
increment, set the asset class on the run, or use one of the instruments
in the catalog. Running it as a forex pair would produce a result that
looks fine and is wrong.

There is no fallback and no default class. Being stopped is the point. A stock priced as a currency pair does not fail: it finishes, and hands you a number that is wrong.

Declaring a class on a run

assetClass on the run configuration takes one of the six names above. It is consulted only for a symbol nothing else can place, and it hands the run that class's default contract size and pip size. The app fills it in from the instrument catalog, and sends nothing for the forex pairs and the metals, which the engine places on its own.

Declaring a class is not the same as defining a custom symbol. A class gives you the defaults for the class. A custom symbol gives you your broker's actual contract, and it wins over everything else.

What the engine reads from a symbol spec

The instrument editor under Data, Instruments has more fields than a run uses. These two reach the money model:

FieldUsed for
Pip sizeConverting stop and target distances, spread and swap into price
Contract sizeTurning lots into position value

Quote currency reaches the result but not the arithmetic: it is the currency the figures are reported in, and the run log warns when it differs from the account currency. No number changes.

A spec may also carry an asset class, which is what the result reports for the run. The editor has no field for it, so a symbol saved there runs as cfd, the class that claims the least: priced one for one against something else, with no dividends, no expiry and no trading calendar.

The remaining fields, digits, tick size, tick value, margin percent, per symbol swap, per symbol commission, minimum and maximum lot, lot step, trading hours, expiry date, exchange, settlement type and base currency, are stored but not read by the engine today. Setting them does not change a result.

Commission and swap come from the run configuration instead of from the symbol. The spread comes from the bid and the ask in the data, and from the custom symbol's average spread where the data has one price. See Costs and Fills.

Per class notes

Forex. The case the engine was built for. Pip and contract size are placed by the symbol's shape, fills read the bid and the ask the data recorded on every tier, swap is charged per night held, and Dukascopy tick data can be downloaded in the app on a plan with the Tick tier.

Metals. Gold and silver quote in ounces and are listed with their own rows. Dukascopy serves both.

Indices. Priced as a cash index CFD: one contract is one index point of exposure. If your broker quotes a different multiplier, define a custom symbol with it. Expiry, roll and cash settlement are not modelled, so a run treats an index as continuously tradable.

Crypto. A three letter coin code against USD, EUR, GBP, BTC or ETH places itself; anything longer on either side needs a declared class or a custom symbol. One lot is one coin, so the default lot size of 0.1 is a tenth of a coin, and a pip is one whole unit of the quote currency: a 500 pip stop on BTCUSD sits 500 dollars away. There is no funding rate model, and the swap you configure is charged per UTC date boundary crossed, weekends included.

Equities and CFDs. One share or one unit, and a cent. The default lot size of 0.1 buys a tenth of a share, so set the lot size to a share count you could actually trade. Dividends and splits are not modelled: an unadjusted series shows a gap on the ex dividend date and on a split, and the engine reads that gap as a price move. Use an adjusted series, or treat the result as unreliable across those dates. There is no trading calendar either, so a holiday is a stretch with no bars and nothing marks it.

Gaps. A bar whose open is already through your stop fills at the open, not at the stop level, because a stop is a market order once it is touched. A target is a limit order and fills at its price or better, so a gap through a target also fills at the open. On the tick tier the question does not arise: every fill is at a real traded price.

Insolvency ends the run

If equity reaches zero or below on a bar, the engine closes the open position at the price where exactly what was left is gone, stops the run at that bar, and records the wipe out with the time it happened. Without it, a wiped out account would go on trading with money that does not exist, and the result would come back with a drawdown above 100% and a return computed from it.

A run that ends early for this reason says so, so a short run is not mistaken for a strategy that simply stopped signalling.

What a finished run records

Which asset class it settled on, which data source and which accuracy tier it actually got, and the currency the figures are in, which is the instrument's quote currency and not your account currency. Whether the account was wiped out, and on which bar, is recorded the same way.

The class is recorded rather than assumed because it decides every money figure in that result. None of it is shown inside the app: export the report to read it. See Backtesting for what each one means.

Where non forex bars come from

The built in source serves every instrument in OrionQuant's archive of Dukascopy data: forex pairs, gold and silver, cash indices, US shares, energy, copper and ten coins. The archive holds no futures yet. Anything else needs a CSV import or a data source a plugin adds. The Data source menu under Data, This machine lists whatever can serve a download right now, and the download says which one it used.

Symbol codes

Each archive instrument has one code, and a download, a run and its result all carry it.

  • Eleven cash indices keep the short names in the picker: US500, US30, USTEC, US2000, DE40, UK100, FR40, EU50, JP225, AUS200, HK50.
  • Everything else uses the vendor's code: AAPL.US/USD for a share, LIGHT.CMD/USD for crude oil, CHE.IDX/CHF for the Swiss index, EURUSD for a pair.

Other spellings of the same instrument are accepted and read as it. AAPLUSUSD, the flat form the archive uses in its addresses, is AAPL.US/USD, and USA500.IDX/USD is US500. Data downloaded under an older spelling is moved to the code the first time it is read.

Not implemented

These are absent, not hidden. Nothing in the engine handles them today:

  • Dividends and corporate actions
  • Stock splits
  • Trading calendars, market holidays, half days, auctions
  • Contract expiry, roll, and cash settlement
  • Maintenance margin and margin calls. Leverage binds at entry. Initial margin is enforced by default (enforce_margin), so an order the account cannot margin is refused and counted in the report, as Margin on Costs and Fills explains. Nothing watches a maintenance level after that, so there is no margin call, and the only stop out is the insolvency check above, at zero equity
  • Per symbol costs. Swap and commission are global to the run
  • Short borrow cost and hard to borrow lists
  • Account currency conversion. The rate is fixed at 1.0. Profit and loss on an instrument not quoted in your account currency is reported in the quote currency and not converted. The result names that currency, and the run log says so when it differs from the account currency
  • More than one open position, and more than one instrument in a run. The standard engine holds one position at a time: a second signal in the same direction while one is open is dropped, and an opposite signal closes and reverses. Three paths run their own multi position simulation instead: Grid Basket (cTrader MDB), Lot Doubling + Hedge (benchmark), and a C# strategy that places its own orders

One limit that is not a missing feature but will surprise you:

  • Ticker names must be 1 to 16 characters of A-Z and 0-9, with -, . and = allowed between them but not at either end, and .. refused outright. BRK.B, BTC-USD and EURUSD=X are usable; ^GSPC is not

If a result depends on any of these, the engine cannot give you that result, and it will not tell you it is missing. That is what this list is for.

OrionQuant