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