The Odyssey — Chapter Zero: Trading with the Gods’ Blessing

Youssef Victor
Aug 4 · 7 min read

Table of Contents:

Minerva and the Triumph of Jupiter (1706) by René-Antoine Houasse

For Odysseus to begin his epic journey, he must first have the full power of the Gods by his side. Our trading platform will also make use of lots of general-purpose modules in order to rapidly test and modify strategies while having to maintain the least amount of code possible in between.

A Step Back

First, let’s take a look at the big picture. The purpose of this series is to catalog my efforts in giving algorithmic trading a real shot with well-written code that can be maintained in the future. Because I am a fan of Ethereum, performances will be tested on a subset of the historical price for ETH/USD. Occasionally, I will be adding the actual code for some components from my actual repo. Over the course of the next few weeks, I will (try to) post a new post every week examining new strategies and how they perform on cryptocurrencies. Everything will be written in Python.

The Gods

Before we write any trading logic, however, we lay the foundations for our Odysseus platform using special-purpose modules:

  • Athena: Goddess of War, known for her wisdom in warfare and her aid to many of Greek Mythology’s most famous heroes (including Odysseus). The Athena module is built on a script that automatically collects various cryptocurrency data from exchanges’ APIs. It provides us with the data we need to test our strategies. For the most part, we will be using ETHUSD 1min data scraped from Gemini unless explicitly stated otherwise.
  • Hermes: As the messenger of the gods, Hermes provides us with all the important logging information we need to understand our strategy’s performance.
  • Davinci: I decided to get off of the Greek Mythology train for this module. Named after one of the most famous artists of the renaissance era who also had a knack for designing war machines, Davinci is a graphing module that helps us plot our strategy’s performance against a benchmark.
  • Backtester: As you can tell, I started getting lazy with the names. This is one of the core backbones of Odysseus so I wanted it to have a clear name. It was also just hard to find a Greek god that is relevant given that the concept of backtesting doesn’t really translate easily to Ancient Greek ideas. Backtester is a module whose sole purpose is just that: to take in a Strategy and see how well it performs on the data provided.
  • Strategy: This module houses all the trading logic. It has no concept of position and carries no state. Its purpose is to tell us when to sell or buy. More on how it works later.

The (Pseudo) Code*

*The first three modules’ code is really not that interesting. So I won’t be posting it. Feel free to reach out if you have any specific questions.

Athena just uses exchanges’ APIs and formats them neatly in a database which I then pull from and parse in where needed. It has a simple API to call whenever I need any data, and it splits up each dataset equally into 60% for training, 20% for validation, and 20% for testing following the 80/20 rule.

Hermes is a Python logger that outputs information such as backtesting performance and trade execution. The module is instantiated as multiple “scribe” objects to log different types of information. For example, I always need a performance scribe that logs how well a strategy performed given a set of parameters. On the other hand, I don’t always need to see the log of trades executed during the backtest if I know that the strategy is working as intended. Hermes takes care of that logic for me, all I need to do is send everything through.

Davinci is both my favorite and least favorite module. It currently uses matplotlib to create graphs, but it is incredibly slow. The graphs generated can sometimes be beautiful, but it takes way too long to display them because I sometimes work with hundreds of thousands of ticks and multiple lines overlayed on top of each other. I am in the process of developing Davinci v2, which will be using VisPy for speed. The problem is that VisPy’s built-in plotting abilities are somewhat limited, and VisPy uses OpenGL under the hood. Now I have worked with OpenGL before and I do have experience with GPU Programming in general, so in theory, this does seem feasible.

Backtester is meant to be the main research module which I use to see how well a strategy performs. It stores all the state and uses the other modules to modify that state. Thinking in terms of the MVC pattern, this is the model. The API for using Backtester is as simple as:

backtest_strategy(MovingAveragesCrossover,
data=training_1,
benchmark=training_1)

Under the hood, backtest_strategy does is the following:

def backtest_strategy(strategy: Strategy,
data : np.ndarray,
benchmark: np.ndarray,
show_graph = True):
cash_position = strategy.STARTING_CASH
crypto_position = strategy.STARTING_CRYPTO
for tick in data:
if strategy.should_buy(**tick_info):
buy_qty = strategy.buy_qty(**position_info)
cash_position -= buy_qty
crypto_position += buy_qty
elif strategy.should_sell(**tick_info) > 0:
sell_qty = strategy.sell_qty(**position_info)
cash_position += sell_qty
crypto_position -= sell_qty
elif strategy.should_stoploss(**tick_info) > 0:
stopsell_qty = strategy.stopsell_qty(**position_info)
cash_position += stopsell_qty
crypto_position -= stopsell_qty
performance_scribe.log_performace(**backtest_info) if show_graph:
davinci_grapher.show()

This is a very broad strokes outline of how Backtester works. As you can see, the simplicity of this method is possible through the magic of Python’s kwargs. It is not important to know how they’re defined within the method. For now, we can just assume that they are black boxes that contain all the information needed for the strategy/scribe to execute the method call. Recall that performance_scribe is just a Hermes instance used to record backtest performance.

We saw a brief glimpse of how Strategy’s API looks like above, but it’s time to see what it looks like in more detail. We first create an Abstract Base Class (ABC) called Strategy that defines the API. Here is how that looks like:

from abc import abstractmethod, ABC

class Strategy(ABC):

@abstractmethod
def should_buy(self, **tick_info):
"""
given a bunch of indicators, returns whether or not a
strategy should trigger a BUY order
:param tick_info: information about current period
:return: True or False, whether or not we should buy
"""
pass

@abstractmethod
def buy_qty(self, **position_info):
"""
given a position, how much of that should we sell or buy
:param position_info: information about our current position
:return: the amount to buy
:raise: InvalidStateError if should_buy() is False
"""
pass

@abstractmethod
def should_sell(self, **tick_info):
"""
given a bunch of indicators, returns whether or not
a strategy should trigger a SELL order
:param tick_info: information about current period
:return: True or False, whether or not we should sell
"""
pass

@abstractmethod
def sell_qty(self, **position_info):
"""
given a position, how much of that should we sell or buy
:param position_info: information about our current position
:return: the amount to sell
:raise: InvalidStateError if should_sell() is False
"""
pass

@abstractmethod
def should_stoploss(self, **tick_info):
"""
the exit strategy: whether or not we should cut our losses
and sell to prevent further losses
:param tick_info: information about current period
:return: True or False, whether or not we should sell
"""
pass

@abstractmethod
def should_cancel_buy(self, **order_info):
"""
if an order hasn't been filled yet, and the market has
changed wildly since, use this as an opportunity
to cancel the order before it's filled
:return: whether or not we should cancel the order
"""
pass

@abstractmethod
def should_cancel_sell(self, **order_info):
"""
if an order hasn't been filled yet, and the market has
changed wildly since, use this as an opportunity
to cancel the order before it's filled
:return: whether or not we should cancel the order
"""
pass

These methods all seem self-explanatory. The one exception may be the last two. Why would you want to cancel an order will make you money? Well, not all orders are created/filled equally. Some take time to get filled depending on order size, volume, and many other factors. In that time, if we are no longer in a favorable position to have that order filled, we need to cancel it asap and reevaluate.

This method is called before we check whether to buy/sell/stoploss in backtest_strategy. This way we can cancel an order and make a new (hopefully better) one within the same period if we see the need to do so.

Summary

With that, we have Odysseus. A fully-functional backtesting platform ready to test out and evaluate any trading strategy we desire! We have Athena providing the data, Hermes writing everything down, Davinci displaying the plots, and Backtester executing the strategy and evaluating performance.

All we need now is to create a strategy to trade!

Youssef Victor

Written by

Computer Science and Data Science Master‘s Graduate interested in applying Statistics and Machine Learning to real-world trading. https://tinyurl.com/YVLinkedIn

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade