• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

freqtrade / freqtrade / 9394559170

26 Apr 2024 06:36AM UTC coverage: 94.656% (-0.02%) from 94.674%
9394559170

push

github

xmatthias
Loader should be passed as kwarg for clarity

20280 of 21425 relevant lines covered (94.66%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.56
/ft_client/freqtrade_client/ft_rest_client.py
1
"""
2
A Rest Client for Freqtrade bot
3

4
Should not import anything from freqtrade,
5
so it can be used as a standalone script, and can be installed independently.
6
"""
7

8
import json
1✔
9
import logging
1✔
10
from typing import Any, Dict, List, Optional, Union
1✔
11
from urllib.parse import urlencode, urlparse, urlunparse
1✔
12

13
import requests
1✔
14
from requests.exceptions import ConnectionError
1✔
15

16

17
logger = logging.getLogger("ft_rest_client")
1✔
18

19
ParamsT = Optional[Dict[str, Any]]
1✔
20
PostDataT = Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]
1✔
21

22

23
class FtRestClient:
1✔
24

25
    def __init__(self, serverurl, username=None, password=None, *,
1✔
26
                 pool_connections=10, pool_maxsize=10):
27

28
        self._serverurl = serverurl
1✔
29
        self._session = requests.Session()
1✔
30

31
        # allow configuration of pool
32
        adapter = requests.adapters.HTTPAdapter(
1✔
33
            pool_connections=pool_connections,
34
            pool_maxsize=pool_maxsize
35
        )
36
        self._session.mount('http://', adapter)
1✔
37

38
        self._session.auth = (username, password)
1✔
39

40
    def _call(self, method, apipath, params: Optional[dict] = None, data=None, files=None):
1✔
41

42
        if str(method).upper() not in ('GET', 'POST', 'PUT', 'DELETE'):
1✔
43
            raise ValueError(f'invalid method <{method}>')
1✔
44
        basepath = f"{self._serverurl}/api/v1/{apipath}"
1✔
45

46
        hd = {"Accept": "application/json",
1✔
47
              "Content-Type": "application/json"
48
              }
49

50
        # Split url
51
        schema, netloc, path, par, query, fragment = urlparse(basepath)
1✔
52
        # URLEncode query string
53
        query = urlencode(params) if params else ""
1✔
54
        # recombine url
55
        url = urlunparse((schema, netloc, path, par, query, fragment))
1✔
56

57
        try:
1✔
58
            resp = self._session.request(method, url, headers=hd, data=json.dumps(data))
1✔
59
            # return resp.text
60
            return resp.json()
1✔
61
        except ConnectionError:
1✔
62
            logger.warning("Connection error")
1✔
63

64
    def _get(self, apipath, params: ParamsT = None):
1✔
65
        return self._call("GET", apipath, params=params)
1✔
66

67
    def _delete(self, apipath, params: ParamsT = None):
1✔
68
        return self._call("DELETE", apipath, params=params)
1✔
69

70
    def _post(self, apipath, params: ParamsT = None, data: PostDataT = None):
1✔
71
        return self._call("POST", apipath, params=params, data=data)
1✔
72

73
    def start(self):
1✔
74
        """Start the bot if it's in the stopped state.
75

76
        :return: json object
77
        """
78
        return self._post("start")
1✔
79

80
    def stop(self):
1✔
81
        """Stop the bot. Use `start` to restart.
82

83
        :return: json object
84
        """
85
        return self._post("stop")
1✔
86

87
    def stopbuy(self):
1✔
88
        """Stop buying (but handle sells gracefully). Use `reload_config` to reset.
89

90
        :return: json object
91
        """
92
        return self._post("stopbuy")
1✔
93

94
    def reload_config(self):
1✔
95
        """Reload configuration.
96

97
        :return: json object
98
        """
99
        return self._post("reload_config")
1✔
100

101
    def balance(self):
1✔
102
        """Get the account balance.
103

104
        :return: json object
105
        """
106
        return self._get("balance")
1✔
107

108
    def count(self):
1✔
109
        """Return the amount of open trades.
110

111
        :return: json object
112
        """
113
        return self._get("count")
1✔
114

115
    def entries(self, pair=None):
1✔
116
        """Returns List of dicts containing all Trades, based on buy tag performance
117
        Can either be average for all pairs or a specific pair provided
118

119
        :return: json object
120
        """
121
        return self._get("entries", params={"pair": pair} if pair else None)
1✔
122

123
    def exits(self, pair=None):
1✔
124
        """Returns List of dicts containing all Trades, based on exit reason performance
125
        Can either be average for all pairs or a specific pair provided
126

127
        :return: json object
128
        """
129
        return self._get("exits", params={"pair": pair} if pair else None)
1✔
130

131
    def mix_tags(self, pair=None):
1✔
132
        """Returns List of dicts containing all Trades, based on entry_tag + exit_reason performance
133
        Can either be average for all pairs or a specific pair provided
134

135
        :return: json object
136
        """
137
        return self._get("mix_tags", params={"pair": pair} if pair else None)
1✔
138

139
    def locks(self):
1✔
140
        """Return current locks
141

142
        :return: json object
143
        """
144
        return self._get("locks")
1✔
145

146
    def delete_lock(self, lock_id):
1✔
147
        """Delete (disable) lock from the database.
148

149
        :param lock_id: ID for the lock to delete
150
        :return: json object
151
        """
152
        return self._delete(f"locks/{lock_id}")
1✔
153

154
    def lock_add(self, pair: str, until: str, side: str = '*', reason: str = ''):
1✔
155
        """Lock pair
156

157
        :param pair: Pair to lock
158
        :param until: Lock until this date (format "2024-03-30 16:00:00Z")
159
        :param side: Side to lock (long, short, *)
160
        :param reason: Reason for the lock
161
        :return: json object
162
        """
163
        data = [
1✔
164
            {
165
                "pair": pair,
166
                "until": until,
167
                "side": side,
168
                "reason": reason
169
            }
170
        ]
171
        return self._post("locks", data=data)
1✔
172

173
    def daily(self, days=None):
1✔
174
        """Return the profits for each day, and amount of trades.
175

176
        :return: json object
177
        """
178
        return self._get("daily", params={"timescale": days} if days else None)
1✔
179

180
    def weekly(self, weeks=None):
1✔
181
        """Return the profits for each week, and amount of trades.
182

183
        :return: json object
184
        """
185
        return self._get("weekly", params={"timescale": weeks} if weeks else None)
1✔
186

187
    def monthly(self, months=None):
1✔
188
        """Return the profits for each month, and amount of trades.
189

190
        :return: json object
191
        """
192
        return self._get("monthly", params={"timescale": months} if months else None)
1✔
193

194
    def edge(self):
1✔
195
        """Return information about edge.
196

197
        :return: json object
198
        """
199
        return self._get("edge")
1✔
200

201
    def profit(self):
1✔
202
        """Return the profit summary.
203

204
        :return: json object
205
        """
206
        return self._get("profit")
1✔
207

208
    def stats(self):
1✔
209
        """Return the stats report (durations, sell-reasons).
210

211
        :return: json object
212
        """
213
        return self._get("stats")
1✔
214

215
    def performance(self):
1✔
216
        """Return the performance of the different coins.
217

218
        :return: json object
219
        """
220
        return self._get("performance")
1✔
221

222
    def status(self):
1✔
223
        """Get the status of open trades.
224

225
        :return: json object
226
        """
227
        return self._get("status")
1✔
228

229
    def version(self):
1✔
230
        """Return the version of the bot.
231

232
        :return: json object containing the version
233
        """
234
        return self._get("version")
1✔
235

236
    def show_config(self):
1✔
237
        """ Returns part of the configuration, relevant for trading operations.
238
        :return: json object containing the version
239
        """
240
        return self._get("show_config")
1✔
241

242
    def ping(self):
1✔
243
        """simple ping"""
244
        configstatus = self.show_config()
1✔
245
        if not configstatus:
1✔
246
            return {"status": "not_running"}
×
247
        elif configstatus['state'] == "running":
1✔
248
            return {"status": "pong"}
×
249
        else:
250
            return {"status": "not_running"}
1✔
251

252
    def logs(self, limit=None):
1✔
253
        """Show latest logs.
254

255
        :param limit: Limits log messages to the last <limit> logs. No limit to get the entire log.
256
        :return: json object
257
        """
258
        return self._get("logs", params={"limit": limit} if limit else 0)
1✔
259

260
    def trades(self, limit=None, offset=None):
1✔
261
        """Return trades history, sorted by id
262

263
        :param limit: Limits trades to the X last trades. Max 500 trades.
264
        :param offset: Offset by this amount of trades.
265
        :return: json object
266
        """
267
        params = {}
1✔
268
        if limit:
1✔
269
            params['limit'] = limit
1✔
270
        if offset:
1✔
271
            params['offset'] = offset
1✔
272
        return self._get("trades", params)
1✔
273

274
    def trade(self, trade_id):
1✔
275
        """Return specific trade
276

277
        :param trade_id: Specify which trade to get.
278
        :return: json object
279
        """
280
        return self._get(f"trade/{trade_id}")
1✔
281

282
    def delete_trade(self, trade_id):
1✔
283
        """Delete trade from the database.
284
        Tries to close open orders. Requires manual handling of this asset on the exchange.
285

286
        :param trade_id: Deletes the trade with this ID from the database.
287
        :return: json object
288
        """
289
        return self._delete(f"trades/{trade_id}")
1✔
290

291
    def cancel_open_order(self, trade_id):
1✔
292
        """Cancel open order for trade.
293

294
        :param trade_id: Cancels open orders for this trade.
295
        :return: json object
296
        """
297
        return self._delete(f"trades/{trade_id}/open-order")
1✔
298

299
    def whitelist(self):
1✔
300
        """Show the current whitelist.
301

302
        :return: json object
303
        """
304
        return self._get("whitelist")
1✔
305

306
    def blacklist(self, *args):
1✔
307
        """Show the current blacklist.
308

309
        :param add: List of coins to add (example: "BNB/BTC")
310
        :return: json object
311
        """
312
        if not args:
1✔
313
            return self._get("blacklist")
1✔
314
        else:
315
            return self._post("blacklist", data={"blacklist": args})
1✔
316

317
    def forcebuy(self, pair, price=None):
1✔
318
        """Buy an asset.
319

320
        :param pair: Pair to buy (ETH/BTC)
321
        :param price: Optional - price to buy
322
        :return: json object of the trade
323
        """
324
        data = {"pair": pair,
1✔
325
                "price": price
326
                }
327
        return self._post("forcebuy", data=data)
1✔
328

329
    def forceenter(self, pair, side, price=None):
1✔
330
        """Force entering a trade
331

332
        :param pair: Pair to buy (ETH/BTC)
333
        :param side: 'long' or 'short'
334
        :param price: Optional - price to buy
335
        :return: json object of the trade
336
        """
337
        data = {"pair": pair,
1✔
338
                "side": side,
339
                }
340
        if price:
1✔
341
            data['price'] = price
1✔
342
        return self._post("forceenter", data=data)
1✔
343

344
    def forceexit(self, tradeid, ordertype=None, amount=None):
1✔
345
        """Force-exit a trade.
346

347
        :param tradeid: Id of the trade (can be received via status command)
348
        :param ordertype: Order type to use (must be market or limit)
349
        :param amount: Amount to sell. Full sell if not given
350
        :return: json object
351
        """
352

353
        return self._post("forceexit", data={
1✔
354
            "tradeid": tradeid,
355
            "ordertype": ordertype,
356
            "amount": amount,
357
            })
358

359
    def strategies(self):
1✔
360
        """Lists available strategies
361

362
        :return: json object
363
        """
364
        return self._get("strategies")
1✔
365

366
    def strategy(self, strategy):
1✔
367
        """Get strategy details
368

369
        :param strategy: Strategy class name
370
        :return: json object
371
        """
372
        return self._get(f"strategy/{strategy}")
1✔
373

374
    def pairlists_available(self):
1✔
375
        """Lists available pairlist providers
376

377
        :return: json object
378
        """
379
        return self._get("pairlists/available")
1✔
380

381
    def plot_config(self):
1✔
382
        """Return plot configuration if the strategy defines one.
383

384
        :return: json object
385
        """
386
        return self._get("plot_config")
1✔
387

388
    def available_pairs(self, timeframe=None, stake_currency=None):
1✔
389
        """Return available pair (backtest data) based on timeframe / stake_currency selection
390

391
        :param timeframe: Only pairs with this timeframe available.
392
        :param stake_currency: Only pairs that include this timeframe
393
        :return: json object
394
        """
395
        return self._get("available_pairs", params={
1✔
396
            "stake_currency": stake_currency if timeframe else '',
397
            "timeframe": timeframe if timeframe else '',
398
        })
399

400
    def pair_candles(self, pair, timeframe, limit=None):
1✔
401
        """Return live dataframe for <pair><timeframe>.
402

403
        :param pair: Pair to get data for
404
        :param timeframe: Only pairs with this timeframe available.
405
        :param limit: Limit result to the last n candles.
406
        :return: json object
407
        """
408
        params = {
1✔
409
            "pair": pair,
410
            "timeframe": timeframe,
411
        }
412
        if limit:
1✔
413
            params['limit'] = limit
1✔
414
        return self._get("pair_candles", params=params)
1✔
415

416
    def pair_history(self, pair, timeframe, strategy, timerange=None, freqaimodel=None):
1✔
417
        """Return historic, analyzed dataframe
418

419
        :param pair: Pair to get data for
420
        :param timeframe: Only pairs with this timeframe available.
421
        :param strategy: Strategy to analyze and get values for
422
        :param freqaimodel: FreqAI model to use for analysis
423
        :param timerange: Timerange to get data for (same format than --timerange endpoints)
424
        :return: json object
425
        """
426
        return self._get("pair_history", params={
1✔
427
            "pair": pair,
428
            "timeframe": timeframe,
429
            "strategy": strategy,
430
            "freqaimodel": freqaimodel,
431
            "timerange": timerange if timerange else '',
432
        })
433

434
    def sysinfo(self):
1✔
435
        """Provides system information (CPU, RAM usage)
436

437
        :return: json object
438
        """
439
        return self._get("sysinfo")
1✔
440

441
    def health(self):
1✔
442
        """Provides a quick health check of the running bot.
443

444
        :return: json object
445
        """
446
        return self._get("health")
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc