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(meansPositionResetRequest; zeros position) orSNAPSHOT(meansPositionSnapshot; 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.
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 aSymbolprojection key and must not containRootSymbol. Example:Source[BOT42]/Destination[COINBASED]/Symbol[BTCZ25-M].--limit(optional, default10000) — 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, defaulttrue) — if set tofalse, 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:
OrderTradeReportEventmessages (normal trades) update the quantity and average cost using the fill price and size;PositionSnapshotResetmessages adjust position with user-provided values; andPositionResetRequestmessages 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.
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.