Detect simple TWAP executions in tick trades data.
We use crypto-lake.com sample/free market data, FTRB-USDT market on Ascendex.
Quick links:
import datetime
import math
import numpy as np
import pandas as pd
import cufflinks as cf
from scipy import signal
import plotly.express as px
import lakeapi
cf.go_offline()
lakeapi.use_sample_data(anonymous_access=True)
# Parameters
symbol = 'FTRB-USDT'
exchange = 'ASCENDEX'
# Free sample data contain subset of the below time period
start = datetime.datetime(2022, 9, 1)
end = datetime.datetime(2022, 12, 15)
print('Loading trades')
trades = lakeapi.load_data(
table = 'trades',
start = start,
end = end,
symbols = [symbol],
exchanges = [exchange],
drop_partition_cols = True,
).sort_values('origin_time')
Loading trades
0%| | 0/81 [00:00<?, ?it/s]
trades['signed_quantity'] = trades.quantity * trades.side.map({'buy': +1, 'sell': -1}).astype(int)
# A trick here: instead of using trade side as 'aggressor', I use something else as aggressivity:
# direction and strength of current short-term trend.
trades['price_ewm'] = trades.price.ewm(halflife = 5).mean()
trades['aggressivity'] = (trades.price - trades.price_ewm) / trades.price_ewm
trades['aggr_quantity'] = trades.quantity * trades.aggressivity
print(trades.shape)
trades[100:].head(2)
(99539, 10)
side | quantity | price | trade_id | origin_time | received_time | signed_quantity | price_ewm | aggressivity | aggr_quantity | |
---|---|---|---|---|---|---|---|---|---|---|
100 | sell | 2586.0 | 0.010007 | 108086470187312032 | 2022-09-01 15:57:58.421999872 | 2022-09-01 15:57:58.613365504 | -2586.0 | 0.010023 | -0.001642 | -4.246117 |
101 | buy | 2550.0 | 0.010013 | 108086470187322384 | 2022-09-01 15:58:23.803000064 | 2022-09-01 15:58:24.033291776 | 2550.0 | 0.010022 | -0.000908 | -2.316488 |
def volume_share_plot(selector, title = None, resample = '60min'):
return (
trades[selector].set_index('origin_time').resample(resample).quantity.sum()
/
trades.set_index('origin_time').resample(resample).quantity.sum()
).iplot(title = title, yTitle = 'volume share')
def volume_plot(selector, title = None, resample = '60min'):
df = trades
return df[selector].set_index('origin_time').resample(resample).agg({'aggr_quantity': 'sum'}).iplot(title = title, yTitle = 'adjusted imbalance')
# Estimate power spectral density using a periodogram
f, Pxx = signal.periodogram(trades.quantity, fs = 1e6, nfft = 1000)
plot = px.line(x = f, y = Pxx, title = 'Peak frequencies (in ms)')
for i in [6, 60, 5*60]:
plot.add_vline(i * 1_000, line_width=1, line_color="orange")
plot
We see a lot of quantity is submited in frequencies of 6 seconds, 60 seconds and 5 minutes. Lets focus on 60-second twap orders aligned to second boundary = eg. around 12:35:45.000 +- a few millis.
around_whole_second = (trades.origin_time.dt.microsecond < 80_000) | (trades.origin_time.dt.microsecond > 950_000)
volume_share_plot(selector = around_whole_second, resample = '60min')
volume_plot(selector = around_whole_second, resample = '60min')
trades.set_index('origin_time').price[::100].iplot(yTitle = 'price')
We can see a few strong twaps in the charts. Eg. Nov 7 -- Nov 11 had a significant sell twap, it's effect is clearly visible in price chart as well. Other sells are around Dec 4 and perhaps Nov 2. One buy twap with smaller volume is around Oct 25. Some of the twap chunks don't seem to be imbalanced and therefore affecting price, eg. in September.
We detected a few twap executions, some of which were affecting price significantly. This analysis can be extended to run in real-time to serve as a trading signal or MM bot adjustment. The advantage of filtering out twap data compared to following price trend is that the signal is more clearly readable and can be easily used for trend-following.