Futures Trading Bot Example
The template presented below serves as a starting point for the development of a trading algorithm for trading futures contracts on the cryptocurrency platform Kraken using the python-kraken-sdk.
The ManagedBot class is a helper class that instantiates the trading strategy. The trading strategy
can be implemented in the TradingBot class. This class has access to all REST clients and gets
gets via the on_message method all messages that are sent via the subscribed websocket feeds.
This is the starting point from which a strategy can be implemented and applied.
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"""Module that provides an example Futures trading bot data structure."""
8
9from __future__ import annotations
10
11import asyncio
12import logging
13import logging.config
14import os
15import sys
16import traceback
17from typing import Optional, Union
18
19import requests
20import urllib3
21
22from kraken.exceptions import KrakenException
23from kraken.futures import Funding, KrakenFuturesWSClient, Market, Trade, User
24
25logging.basicConfig(
26 format="%(asctime)s %(module)s,line: %(lineno)d %(levelname)8s | %(message)s",
27 datefmt="%Y/%m/%d %H:%M:%S",
28 level=logging.INFO,
29)
30logging.getLogger().setLevel(logging.INFO)
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 events from the websocket feed
40 * Decisions can be made based on these events
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__(
55 key=config["key"], secret=config["secret"]
56 ) # initialize the KrakenFuturesWSClient
57 self.__config: dict = config
58
59 self.__user: User = User(key=config["key"], secret=config["secret"])
60 self.__trade: Trade = Trade(key=config["key"], secret=config["secret"])
61 self.__market: Market = Market(key=config["key"], secret=config["secret"])
62 self.__funding: Funding = Funding(key=config["key"], secret=config["secret"])
63
64 async def on_message(self: "TradingBot", event: Union[list, dict]) -> None:
65 """Receives all events that came form the websocket feed(s)"""
66 logging.info(event)
67 # ... apply your trading strategy here
68
69 # call functions from self.__trade and other clients if conditions met...
70
71 # response = self.__trade.create_order(
72 # orderType='lmt',
73 # size=2,
74 # symbol='PI_XBTUSD',
75 # side='buy',
76 # limitPrice=10000
77 # )
78 # ...
79
80 # you can also un-/subscribe here using `self.subscribe(...)` or `self.unsubscribe(...)`
81
82 # add more functions to customize the trading strategy
83 # ...
84 # ...
85
86 def save_exit(self: "TradingBot", reason: Optional[str] = "") -> None:
87 """Controlled shutdown of the strategy"""
88 logging.warning(f"Save exit triggered, reason: {reason}")
89 # ideas:
90 # * save the bots data
91 # * maybe close trades
92 # * enable dead man's switch
93 sys.exit(1)
94
95
96class ManagedBot:
97 """
98 Class to manage the trading strategy
99
100 Subscribes to desired feeds, instantiates the strategy and runs until condition met
101
102 ====== P A R A M E T E R S ======
103 config: dict
104 bot configuration like: {
105 'key' 'kraken-futures-key',
106 'secret': 'kraken-secret-key',
107 'products': ['PI_XBTUSD']
108 }
109 """
110
111 def __init__(self: "ManagedBot", config: dict) -> None:
112 self.__config: dict = config
113 self.__trading_strategy: Optional[TradingBot] = None
114
115 def run(self: "ManagedBot") -> None:
116 """Runner function"""
117 if not self.__check_credentials():
118 sys.exit(1)
119
120 try:
121 asyncio.run(self.__main())
122 except KeyboardInterrupt:
123 pass
124 finally:
125 if self.__trading_strategy is not None:
126 self.__trading_strategy.save_exit(reason="Asyncio loop left")
127
128 async def __main(self: "ManagedBot") -> None:
129 """
130 Instantiates the trading strategy/algorithm and subscribes to the
131 desired websocket feeds. Run the loop while no exception occur.
132
133 Thi variable `exception_occur` which is an attribute of the KrakenFuturesWSClient
134 can be set individually but is also being set to `True` if the websocket connection
135 has some fatal error. This is used to exit the asyncio loop.
136 """
137 self.__trading_strategy = TradingBot(config=self.__config)
138
139 await self.__trading_strategy.subscribe(
140 feed="ticker", products=self.__config["products"]
141 )
142 await self.__trading_strategy.subscribe(
143 feed="book", products=self.__config["products"]
144 )
145
146 await self.__trading_strategy.subscribe(feed="fills")
147 await self.__trading_strategy.subscribe(feed="open_positions")
148 await self.__trading_strategy.subscribe(feed="open_orders")
149 await self.__trading_strategy.subscribe(feed="balances")
150
151 while not self.__trading_strategy.exception_occur:
152 try:
153 # check if bot feels good
154 # maybe send a status update every day
155 # ...
156 pass
157
158 except Exception as exc:
159 message: str = f"Exception in main: {exc} {traceback.format_exc()}"
160 logging.error(message)
161 self.__trading_strategy.save_exit(reason=message)
162
163 await asyncio.sleep(6)
164 self.__trading_strategy.save_exit(
165 reason="Left main loop because of exception in strategy."
166 )
167 return
168
169 def __check_credentials(self: "ManagedBot") -> bool:
170 """Checks the user credentials and the connection to Kraken"""
171 try:
172 User(self.__config["key"], self.__config["secret"]).get_wallets()
173 logging.info("Client credentials are valid")
174 return True
175 except urllib3.exceptions.MaxRetryError:
176 logging.error("MaxRetryError, cannot connect.")
177 return False
178 except requests.exceptions.ConnectionError:
179 logging.error("ConnectionError, Kraken not available.")
180 return False
181 except KrakenException.KrakenAuthenticationError:
182 logging.error("Invalid credentials!")
183 return False
184
185 def save_exit(self: "ManagedBot", reason: str = "") -> None:
186 """Calls the save exit function of the trading strategy"""
187 print(f"Save exit triggered - {reason}")
188 if self.__trading_strategy is not None:
189 self.__trading_strategy.save_exit(reason=reason)
190
191
192def main() -> None:
193 """Main"""
194 bot_config: dict = {
195 "key": os.getenv("FUTURES_API_KEY"),
196 "secret": os.getenv("FUTURES_SECRET_KEY"),
197 "products": ["PI_XBTUSD", "PF_SOLUSD"],
198 }
199 try:
200 managed_bot: ManagedBot = ManagedBot(config=bot_config)
201 managed_bot.run()
202 except Exception:
203 managed_bot.save_exit(
204 reason=f"manageBot.run() has ended: {traceback.format_exc()}"
205 )
206
207
208if __name__ == "__main__":
209 main()