<jupyter_start><jupyter_text>![cuemacro_logo.png](attachment:cuemacro_logo.png) Additional benchmarks and metrics for tcapy**<NAME> / Founder of Cuemacro**https://www.cuemacro.com / <EMAIL> / @saeedamenfx / All material is copyright Cuemacro / 2020 In this notebook we discuss some of the additional benchmarks and metrics which are available in tcapy. You can also create your own benchmarks and metrics with tcapy. Before going through this notebook, we recommend first reading through other notebooks available for tcapy [here](https://github.com/cuemacro/tcapy/blob/master/tcapy_notebooks/jupyter_notebooks_description.md) in particular the introduction.  BenchmarksHere we'll go through some of the benchmarks available in tcapy, which can be used for trades/orders (although some are more appropriate for orders). The abstract class `Benchmark` in tcapy can be extended to create your own customised benchmarks. Some which extend `BenchmarkMarket` are calculated purely on market data to provide more granularity over the market. However, there are many which extend `BenchmarkTrade` which can be used to calculate specific benchmarks for trades/orders.* Arrival price (`BenchmarkArrival`) -     * arrival price at start of order/trade fill* TWAP (`BenchmarkTWAP`)     * time weighted average price over the order window* VWAP (`BenchmarkVWAP`) -     * volume weighted average price over the order window* Best price (`BenchmarkBest`)     * best price over the order over the order window, ie. the minimum price for buys and maximum price for sells* Worst price (`BenchmarkWorst`)     * worst price over the order over the order window, ie. the maximum price for buys and minimum price for sells, if we repeatedly get filled at the worst price in our window, it is worth investigating why and talk to our liquidity providers* Median price (`BenchmarkMedian`)    * median price over the order windowWe can also select parameters to start the window earlier before our trade/order and finish later. This enables us to create a window for trades, which are points-in-time (unlike orders which already have a start and finish times). We can also assign a specific time of day as a benchmark. Hence for example, we could benchmark our trades/orders against:* A TWAP calculated on the window between 15:59:30 - 16:00:30 (London time) on the same day as a trade/order window as a proxy for the WMR fix (note, for Sunday evening FX trades the benchmark would be last tick of Friday)* Arrival/mid price at 16:00:00We later show how to do these benchmarks when calculating the slippage metric. Slippage is simply the difference between the execution price and the benchmark. If it is negative, it implies you executed worse than the benchmark, whilst positive numbers indicate you've executed better than the benchmark.  Importing and setting up source for tcapyOur first step is to add the tcapy library to our `PYTHONPATH`. You may need to change these lines. Note, that in our setup, we are running the Jupyter notebook on Windows, and the backend Celery workers on Linux. If you are running everything in the same environment, the paths are likely to be same.<jupyter_code>import sys
import os

windows_tcapy_path = 'e:/cuemacro/tcapy' # Windows platform
linux_tcapy_path = '/home/tcapyuser/cuemacro/tcapy' # Linux platform
local_test_data_path = '../test/resources/' # Windows platform
remote_test_data_path = '../test/resources/' # WSL drive

# Assuming the front end is on Windows
sys.path.insert(0, windows_tcapy_path)<jupyter_output><empty_output><jupyter_text>If the Python path now includes tcapy, we can do all the imports from it, that we'll need later. We'll also import all the other Python libraries we'll use.<jupyter_code>from collections import OrderedDict

# This is the entry point for our TCA analysis
from tcapy.analysis.tcaengine import TCAEngineImpl

# To construct the parameters for our TCA calculation
from tcapy.analysis.tcarequest import TCARequest

# To access trade and market data
from tcapy.data.databasesource import DatabaseSourceCSVBinary, \
    DatabaseSourceDataFrame, DatabaseSourceDukascopy

# Import all the metrics and benchmarks we'll use
from tcapy.analysis.algos.benchmark import *
from tcapy.analysis.algos.metric import *

# To aggregate the TCA results
from tcapy.analysis.algos.resultsform import TimelineResultsForm, DistResultsForm, BarResultsForm

# To help display the output of a TCA calculation
from tcapy.vis.tcaresults import TCAResults
from tcapy.vis.report.tcareport import TCAReport
from tcapy.vis.displaylisteners import PlotRender

# General classes
from tcapy.conf.constants import Constants
from tcapy.util.mediator import Mediator

# Prevent requests from displaying debug messages for certain libraries
import logging

logging.getLogger("findatapy").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)

# For plotting later
import plotly.io as pio

# For interactive plots (but these disappear when we reopen Jupyter)
pio.renderers.default = "notebook" 

# Using https://github.com/plotly/Kaleido to generate static plot images
# pio.renderers.default = "svg"
pio.renderers.default = "png" 

import plotly

from chartpy import Chart, Style

# Default size for Plotly charts we use
chart_width = 800
chart_height = 500

chart = Chart()
constants = Constants()
style = Style(width=chart_width, height=chart_height, scale_factor=-1, silent_display=True)

# Entry point for TCA calculations
tca_engine = TCAEngineImpl(version=constants.tcapy_version)<jupyter_output>2020-07-27 12:30:56,255; INFO:tcapy.analysis.tcaengine: Init TCAEngine version: pro - Env: desktop_laptop_linux (tcaengine.py:53)<jupyter_text>We'll now create a mapping so tcapy knows where the CSV files for our trade/orders. These test files come with tcapy.<jupyter_code># The test trade/order data is populated between 25 Apr 2017-05 Jun 2017
# with trades/orders for 'EURUSD', 'USDJPY' and 'EURJPY'
local_csv_trade_order_mapping = OrderedDict([('trade_df', os.path.join(local_test_data_path, 'small_test_trade_df.csv')),
                                             ('order_df', os.path.join(local_test_data_path, 'small_test_order_df.csv'))])

remote_csv_trade_order_mapping = OrderedDict([('trade_df', os.path.join(remote_test_data_path, 'small_test_trade_df.csv')),
                                              ('order_df', os.path.join(remote_test_data_path, 'small_test_order_df.csv'))])<jupyter_output><empty_output><jupyter_text>Calculating slippage with 1600 LDN vs mid (arrival) price benchmarkIn this case, we create a benchmark price at 1600 LDN to proxy WMR, calculating a TWAP between 15:58:00 - 16:00:30, which can be used to benchmark all trades that day. We then use this value to calculate the slippage. We'll also use a mid price (and arrival price, although this is essentially the same thing) as other benchmarks.<jupyter_code>benchmark_calcs = [
    # Calculate mid as a benchmark
    BenchmarkMarketMid(),
    
    # Calculate TWAP around 1600 LDN as a benchmark for every trade/order
    BenchmarkTWAP(start_time_before_offset={'m' : 2}, 
        finish_time_after_offset={'s' : 30},
        overwrite_time_of_day='16:00', 
        overwrite_timezone='Europe/London',
        benchmark_post_fix="1600LDN"), 
    
    # Calculate arrival of mid price for every trade/order
    BenchmarkArrival()]<jupyter_output><empty_output><jupyter_text>We'll calculate slippage using two benchmarks 1600 LDN TWAP and the mid price at time of trade/order (the same as the arrival price).<jupyter_code>metric_calcs = [MetricSlippage(bid_benchmark='twap1600LDN', ask_benchmark='twap1600LDN', metric_post_fix='twap1600LDN'),
               MetricSlippage(bid_benchmark='mid', ask_benchmark='mid', metric_post_fix='mid')]<jupyter_output><empty_output><jupyter_text>We want to calculate a timeline of these two slippage metrics, calculated with these two respective benchmarks. Note, that we multiply the `scalar` by `10000.0` given that we want to convert the results into basis points.<jupyter_code>results_form=[TimelineResultsForm(metric_name='slippagetwap1600LDN', scalar=10000.0),
              TimelineResultsForm(metric_name='slippagemid', scalar=10000.0)]<jupyter_output><empty_output><jupyter_text>Let's wrap this calculation up in a `TCARequest`. Here we specify the dates, ticker and other parameters, alongside all the `metric_calcs`, `results_form` and `benchmark_calcs`. For simplicity we are not using multithreading here and we are downloading the data directly from Dukascopy over the web, so not using any database for market data.<jupyter_code># We are conducting a TCA computation for trade/order data between 07 May-15 May 2017 for EURUSD
# The trade dataset is from CSV and the market data is downloaded from Dukascopy on the fly
tca_request = TCARequest(start_date='07 May 2017', finish_date='15 May 2017', ticker=['EURUSD'],
                         tca_type='detailed',
                         trade_data_store='csv', market_data_store='dukascopy',
                         trade_order_mapping=local_csv_trade_order_mapping,
                         metric_calcs=metric_calcs,
                         results_form=results_form,
                         benchmark_calcs=benchmark_calcs,
                         extra_lines_to_plot='twap1600LDN',
                         use_multithreading=False)<jupyter_output><empty_output><jupyter_text>Let's kick off the TCA calculation, on our trade/order data, with market data from Dukascopy.<jupyter_code># Dictionary of (mostly) dataframes as output from TCA calculation
dict_of_df = tca_engine.calculate_tca(tca_request)<jupyter_output>2020-07-27 12:30:56,319; DEBUG:tcapy.analysis.tcamarkettradeloader: Start loading trade/data/computation (tcamarkettradeloader.py:241)
2020-07-27 12:30:56,322; DEBUG:tcapy.analysis.tcatickerloaderimpl: Get market and trade/order data for EURUSD from 2017-05-07 00:00:00+00:00 - 2017-05-15 00:00:00+00:00 (tcatickerloaderimpl.py:80)
2020-07-27 12:30:56,324; DEBUG:tcapy.data.volatilecache: Attempting to get list from cache: ['dukascopy_EURUSD_2017-05-07 00:00:00+00:00_2017-05-15 00:00:00+00:00_market_df_None_comp'] (volatilecache.py:540)
2020-07-27 12:30:56,334; DEBUG:tcapy.data.volatilecache: Attempting to get list from cache: ['dukascopy_EURUSD_2017-05-07 00:00:00+00:00_2017-05-15 00:00:00+00:00_market_df_None_comp'] (volatilecache.py:540)
2020-07-27 12:30:56,509; DEBUG:tcapy.data.databasesource: Downloading 2017-05-07 00:00:00 - 2017-05-15 00:00:00 for EURUSD (databasesource.py:3819)
2020-07-27 12:31:09,655; WARNING:tcapy.analysis.algos.benchmark: mid not in market data (benchmark.py:90[...]<jupyter_text>We get back a bunch of DataFrames (and candlestick Plotly Figure) etc. from our calculations, which we display below.<jupyter_code>dict_of_df.keys()<jupyter_output><empty_output><jupyter_text>If we display the first few trades, we note all the additional calculated fields to the right of the table, such as `twap1600LDN`, `arrival`, `slippagetwap1600LDN` etc.<jupyter_code>dict_of_df['trade_df'].head(3)<jupyter_output><empty_output><jupyter_text>If we create a `TCAResults` object, we can render lots of nice charts from our TCA output. It also organises the various DataFrames in an easier to access way.<jupyter_code>tca_results = TCAResults(dict_of_df, tca_request, chart_width=chart_width, chart_height=chart_height)
tca_results.render_computation_charts()<jupyter_output>2020-07-27 12:31:11,165; DEBUG:tcapy.vis.displaylisteners: Plotting main timeline 2840 (displaylisteners.py:988)
2020-07-27 12:31:11,230; DEBUG:tcapy.vis.displaylisteners: Rendered plot 2840 and  (displaylisteners.py:998)
2020-07-27 12:31:11,233; DEBUG:tcapy.vis.displaylisteners: Plotting main timeline 2840 (displaylisteners.py:988)
2020-07-27 12:31:11,296; DEBUG:tcapy.vis.displaylisteners: Rendered plot 2840 and  (displaylisteners.py:998)<jupyter_text>Let's take a look at the timeline DataFrame objects which have been output. In this instance, these are time series of average slippage both for our trades and also orders, which have been calculated with the mid price.<jupyter_code>tca_results.timeline.keys()<jupyter_output><empty_output><jupyter_text>Let's plot the slippage (in basis points on the y-axis) based upon the 1600 LDN TWAP as benchmark and also later using the mid price. We see that we've generally got quite large amounts of slippage versus 1600 LDN TWAP, hence, it would have generally been better to trade at 1600 LDN. Looking at the mid price as a benchmark, our slippage hasn't been too bad.We should note that these are all randomized trades, so it's difficult to read too much into these results (they would of course be more meaningful with real trade data). The time series is also relatively short to make any specific inferences.<jupyter_code>tca_results.timeline_charts['trade_df_slippagetwap1600LDN_by/mean_date/all']
tca_results.timeline_charts['trade_df_slippagemid_by/mean_date/all']<jupyter_output><empty_output><jupyter_text>We can also display the market data charts alongside our orders executed (and the benchmarks, which in our case is the arrival price, as well as the TWAP around 1600 LDN the same day.<jupyter_code>tca_results.sparse_market_charts.keys()
tca_results.sparse_market_charts['EURUSD_order_df']<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07644119621149974, 'fraction_numerical': 0.04019688269073011, 'mean_word_length': 5.171191900598251, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 56, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '145044', 'license': 'Apache-2.0', 'n_tokens_mistral': 4504, 'n_tokens_neox': 3960, 'n_words': 1392, 'path': 'tcapy_notebooks/additional_benchmarks_metrics_for_tcapy.ipynb', 'repo_name': 'Ahrvo-Trading-Systems/tcapy', 'size': '13424'}