Skip to main content

position-tracer

position-tracer is a tool for dumping Ember positions in CSV format. Given a projection path, it reads the Ember journal and shows how a position changes after each trade or position reset/snapshot message.

Like most Ember tools, position-tracer relies on the EMBER_HOME environment variable (and optionally EMBER_WORK) to locate Ember configuration and journal files.

Ember uses the average cost method for position calculation.

The first output line contains the table with the following fields:

  • Timestamp — timestamp of the trade, position reset message or position snapshot message
  • Action — either TRADE_BUY \ TRADE_SELL, RESET(means PositionResetRequest; zeros position) or SNAPSHOT (means PositionSnapshot; sets the position to an arbitrary size)
  • TradeSize — fill size of the trade reported on this line
  • TradePrice — fill price of the trade reported on this line
  • PositionQty — resulting position size after this trade
  • PositionAvgCost — resulting average position cost after this trade
  • PositionPnL — resulting profit or loss after this trade

In case of a position reset, TradeSize and TradePrice are omitted.

The tool takes instrument multipliers into account.

danger

The current version does not handle inverse contracts.

Usage

/opt/deltix/ember/bin/position-tracer --projectionPath <path> [--limit <n>] [--output <file>] [--skipNotCounted <bool>] [--timeBreakpoint <iso-timestamp>]...

Arguments:

  • --projectionPath (required) — projection path identifying the position to trace. Must contain a Symbol projection key and must not contain RootSymbol. Example: Source[BOT42]/Destination[COINBASED]/Symbol[BTCZ25-M].
  • --limit (optional, default 10000) — maximum number of history entries kept between flushes. Must be a positive integer.
  • --output (optional) — path to the output file. If omitted, the report is printed to standard output.
  • --skipNotCounted (optional, default true) — if set to false, then position tracer will show all trades in the history which aren't counted toward position (they're marked with * in the Action column).
  • --timeBreakpoint (optional, repeatable) — ISO-8601 timestamp (uuuu-MM-ddTHH:mm:ss.SSSZ, e.g. 2026-05-19T10:00:00.000Z) at which the accumulated position history is flushed.

By default, the instrument's multipliers are loaded from TimeBase. To load it from the journal instead, set the ENV property instruments.from.journal=true.

The example below shows how the position of the BOT42 algorithm on COINBASE derivatives changed for the BTCZ25-M instrument:

/opt/deltix/ember/bin/position-tracer --projectionPath Source[BOT42]/Destination[COINBASED]/Symbol[BTCZ25-M]
2026-05-20 13:55:19.127 INFO [main] Starting loading instrument snapshot from journal
2026-05-20 13:55:19.148 INFO [main] Extracting list of instruments from journal
2026-05-20 13:55:25.860 INFO [main] Loaded 107 instruments from journal
2026-05-20 13:55:25.861 INFO [main] Loaded instrument snapshot with 107 instruments.
=== End of Journal ===
+--------------------------+------------+------------+------------+-------------+-------------------+--------------------+
| Timestamp | Action | TradeSize | TradePrice | PositionQty | PositionAvgCost | PositionPnL |
+--------------------------+------------+------------+------------+-------------+-------------------+--------------------+
| 2024-05-29T08:51:13.438Z | TRADE_BUY | 0.0001 | 67890.22 | 0.0001 | 67890.22 | 0 |
| 2024-05-29T08:51:22.597Z | TRADE_SELL | 0.0001 | 67873.99 | 0 | 0 | -0.001623 |
| 2024-05-30T14:18:53.933Z | TRADE_BUY | 0.0001 | 68677.9 | 0.0001 | 68677.9 | -0.001623 |
| 2024-05-30T14:19:02.535Z | TRADE_SELL | 0.0001 | 68665.34 | 0 | 0 | -0.002879 |
| 2024-06-05T16:03:16.700Z | TRADE_BUY | 0.01 | 71684.04 | 0.01 | 71684.04 | -0.002879 |
| 2024-06-05T16:03:25.544Z | TRADE_SELL | 0.00279051 | 71658.66 | 0.00720949 | 71684.04 | -0.0737021438 |
| 2024-06-05T16:03:25.544Z | TRADE_SELL | 0.00720949 | 71658.63 | 0 | 0 | -0.2568952847 |
| 2024-06-25T12:29:40.282Z | TRADE_BUY | 0.00000338 | 61221.32 | 0.00000338 | 61221.32 | -0.2568952847 |
| 2024-06-25T12:29:40.283Z | TRADE_BUY | 0.03671707 | 61234.87 | 0.03672045 | 61234.86875276583 | -0.2568952847 |
| 2024-06-25T12:29:40.283Z | TRADE_BUY | 0.09523323 | 61240.06 | 0.13195368 | 61238.6153647727 | -0.2568952847 |
| 2024-06-25T12:29:40.283Z | TRADE_BUY | 0.02449376 | 61240.07 | 0.15644744 | 61238.84310570694 | -0.2568952847 |
| 2024-06-25T12:29:40.284Z | TRADE_BUY | 0.04355256 | 61242.3 | 0.2 | 61239.5958886875 | -0.2568952847 |
| 2024-06-25T12:30:36.107Z | TRADE_BUY | 0.00014595 | 61224.77 | 0.20014595 | 61239.58507738478 | -0.2568952847 |
| 2024-06-25T12:30:36.107Z | TRADE_BUY | 0.04985405 | 61224.78 | 0.25 | 61236.632705112 | -0.2568952847 |
...

How Ember Computes Positions

Ember maintains positions by processing a stream of journal messages in the order they are written. Three message types directly affect position state:

  • OrderTradeReportEvent messages (normal trades) update the quantity and average cost using the fill price and size;
  • PositionSnapshotReset messages adjust position with user-provided values; and
  • PositionResetRequest messages zero out the position.

Trade messages appear as TRADE in the output; both of the latter types manifest as RESET.

Position Manager currently does not handle trade corrections and cancellations (OrderTradeCorrectEvent / OrderTradeCancelEvent). Ask us for workarounds if necessary.

Because multi-level algorithmic orders generate one trade report at every level of the hierarchy, naively counting all of them would inflate the position. Ember avoids this double-counting by applying a projection-path–based filter.

[DMA orders]

A DMA (Direct Market Access) order is one that routes directly to a market via a trade connector — for example, orders sent to the CME or Binance connectors. An Ember algorithm can also be explicitly tagged as DMA, for instance when it implements a matching engine.

Consider an example: a TWAP execution algorithm routes sliced child orders to a Smart Order Router (SOR), which routes its children to trade connectors for EXCHANGE_A and EXCHANGE_B. When the EXCHANGE_A connector emits a trade event, it is republished as a separate trade message for the SOR order and then again on behalf of TWAP.

If Ember is configured to track per-account positions (using an Account/Symbol projection), only trades from EXCHANGE_A and EXCHANGE_B orders (tagged as DMA) will affect the per-account position.

However, in some cases you may want to track positions per algorithm — for example, per order source or destination. Going back to the example above, imagine tracking positions per execution algorithm using a Destination/Symbol projection. Ember recognizes this and counts every trade (DMA and non-DMA), bucketing each one by the destination of the order it belongs to (TWAP, SOR, EXCHANGE_A, or EXCHANGE_B).

In summary: a trade affects the position either because it belongs to a DMA order (when the projection path has no Source/Destination key), or because its Source/Destination matches the projection path key.