Futures Trading Bot Template

The template presented below serves as a starting point for the development of a trading algorithm for trading futures contracts on the cryptocurrency exchange Kraken using the python-kraken-sdk.

The trading strategy can be implemented in the TradingBot class. This class has access to all REST clients and receives all messages that are sent via the subscribed websocket feeds via the on_message function.

Template to build a trading bot using the Kraken Futures Websocket API
  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3# Copyright (C) 2023 Benjamin Thomas Schwertfeger
  4# GitHub: https://github.com/btschwertfeger
  5#
  6
  7"""
  8Module that provides a template to build a Futures trading algorithm using the
  9python-kraken-sdk.
 10"""
 11
 12from __future__ import annotations
 13
 14import asyncio
 15import logging
 16import logging.config
 17import os
 18import sys
 19import traceback
 20from typing import Optional
 21
 22import requests
 23import urllib3
 24
 25from kraken.exceptions import KrakenAuthenticationError
 26from kraken.futures import Funding, KrakenFuturesWSClient, Market, Trade, User
 27
 28logging.basicConfig(
 29    format="%(asctime)s %(module)s,line: %(lineno)d %(levelname)8s | %(message)s",
 30    datefmt="%Y/%m/%d %H:%M:%S",
 31    level=logging.INFO,
 32)
 33logging.getLogger("requests").setLevel(logging.WARNING)
 34logging.getLogger("urllib3").setLevel(logging.WARNING)
 35
 36
 37class TradingBot(KrakenFuturesWSClient):
 38    """
 39    Class that implements the trading strategy
 40
 41    * The on_message function gets all messages sent by the websocket feeds.
 42    * Decisions can be made based on these messages
 43    * Can place trades using the self.__trade client
 44    * Do everything you want
 45
 46    ====== P A R A M E T E R S ======
 47    config: dict
 48        configuration like: {
 49            'key' 'kraken-futures-key',
 50            'secret': 'kraken-secret-key',
 51            'products': ['PI_XBTUSD']
 52        }
 53    """
 54
 55    def __init__(self: TradingBot, config: dict) -> None:
 56        super().__init__(  # initialize the KrakenFuturesWSClient
 57            key=config["key"],
 58            secret=config["secret"],
 59        )
 60        self.__config: dict = config
 61
 62        self.__user: User = User(key=config["key"], secret=config["secret"])
 63        self.__trade: Trade = Trade(key=config["key"], secret=config["secret"])
 64        self.__market: Market = Market(key=config["key"], secret=config["secret"])
 65        self.__funding: Funding = Funding(key=config["key"], secret=config["secret"])
 66
 67    async def on_message(self: TradingBot, message: list | dict) -> None:
 68        """Receives all messages that came form the websocket feed(s)"""
 69        logging.info(message)
 70
 71        # == apply your trading strategy here ==
 72
 73        # Call functions of `self.__trade` and other clients if conditions met …
 74        # print(
 75        #     self.__trade.create_order(
 76        #         orderType='lmt',
 77        #         size=2,
 78        #         symbol='PI_XBTUSD',
 79        #         side='buy',
 80        #         limitPrice=10000
 81        #     )
 82        # )
 83
 84        # You can also un-/subscribe here using `self.subscribe(...)` or
 85        # `self.unsubscribe(...)`
 86        # … more can be found in the documentation
 87        #        (https://python-kraken-sdk.readthedocs.io/en/stable/).
 88
 89    # Add more functions to customize the trading strategy …
 90
 91    def save_exit(self: TradingBot, reason: str = "") -> None:
 92        """Controlled shutdown of the strategy"""
 93        logging.warning("Save exit triggered, reason: %s", reason)
 94        # some ideas:
 95        #   * save the bots data
 96        #   * maybe close trades
 97        #   * enable dead man's switch
 98        sys.exit(1)
 99
100
101class ManagedBot:
102    """
103    Class to manage the trading strategy
104
105    … subscribes to desired feeds, instantiates the strategy and runs as long
106    as there is no error.
107
108    ====== P A R A M E T E R S ======
109    config: dict
110        bot configuration like: {
111            'key' 'kraken-futures-key',
112            'secret': 'kraken-secret-key',
113            'products': ['PI_XBTUSD']
114        }
115    """
116
117    def __init__(self: ManagedBot, config: dict) -> None:
118        self.__config: dict = config
119        self.__trading_strategy: Optional[TradingBot] = None
120
121    def run(self: ManagedBot) -> None:
122        """Runner function"""
123        if not self.__check_credentials():
124            sys.exit(1)
125
126        try:
127            asyncio.run(self.__main())
128        except KeyboardInterrupt:
129            self.save_exit(reason="KeyboardInterrupt")
130        else:
131            self.save_exit(reason="Asyncio loop left")
132
133    async def __main(self: ManagedBot) -> None:
134        """
135        Instantiates the trading strategy/algorithm and subscribes to the
136        desired websocket feeds. Run the loop while no exception occur.
137
138        The variable `exception_occur` which is an attribute of the
139        KrakenFuturesWSClient can be set individually but is also being set to
140        `True` if the websocket connection has some fatal error. This is used to
141        exit the asyncio loop - but you can also apply your own reconnect rules.
142        """
143        self.__trading_strategy = TradingBot(config=self.__config)
144
145        await self.__trading_strategy.subscribe(
146            feed="ticker",
147            products=self.__config["products"],
148        )
149        await self.__trading_strategy.subscribe(
150            feed="book",
151            products=self.__config["products"],
152        )
153
154        await self.__trading_strategy.subscribe(feed="fills")
155        await self.__trading_strategy.subscribe(feed="open_positions")
156        await self.__trading_strategy.subscribe(feed="open_orders")
157        await self.__trading_strategy.subscribe(feed="balances")
158
159        while not self.__trading_strategy.exception_occur:
160            try:
161                # check if the strategy feels good
162                # maybe send a status update every day
163                # …
164                pass
165
166            except Exception as exc:
167                message: str = f"Exception in main: {exc} {traceback.format_exc()}"
168                logging.error(message)
169                self.__trading_strategy.save_exit(reason=message)
170
171            await asyncio.sleep(6)
172        self.__trading_strategy.save_exit(
173            reason="Left main loop because of exception in strategy.",
174        )
175
176    def __check_credentials(self: ManagedBot) -> bool:
177        """Checks the user credentials and the connection to Kraken"""
178        try:
179            User(self.__config["key"], self.__config["secret"]).get_wallets()
180            logging.info("Client credentials are valid.")
181            return True
182        except urllib3.exceptions.MaxRetryError:
183            logging.error("MaxRetryError, cannot connect.")
184            return False
185        except requests.exceptions.ConnectionError:
186            logging.error("ConnectionError, Kraken not available.")
187            return False
188        except KrakenAuthenticationError:
189            logging.error("Invalid credentials!")
190            return False
191
192    def save_exit(self: ManagedBot, reason: str = "") -> None:
193        """Calls the save exit function of the trading strategy"""
194        print(f"Save exit triggered - {reason}")
195        if self.__trading_strategy is not None:
196            self.__trading_strategy.save_exit(reason=reason)
197        else:
198            sys.exit(1)
199
200
201def main() -> None:
202    """Example main - load environment variables and run the strategy."""
203
204    managed_bot: ManagedBot = ManagedBot(
205        config={
206            "key": os.getenv("FUTURES_API_KEY"),
207            "secret": os.getenv("FUTURES_SECRET_KEY"),
208            "products": ["PI_XBTUSD", "PF_SOLUSD"],
209        },
210    )
211
212    try:
213        managed_bot.run()
214    except Exception:
215        managed_bot.save_exit(
216            reason=f"manageBot.run() has ended: {traceback.format_exc()}",
217        )
218
219
220if __name__ == "__main__":
221    main()