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
objectUsing 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 /home/alex/.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 makingNote 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 oftrade-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 6,467 bytes state data
---------------------------------------------------------------------------
JSONDecodeError Traceback (most recent call last)
Cell In[2], line 19
15 state_blob = resp.content
17 print(f"Downloaded {len(state_blob):,} bytes state data")
---> 19 state = State.from_json(state_blob)
21 print(f"trade-executor was launched at: {state.created_at}, we have {len(list(state.portfolio.get_all_trades()))} trades")
File ~/.pyenv/versions/3.10.8/lib/python3.10/site-packages/dataclasses_json/api.py:58, in DataClassJsonMixin.from_json(cls, s, parse_float, parse_int, parse_constant, infer_missing, **kw)
49 @classmethod
50 def from_json(cls: Type[A],
51 s: JsonData,
(...)
56 infer_missing=False,
57 **kw) -> A:
---> 58 kvs = json.loads(s,
59 parse_float=parse_float,
60 parse_int=parse_int,
61 parse_constant=parse_constant,
62 **kw)
63 return cls.from_dict(kvs, infer_missing=infer_missing)
File ~/.pyenv/versions/3.10.8/lib/python3.10/json/__init__.py:346, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
341 s = s.decode(detect_encoding(s), 'surrogatepass')
343 if (cls is None and object_hook is None and
344 parse_int is None and parse_float is None and
345 parse_constant is None and object_pairs_hook is None and not kw):
--> 346 return _default_decoder.decode(s)
347 if cls is None:
348 cls = JSONDecoder
File ~/.pyenv/versions/3.10.8/lib/python3.10/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
332 def decode(self, s, _w=WHITESPACE.match):
333 """Return the Python representation of ``s`` (a ``str`` instance
334 containing a JSON document).
335
336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
338 end = _w(s, end).end()
339 if end != len(s):
File ~/.pyenv/versions/3.10.8/lib/python3.10/json/decoder.py:355, in JSONDecoder.raw_decode(self, s, idx)
353 obj, end = self.scan_once(s, idx)
354 except StopIteration as err:
--> 355 raise JSONDecodeError("Expecting value", s, err.value) from None
356 return obj, end
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Analyse the live trades performance#
We can use the same functions as in our backtesting notebooks to produce summaries, graphs.
[ ]:
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):
summary.display()
Determinte strategy trading pairs#
Figure out what trading pairs the strategy has been trading from its state
[ ]:
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}")
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
[ ]:
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}")
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.
[ ]:
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)
[ ]:
print("All done")