Examining live trading strategy using notebook#

In this notebook, we show how to examine live trading strategy instance using Python and Jupyter notebook.

  • Downloading the trades from the live instance

  • Reading data to State object

  • Using the same analytics functions we use in backtests against the live data

Getting started#

First, let’s create Trading Strategy oracle market data client. If you do not have an API key yet, you will be asked to create one.

[1]:
from tradingstrategy.client import Client

client = Client.create_jupyter_client()
Started Trading Strategy in Jupyter notebook environment, configuration is stored in /Users/moo/.tradingstrategy

Download strategy trades#

  • Each strategy Docker instances offers a webhook that allows access the data of this strategy, include State object that is the flat file state of the all the strategy current and past decision making

  • Note that State class and any children classes in the state tree may have changes between versions and decoding might need you to use a specific version of trade-executor

[2]:

from tradeexecutor.monkeypatch.dataclasses_json import patch_dataclasses_json from tradeexecutor.state.state import State import requests # Currently needed because unpatched dataclasses_json package issues patch_dataclasses_json() # Public internet endpoint as exposed by the trade executor Docker webbhook_url = "https://pancake-eth-usd-sma.tradingstrategy.ai" state_api = f"{webbhook_url}/state" resp = requests.get(state_api) state_blob = resp.content print(f"Downloaded {len(state_blob):,} bytes state data") state = State.from_json(state_blob) print(f"trade-executor was launched at: {state.created_at}, we have {len(list(state.portfolio.get_all_trades()))} trades")
Downloaded 256,232 bytes state data
trade-executor was launched at: 2022-12-01 22:17:08.835950, we have 14 trades

Analyse the live trades performance#

We can use the same functions as in our backtesting notebooks to produce summaries, graphs.

[3]:
import pandas as pd
from IPython.core.display_functions import display
from tradeexecutor.analysis.trade_analyser import build_trade_analysis

analysis = build_trade_analysis(state.portfolio)

summary = analysis.calculate_summary_statistics()

with pd.option_context("display.max_row", None):
    display(summary.to_dataframe())
0
Trading period length 5 days
Return % -1.81%
Annualised return % -124.87%
Cash at start $498.78
Value at end $489.75
Trade win percent 14.29%
Total trades done 7
Won trades 1
Lost trades 6
Stop losses triggered 4
Stop loss % of all 57.14%
Stop loss % of lost 66.67%
Zero profit trades 0
Positions open at the end 0
Realised profit and loss $-9.03
Portfolio unrealised value $0.00
Extra returns on lending pool interest $0.00
Cash left at the end $489.75
Average winning trade profit % 0.15%
Average losing trade loss % -0.46%
Biggest winning trade % 0.15%
Biggest losing trade % -0.72%
Average duration of winning trades 0 days
Average duration of losing trades 0 days

Determinte strategy trading pairs#

  • Figure out what trading pairs the strategy has been trading from its state

[4]:
from tradeexecutor.state.position import TradingPosition

pairs = set()

position: TradingPosition
for trade in state.portfolio.get_all_trades():
    pairs.add(trade.pair)


first_trade, last_trade = state.portfolio.get_first_and_last_executed_trade()

print(f"Strategy was trading: {pairs}")
Strategy was trading: {<Pair ETH-USDC at 0xea26b78255df2bbc31c1ebf60010d78670185bd0 on exchange 0xca143ce32fe78f1f7019d7d551a6402fc5350c73>}

Download market data#

  • We download the trading pair OHCLV candles so that we can overlay the strategy trades on the

  • We use 5 minutes accuracy to be able visualise trade events and slippage more accurately on the price chart

[5]:
from tradeexecutor.state.identifier import TradingPairIdentifier
from tradingstrategy.timebucket import TimeBucket
import datetime

assert len(pairs) == 1, "In this notebook, we support analysing only single pair strategies"
pair: TradingPairIdentifier
(pair,) = pairs  # Operate on a single pair now on

# Add some data margin around our
# trade timeline visualisation
feed_start_at = first_trade.started_at - datetime.timedelta(days=2)
feed_end_at = last_trade.executed_at + datetime.timedelta(days=2)

candles: pd.DataFrame = client.fetch_candles_by_pair_ids(
    {pair.internal_id},
    TimeBucket.m15,
    progress_bar_description=f"Download data for {pair.base.token_symbol} - {pair.quote.token_symbol}",
    start_time=feed_start_at,
    end_time=feed_end_at,
)

print(f"Loaded {len(candles)} candles, {feed_start_at} - {feed_end_at}")
Loaded 52660 candles, 2022-12-01 03:08:15.749248 - 2022-12-10 10:08:26.530533

Visualise positions#

  • We use more accurate 15 minutes candle data do visualite the price action, instead of the strategy decision making 1h cycle. This allows us to examine our trade execution against more precie price movement.

  • We use a specific chart to analyse live trading positions visually.

  • Positions are visualised as area: green is closed for profit, red is closed for loss. Each position has a tooltip hint at the top to show its data.

  • This chart type also shows slippage and duration, between the trade start and trade end, to visualise how much much losses were taken on slippage.

  • Individual trades, entries and exists are visualised as arrows.

  • If arrows have any meaningful length in Y (price) direction it means expected price and execution prices differ. There was slippage and we could not execute the trade with the price we wanted.

  • If arrows have any meaningful length in X (time) direction it means that executing the trade too longer than expected - block and transaction confirmation was slow, or there was some other issue in the trade execution.

  • If the arrows are point like it means the trade was executed as expected.

[6]:
from tradeexecutor.visual.single_pair import visualise_single_pair_positions_with_duration_and_slippage

fig = visualise_single_pair_positions_with_duration_and_slippage(
    state,
    candles,
    start_at=feed_start_at,
    end_at=feed_end_at,
)

display(fig)