Spot Trading Bot Templates
The templates presented below serve as starting points for the development of a trading algorithms for Spot trading on the cryptocurrency exchange Kraken using the python-kraken-sdk.
The trading strategy can be implemented using the TradingBot
class. This
class has access to all REST clients and receives all messages that are sent by
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# ruff: noqa: RUF027
6"""
7Module that provides a template to build a Spot trading algorithm using the
8python-kraken-sdk and Kraken Spot websocket API v2.
9"""
10
11from __future__ import annotations
12
13import asyncio
14import logging
15import logging.config
16import os
17import sys
18import traceback
19from typing import Any, Optional
20
21import requests
22import urllib3
23
24from kraken.exceptions import KrakenAuthenticationError # , KrakenPermissionDeniedError
25from kraken.spot import Funding, KrakenSpotWSClientV2, Market, Staking, Trade, User
26
27logging.basicConfig(
28 format="%(asctime)s %(module)s,line: %(lineno)d %(levelname)8s | %(message)s",
29 datefmt="%Y/%m/%d %H:%M:%S",
30 level=logging.INFO,
31)
32logging.getLogger("requests").setLevel(logging.WARNING)
33logging.getLogger("urllib3").setLevel(logging.WARNING)
34
35
36class TradingBot(KrakenSpotWSClientV2):
37 """
38 Class that implements the trading strategy
39
40 * The on_message function gets all messages sent by the websocket feeds.
41 * Decisions can be made based on these messages
42 * Can place trades using the self.__trade client or self.send_message
43 * Do everything you want
44
45 ====== P A R A M E T E R S ======
46 config: dict
47 configuration like: {
48 "key": "kraken-spot-key",
49 "secret": "kraken-spot-secret",
50 "pairs": ["DOT/USD", "BTC/USD"],
51 }
52 """
53
54 def __init__(self: TradingBot, config: dict, **kwargs: Any) -> None:
55 super().__init__( # initialize the KrakenSpotWSClientV2
56 key=config["key"],
57 secret=config["secret"],
58 **kwargs,
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 self.__staking: Staking = Staking(key=config["key"], secret=config["secret"])
67
68 async def on_message(self: TradingBot, message: dict) -> None:
69 """Receives all messages of the websocket connection(s)"""
70 if message.get("method") == "pong" or message.get("channel") == "heartbeat":
71 return
72 if "error" in message:
73 # handle exceptions/errors sent by websocket connection …
74 pass
75
76 logging.info(message)
77
78 # == apply your trading strategy here ==
79
80 # Call functions of `self.__trade` and other clients if conditions met …
81 # try:
82 # print(self.__trade.create_order(
83 # ordertype='limit',
84 # side='buy',
85 # volume=2,
86 # pair='XBTUSD',
87 # price=12000
88 # ))
89 # except KrakenPermissionDeniedError:
90 # # … handle exceptions
91 # pass
92
93 # The spot websocket client also allow sending orders via websockets
94 # this is way faster than using REST endpoints.
95 # await self.send_message(
96 # message={
97 # "method": "add_order",
98 # "params": {
99 # "limit_price": 1234.56,
100 # "order_type": "limit",
101 # "order_userref": 123456789,
102 # "order_qty": 1.0,
103 # "side": "buy",
104 # "symbol": "BTC/USD",
105 # "validate": True,
106 # },
107 # }
108 # )
109
110 # You can also un-/subscribe here using `self.subscribe(...)` or
111 # `self.unsubscribe(...)`.
112 #
113 # … more can be found in the documentation
114 # (https://python-kraken-sdk.readthedocs.io/en/stable/).
115
116 # Add more functions to customize the trading strategy …
117
118 def save_exit(self: TradingBot, reason: Optional[str] = "") -> None:
119 """controlled shutdown of the strategy"""
120 logging.warning(
121 "Save exit triggered, reason: {reason}",
122 extra={"reason": reason},
123 )
124 # some ideas:
125 # * save the current data
126 # * maybe close trades
127 # * enable dead man's switch
128 sys.exit(1)
129
130
131class Manager:
132 """
133 Class to manage the trading strategy
134
135 … subscribes to desired feeds, instantiates the strategy and runs as long
136 as there is no error.
137
138 ====== P A R A M E T E R S ======
139 config: dict
140 configuration like: {
141 "key" "kraken-spot-key",
142 "secret": "kraken-secret-key",
143 "pairs": ["DOT/USD", "BTC/USD"],
144 }
145 """
146
147 def __init__(self: Manager, config: dict):
148 self.__config: dict = config
149 self.__trading_strategy: Optional[TradingBot] = None
150
151 def run(self: Manager) -> None:
152 """Starts the event loop and bot"""
153 if not self.__check_credentials():
154 sys.exit(1)
155
156 try:
157 asyncio.run(self.__main())
158 except KeyboardInterrupt:
159 self.save_exit(reason="KeyboardInterrupt")
160 else:
161 self.save_exit(reason="Asyncio loop left")
162
163 async def __main(self: Manager) -> None:
164 """
165 Instantiates the trading strategy and subscribes to the desired
166 websocket feeds. While no exception within the strategy occur run the
167 loop.
168
169 The variable `exception_occur` which is an attribute of the
170 KrakenSpotWSClientV2 can be set individually but is also being set to
171 `True` if the websocket connection has some fatal error. This is used to
172 exit the asyncio loop - but you can also apply your own reconnect rules.
173 """
174 self.__trading_strategy = TradingBot(config=self.__config)
175
176 await self.__trading_strategy.subscribe(
177 params={"channel": "ticker", "symbol": self.__config["pairs"]},
178 )
179 await self.__trading_strategy.subscribe(
180 params={
181 "channel": "ohlc",
182 "interval": 15,
183 "symbol": self.__config["pairs"],
184 },
185 )
186
187 await self.__trading_strategy.subscribe(params={"channel": "executions"})
188
189 while not self.__trading_strategy.exception_occur:
190 try:
191 # check if the algorithm feels good
192 # maybe send a status update every day via Telegram or Mail
193 # …
194 pass
195
196 except Exception as exc:
197 message: str = f"Exception in main: {exc} {traceback.format_exc()}"
198 logging.error(message)
199 self.__trading_strategy.save_exit(reason=message)
200
201 await asyncio.sleep(6)
202 self.__trading_strategy.save_exit(
203 reason="Left main loop because of exception in strategy.",
204 )
205
206 def __check_credentials(self: Manager) -> bool:
207 """Checks the user credentials and the connection to Kraken"""
208 try:
209 User(self.__config["key"], self.__config["secret"]).get_account_balance()
210 logging.info("Client credentials are valid.")
211 return True
212 except urllib3.exceptions.MaxRetryError:
213 logging.error("MaxRetryError, cannot connect.")
214 return False
215 except requests.exceptions.ConnectionError:
216 logging.error("ConnectionError, Kraken not available.")
217 return False
218 except KrakenAuthenticationError:
219 logging.error("Invalid credentials!")
220 return False
221
222 def save_exit(self: Manager, reason: str = "") -> None:
223 """Invoke the save exit function of the trading strategy"""
224 print(f"Save exit triggered - {reason}")
225 if self.__trading_strategy is not None:
226 self.__trading_strategy.save_exit(reason=reason)
227 else:
228 sys.exit(1)
229
230
231def main() -> None:
232 """Example main - load environment variables and run the strategy."""
233 manager: Manager = Manager(
234 config={
235 "key": os.getenv("SPOT_API_KEY"),
236 "secret": os.getenv("SPOT_SECRET_KEY"),
237 "pairs": ["DOT/USD", "BTC/USD"],
238 },
239 )
240
241 try:
242 manager.run()
243 except Exception:
244 manager.save_exit(
245 reason=f"manageBot.run() has ended: {traceback.format_exc()}",
246 )
247
248
249if __name__ == "__main__":
250 main()
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (C) 2023 Benjamin Thomas Schwertfeger
4# GitHub: https://github.com/btschwertfeger
5# ruff: noqa: RUF027
6
7"""
8Module that provides a template to build a Spot trading algorithm using the
9python-kraken-sdk and Kraken Spot websocket API v1.
10"""
11
12from __future__ import annotations
13
14import asyncio
15import logging
16import logging.config
17import os
18import sys
19import traceback
20from typing import Optional, Union
21
22import requests
23import urllib3
24
25from kraken.exceptions import KrakenAuthenticationError # , KrakenPermissionDeniedError
26from kraken.spot import Funding, KrakenSpotWSClientV1, Market, Staking, 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(KrakenSpotWSClientV1):
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 or self.send_message
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-spot-key",
50 "secret": "kraken-spot-secret",
51 "pairs": ["DOT/USD", "BTC/USD"],
52 }
53 """
54
55 def __init__(self: TradingBot, config: dict) -> None:
56 super().__init__( # initialize the KrakenSpotWSClientV1
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 self.__staking: Staking = Staking(key=config["key"], secret=config["secret"])
67
68 async def on_message(self: TradingBot, message: Union[dict, list]) -> None:
69 """Receives all messages of the websocket connection(s)"""
70 if isinstance(message, dict) and "event" in message:
71 if message["event"] in {"heartbeat", "pong"}:
72 return
73 if "error" in message:
74 # handle exceptions/errors sent by websocket connection …
75 pass
76
77 logging.info(message)
78
79 # == apply your trading strategy here ==
80
81 # Call functions of `self.__trade` and other clients if conditions met …
82 # try:
83 # print(self.__trade.create_order(
84 # ordertype='limit',
85 # side='buy',
86 # volume=2,
87 # pair='XBTUSD',
88 # price=12000
89 # ))
90 # except KrakenPermissionDeniedError:
91 # # … handle exceptions
92 # pass
93
94 # The spot websocket client also allow sending orders via websockets
95 # this is way faster than using REST endpoints.
96 # await self.create_order(
97 # ordertype='limit',
98 # side='buy',
99 # pair='BTC/EUR',
100 # price=20000,
101 # volume=200
102 # )
103
104 # You can also un-/subscribe here using `self.subscribe(...)` or
105 # `self.unsubscribe(...)`.
106 #
107 # … more can be found in the documentation
108 # (https://python-kraken-sdk.readthedocs.io/en/stable/)
109
110 # Add more functions to customize the trading strategy …
111
112 def save_exit(self: TradingBot, reason: Optional[str] = "") -> None:
113 """controlled shutdown of the strategy"""
114 logging.warning(
115 "Save exit triggered, reason: {reason}",
116 extra={"reason": reason},
117 )
118 # some ideas:
119 # * save the bots data
120 # * maybe close trades
121 # * enable dead man's switch
122 sys.exit(1)
123
124
125class Manager:
126 """
127 Class to manage the trading strategy
128
129 … subscribes to desired feeds, instantiates the strategy and runs as long
130 as there is no error.
131
132 ====== P A R A M E T E R S ======
133 config: dict
134 configuration like: {
135 "key": "kraken-spot-key",
136 "secret": "kraken-spot-secret",
137 "pairs": ["DOT/USD", "BTC/USD"],
138 }
139 """
140
141 def __init__(self: Manager, config: dict):
142 self.__config: dict = config
143 self.__trading_strategy: Optional[TradingBot] = None
144
145 def run(self: Manager) -> None:
146 """Starts the event loop and bot"""
147 if not self.__check_credentials():
148 sys.exit(1)
149
150 try:
151 asyncio.run(self.__main())
152 except KeyboardInterrupt:
153 self.save_exit(reason="KeyboardInterrupt")
154 else:
155 self.save_exit(reason="Asyncio loop left")
156
157 async def __main(self: Manager) -> None:
158 """
159 Instantiates the trading strategy (bot) and subscribes to the
160 desired websocket feeds. While no exception within the strategy occur
161 run the loop.
162
163 This variable `exception_occur` which is an attribute of the
164 KrakenSpotWSClientV1 can be set individually but is also being set to
165 `True` if the websocket connection has some fatal error. This is used to
166 exit the asyncio loop - but you can also apply your own reconnect rules.
167 """
168 self.__trading_strategy = TradingBot(config=self.__config)
169
170 await self.__trading_strategy.subscribe(
171 subscription={"name": "ticker"},
172 pair=self.__config["pairs"],
173 )
174 await self.__trading_strategy.subscribe(
175 subscription={"name": "ohlc", "interval": 15},
176 pair=self.__config["pairs"],
177 )
178
179 await self.__trading_strategy.subscribe(subscription={"name": "ownTrades"})
180 await self.__trading_strategy.subscribe(subscription={"name": "openOrders"})
181
182 while not self.__trading_strategy.exception_occur:
183 try:
184 # check if the algorithm feels good
185 # maybe send a status update every day via Telegram or Mail
186 # ..…
187 pass
188
189 except Exception as exc:
190 message: str = f"Exception in main: {exc} {traceback.format_exc()}"
191 logging.error(message)
192 self.__trading_strategy.save_exit(reason=message)
193
194 await asyncio.sleep(6)
195 self.__trading_strategy.save_exit(
196 reason="Left main loop because of exception in strategy.",
197 )
198
199 def __check_credentials(self: Manager) -> bool:
200 """Checks the user credentials and the connection to Kraken"""
201 try:
202 User(self.__config["key"], self.__config["secret"]).get_account_balance()
203 logging.info("Client credentials are valid.")
204 return True
205 except urllib3.exceptions.MaxRetryError:
206 logging.error("MaxRetryError, cannot connect.")
207 return False
208 except requests.exceptions.ConnectionError:
209 logging.error("ConnectionError, Kraken not available.")
210 return False
211 except KrakenAuthenticationError:
212 logging.error("Invalid credentials!")
213 return False
214
215 def save_exit(self: Manager, reason: str = "") -> None:
216 """Invoke the save exit function of the trading strategy"""
217 print(f"Save exit triggered - {reason}")
218 if self.__trading_strategy is not None:
219 self.__trading_strategy.save_exit(reason=reason)
220 else:
221 sys.exit(1)
222
223
224def main() -> None:
225 """Example main - load environment variables and run the strategy."""
226 manager: Manager = Manager(
227 config={
228 "key": os.getenv("SPOT_API_KEY"),
229 "secret": os.getenv("SPOT_SECRET_KEY"),
230 "pairs": ["DOT/USD", "XBT/USD"],
231 },
232 )
233
234 try:
235 manager.run()
236 except Exception:
237 manager.save_exit(
238 reason=f"manageBot.run() has ended: {traceback.format_exc()}",
239 )
240
241
242if __name__ == "__main__":
243 main()