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()