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.
NOTE: Starting from 1.14.257 this tool is available via Ember Monitor (You can right click on any row in Positions panel and run "Download Trace" command).
Like most Ember tools, position-tracer relies on the EMBER_HOME and EMBER_WORK environment variables to locate Ember configuration and journal files.
Ember uses the average cost method for position calculation.
The first output line contains the table header, with the following fields:
- Timestamp — timestamp of the trade, position reset message or position snapshot message
- Action — either
BUY\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>] [--format table|csv]
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 in memory. 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).--format(optional, defaulttable) — output format:tablefor the bordered ASCII table shown below, orcsvfor a single comma-separated table (header row followed by one row per history entry, all fields double-quoted).
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 | BUY | 0.0001 | 67890.22 | 0.0001 | 67890.22 | 0 |
| 2024-05-29T08:51:22.597Z | SELL | 0.0001 | 67873.99 | 0 | 0 | -0.001623 |
| 2024-05-30T14:18:53.933Z | BUY | 0.0001 | 68677.9 | 0.0001 | 68677.9 | -0.001623 |
| 2024-05-30T14:19:02.535Z | SELL | 0.0001 | 68665.34 | 0 | 0 | -0.002879 |
| 2024-06-05T16:03:16.700Z | BUY | 0.01 | 71684.04 | 0.01 | 71684.04 | -0.002879 |
| 2024-06-05T16:03:25.544Z | SELL | 0.00279051 | 71658.66 | 0.00720949 | 71684.04 | -0.0737021438 |
| 2024-06-05T16:03:25.544Z | SELL | 0.00720949 | 71658.63 | 0 | 0 | -0.2568952847 |
| 2024-06-25T12:29:40.282Z | BUY | 0.00000338 | 61221.32 | 0.00000338 | 61221.32 | -0.2568952847 |
| 2024-06-25T12:29:40.283Z | BUY | 0.03671707 | 61234.87 | 0.03672045 | 61234.86875276583 | -0.2568952847 |
| 2024-06-25T12:29:40.283Z | BUY | 0.09523323 | 61240.06 | 0.13195368 | 61238.6153647727 | -0.2568952847 |
| 2024-06-25T12:29:40.283Z | BUY | 0.02449376 | 61240.07 | 0.15644744 | 61238.84310570694 | -0.2568952847 |
| 2024-06-25T12:29:40.284Z | BUY | 0.04355256 | 61242.3 | 0.2 | 61239.5958886875 | -0.2568952847 |
| 2024-06-25T12:30:36.107Z | BUY | 0.00014595 | 61224.77 | 0.20014595 | 61239.58507738478 | -0.2568952847 |
| 2024-06-25T12:30:36.107Z | BUY | 0.04985405 | 61224.78 | 0.25 | 61236.632705112 | -0.2568952847 |
...
CSV output (--format csv) for the same run looks like this:
"Timestamp","Action","TradeSize","TradePrice","PositionQty","PositionAvgCost","PositionPnL"
"2024-05-29T08:51:13.438Z","BUY","0.0001","67890.22","0.0001","67890.22","0"
"2024-05-29T08:51:22.597Z","SELL","0.0001","67873.99","0","0","-0.001623"
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;PositionSnapshotmessages adjust position with user-provided values; andPositionResetRequestmessages zero out the position.
Trade messages appear as BUY or SELL in the output; PositionSnapshot messages manifest as SNAPSHOT, and PositionResetRequest messages 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.