Relative Performance Measure (RPM)#

RPM indicates the percentage of total market activity outperformed by order execution. Fair and reasonable execution is around 50%. Values above 50% indicate superior execution. Converges to 50% as the executed size approaches the market volume.

RPM = V_o/V_t + 0.5 * V_e / V_t

V_t – total market volume between order arrival and exit V_o – market volume traded at prices worse (‘higher’ for buy orders, ‘lower’ for sell order) than the order VWAP. In other words, V_o/V_t is the percentage of market activity that the execution outperformed. V_e – market volume traded at prices equal to the order VWAP

Here’s the complete code to achieve this:

import onetick.py as otp
import operator

# Define your symbols, orders, and trades database
symbol = 'TSLA'
orders_db = 'ORDERS_DB'
trades_db = 'NYSE_TAQ'
date = otp.dt(2022, 3, 2)

# Load orders data
orders = otp.DataSource(orders_db, tick_type='ORDER', symbol=symbol)

# Roll up orders to get VWAP, ARRIVAL_TIME (first tick time), and EXIT_TIME (last tick time)
orders_agg = orders.agg({
    'VWAP': otp.agg.vwap('PRICE_FILLED', 'QTY_FILLED'),
    'ARRIVAL_TIME': otp.agg.first_time(),
    'EXIT_TIME': otp.agg.last_time(),
    'SIDE': otp.agg.first('SIDE')
}, group_by='ID')

# Calculate Direction (1 for BUY, -1 for SELL)
orders_agg['DIRECTION'] = orders_agg.apply(lambda tick: 1 if tick['SIDE'] == 'BUY' else -1)

# Define the function to add state variable aggregation
def add_state_var_aggr(
        src: otp.Source,
        state_var: str,
        column: str,
        condition=lambda row: True,
        action=operator.add
    ):
    src.state_vars[state_var] = 0.0
    src.state_vars[state_var] = src.apply(
        lambda row: action(src.state_vars[state_var], row[column])
        if condition(row) else src.state_vars[state_var]
    )
    return src.state_vars[state_var]

# Define the function to calculate RPM
def rpm(vwap, direction) -> otp.Source:
    # Get trades
    md = otp.DataSource(trades_db, tick_type='TRD')

    # Sum up all qty ('SIZE') of trades
    total = add_state_var_aggr(md, 'TOTAL', 'SIZE')

    # Sum up qty of trades which price was worse than order vwap
    worse = add_state_var_aggr(
        md,
        'WORSE',
        'SIZE',
        lambda row: (row['PRICE'] > vwap and direction == 1) or (row['PRICE'] < vwap and direction == -1)
    )

    # Sum up qty of trades which prices are equal to order's vwap
    equal = add_state_var_aggr(md, 'EQUAL', 'SIZE', lambda row: row['PRICE'] == vwap)

    # Calculate RPM
    md = md.last()
    md['RPM'] = (worse + 0.5 * equal) / total
    md['RPM'] = md.apply(lambda row: otp.nan if vwap == otp.nan else md['RPM'])
    return md[['RPM']]

# Join orders with RPM calculation
orders_with_rpm = orders_agg.join_with_query(
    rpm,
    params=dict(vwap=orders_agg['VWAP'], direction=orders_agg['DIRECTION']),
    start_time=orders_agg['ARRIVAL_TIME'],
    end_time=orders_agg['EXIT_TIME']
)

# Run the query for the specified date
df = otp.run(orders_with_rpm, date=date)