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