Strategy Builder Overview

Demo Video

Writing a Strategy

When writing a strategy, the first step is to import the Strategy and TargetAllocation classes. Optionally, you can import the log() function to help debug your strategy:

from surmount.base_class import Strategy, TargetAllocation
from surmount.logging import log

Next, import the technical indicators and data sources that you want to use for your strategy, like so:

from surmount.technical_indicators import RSI
from surmount.data import Ratios

For more information, scroll down to the example strategies. To see the full list of available indicators and data classes, click on the links below:

After that, define your strategy class, interval, assets, and data. The interval can be one of the following: 1min, 5min, 1hour, 4hour, 1day. The data list should be a list of the data classes you imported earlier.

class TradingStrategy(Strategy):

    def __init__(self):
        self.tickers = ["AAPL", ...]
        self.data_list = [Ratios(ticker) for ticker in self.tickers]

    @property
    def interval(self):
        return "1day"

    @property
    def assets(self):
        return self.tickers

    @property
    def data(self):
        return self.data_list

Finally, define your strategy in the run() method. The data object is a Python dictionary containing OHLCV data as well as data from all of the sources you added to data_list above. This method needs to return a TargetAllocation object, which is a dictionary with the keys being tickers and values being decimal allocations that add up to 1 at the most.

def run(self, data):
    allocation_dict = {i: 1/len(self.tickers) for i in self.tickers}
    ohlcv = data.get("ohlcv")
    ratios = data.get(("ratios","AAPL"))
    log(str(ohlcv))
    log(str(ratios))
    # WRITE STRATEGY LOGIC HERE
    return TargetAllocation(allocation_dict)

The backtesting logs for the code above will be in the following format:

# ohlcv
[
    {
        'AAPL': 
            {
                'date': '2023-07-12 00:00:00', 
                'open': 189.68, 
                'low': 188.47, 
                'high': 191.7, 
                'close': 189.77, 
                'volume': 60751373
            },
    ...},
...]

# ratios
[
    {
        'symbol': 'AAPL', 
        'date': '1985-09-30', 
        'calendarYear': '1985', 
        'period': 'Q4', 
        'currentRatio': 2.7830060934326335, 
        'quickRatio': 1.886255924170616, 
        'cashRatio': 1.140825998645904, 
    ...}, 
...]

Example Strategies

Here is a simple strategy that checks the latest insider trade for sales. More specifically, the strategy first allocates every asset evenly. Then, it checks insider trading data for each asset. If the most recent trade is a sale, the strategy does not allocate any money to that asset.

from surmount.base_class import Strategy, TargetAllocation
from surmount.logging import log
from surmount.data import InsiderTrading

class TradingStrategy(Strategy):

    def __init__(self):
        self.tickers = ["SPY", "QQQ", "AAPL", "GOOGL"]
        self.data_list = [InsiderTrading(i) for i in self.tickers]

    @property
    def interval(self):
        return "1day"

    @property
    def assets(self):
        return self.tickers

    @property
    def data(self):
        return self.data_list

    def run(self, data):
        allocation_dict = {i: 1/len(self.tickers) for i in self.tickers}
        for i in self.data_list:
            if tuple(i)[0]=="insider_trading":
                if data[tuple(i)] and len(data[tuple(i)])>0:
                    if "Sale" in data[tuple(i)][-1]['transactionType']:
                        allocation_dict[tuple(i)[1]] = 0

        return TargetAllocation(allocation_dict)

This is a strategy that buys VIRT when there is a spike in SPY trading volume. The strategy calculates simple moving averages of VIRT volume, and based on a comparison between the 40-day and 10-day moving averages, allocates a certain percentage of VIRT.

from surmount.base_class import Strategy, TargetAllocation
from surmount.logging import log
from surmount.data import Asset, InstitutionalOwnership
import pandas_ta as ta
import pandas as pd

def SMAVol(ticker, data, length):
    '''Calculate the simple moving average of trading volume

    :param ticker: a string ticker
    :param data: data as provided from the OHLCV data function
    :param length: the window

    :return: list with float SMA
    '''
    close = [i[ticker]["volume"] for i in data]
    d = ta.sma(pd.Series(close), length=length)
    if d is None:
        return None
    return d.tolist()

class TradingStrategy(Strategy):
    def __init__(self):
        self.tickers = ["VIRT"]
        self.data_list = []

    @property
    def interval(self):
        return "1day"

    @property
    def assets(self):
        return self.tickers

    @property
    def data(self):
        return self.data_list

    def run(self, data):
        vols = [i["VIRT"]["volume"] for i in data["ohlcv"]]
        smavols = SMAVol("VIRT", data["ohlcv"], 40)
        smavols2 = SMAVol("VIRT", data["ohlcv"], 10)

        if len(vols)==0:
                return TargetAllocation({})

        if smavols2[-1]/smavols[-1]-1>0:
                out = smavols2[-1]/smavols[-1]-1
        else: out = 0

        return TargetAllocation({"VIRT": min(0.9, (out*10)**(0.5))})

This strategy uses pyramiding and Bollinger bands to build long and short positions on QQQ. Based on the bollinger bands and simple moving averages, it will invest in various amounts of QQQ and SQQQ (SQQQ shorts the stocks that QQQ contains).

from surmount.base_class import Strategy, TargetAllocation
from surmount.technical_indicators import SMA, BB
from surmount.logging import log

class TradingStrategy(Strategy):

    @property
    def assets(self):
        return ["QQQ", "SQQQ"]

    @property
    def interval(self):
        return "1hour"

    def run(self, data):
        holdings = data["holdings"]
        data = data["ohlcv"]

        sqqq_stake = 0
        qqq_stake = 0

        qqq_bbands = BB("QQQ", data, 20, 1.4)
        qqq_ma = SMA("QQQ", data, 5)

        if len(data)<20:
            return TargetAllocation({})

        current_price = data[-1]["QQQ"]['close']

        if qqq_bbands is not None and current_price < qqq_bbands['lower'][-1] and qqq_ma[-1]>qqq_ma[-2]:
            log("going long")
            if holdings["QQQ"] >= 0:
                qqq_stake = min(1, holdings["QQQ"]+0.1)
            else:
                qqq_stake = 0.4
        elif qqq_bbands is not None and current_price > qqq_bbands['upper'][-1]:
            log("going short")
            if holdings["SQQQ"] >= 0:
                sqqq_stake = min(1, holdings["SQQQ"]+0.075)
            else:
                sqqq_stake = 0.2
        else:
            log("meh")
            qqq_stake = 0
            sqqq_stake = 0

        return TargetAllocation({"SQQQ": sqqq_stake, "QQQ": qqq_stake})