stable/scripts/rest_client.py

464 lines
13 KiB
Python
Raw Normal View History

2019-04-04 05:08:24 +00:00
#!/usr/bin/env python3
"""
Simple command line client into RPC commands
Can be used as an alternate to Telegram
2019-04-04 19:07:44 +00:00
Should not import anything from freqtrade,
so it can be used as a standalone script.
2019-04-04 05:08:24 +00:00
"""
2019-04-04 19:07:44 +00:00
import argparse
import inspect
2019-04-09 04:40:15 +00:00
import json
2019-04-04 19:07:44 +00:00
import logging
2020-09-28 17:39:41 +00:00
import re
import sys
2019-04-09 04:40:15 +00:00
from pathlib import Path
2023-01-21 21:48:30 +00:00
from typing import Optional
from urllib.parse import urlencode, urlparse, urlunparse
2019-04-04 19:07:44 +00:00
import rapidjson
2019-04-25 18:32:10 +00:00
import requests
2019-04-04 19:07:44 +00:00
from requests.exceptions import ConnectionError
2020-09-28 17:39:41 +00:00
2019-04-04 19:07:44 +00:00
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)
logger = logging.getLogger("ft_rest_client")
2019-04-04 05:08:24 +00:00
2019-04-25 18:32:10 +00:00
class FtRestClient():
2019-05-25 12:14:09 +00:00
def __init__(self, serverurl, username=None, password=None):
2019-04-25 18:32:10 +00:00
2019-04-26 07:55:52 +00:00
self._serverurl = serverurl
self._session = requests.Session()
self._session.auth = (username, password)
2019-04-25 18:32:10 +00:00
2023-01-21 15:02:07 +00:00
def _call(self, method, apipath, params: Optional[dict] = None, data=None, files=None):
2019-04-25 18:32:10 +00:00
if str(method).upper() not in ('GET', 'POST', 'PUT', 'DELETE'):
2021-11-11 11:06:18 +00:00
raise ValueError(f'invalid method <{method}>')
2019-05-15 05:12:33 +00:00
basepath = f"{self._serverurl}/api/v1/{apipath}"
2019-04-25 18:32:10 +00:00
hd = {"Accept": "application/json",
"Content-Type": "application/json"
}
# Split url
2019-04-26 07:27:20 +00:00
schema, netloc, path, par, query, fragment = urlparse(basepath)
2019-04-25 18:32:10 +00:00
# URLEncode query string
2019-04-26 08:03:54 +00:00
query = urlencode(params) if params else ""
2019-04-25 18:32:10 +00:00
# recombine url
2019-04-26 07:27:20 +00:00
url = urlunparse((schema, netloc, path, par, query, fragment))
2019-04-26 07:55:52 +00:00
2019-04-25 18:32:10 +00:00
try:
resp = self._session.request(method, url, headers=hd, data=json.dumps(data))
2019-04-26 07:08:03 +00:00
# return resp.text
return resp.json()
2019-04-25 18:32:10 +00:00
except ConnectionError:
logger.warning("Connection error")
2023-01-21 15:02:07 +00:00
def _get(self, apipath, params: Optional[dict] = None):
2019-04-26 07:27:20 +00:00
return self._call("GET", apipath, params=params)
2023-01-21 15:02:07 +00:00
def _delete(self, apipath, params: Optional[dict] = None):
2020-08-04 17:57:28 +00:00
return self._call("DELETE", apipath, params=params)
2023-01-21 15:02:07 +00:00
def _post(self, apipath, params: Optional[dict] = None, data: Optional[dict] = None):
2019-04-26 07:27:20 +00:00
return self._call("POST", apipath, params=params, data=data)
2019-04-26 08:10:01 +00:00
def start(self):
2019-11-20 12:20:39 +00:00
"""Start the bot if it's in the stopped state.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 08:10:01 +00:00
"""
return self._post("start")
def stop(self):
2019-11-20 12:20:39 +00:00
"""Stop the bot. Use `start` to restart.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 08:10:01 +00:00
"""
return self._post("stop")
def stopbuy(self):
"""Stop buying (but handle sells gracefully). Use `reload_config` to reset.
2019-11-20 12:20:39 +00:00
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 08:10:01 +00:00
"""
return self._post("stopbuy")
def reload_config(self):
2019-11-20 12:20:39 +00:00
"""Reload configuration.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 08:10:01 +00:00
"""
return self._post("reload_config")
2019-04-26 08:10:01 +00:00
2019-04-26 07:55:52 +00:00
def balance(self):
2019-11-20 12:20:39 +00:00
"""Get the account balance.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 07:27:20 +00:00
"""
2019-04-26 07:55:52 +00:00
return self._get("balance")
2019-04-26 07:27:20 +00:00
def count(self):
2019-11-20 12:20:39 +00:00
"""Return the amount of open trades.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 07:27:20 +00:00
"""
return self._get("count")
2020-10-17 15:58:07 +00:00
def locks(self):
"""Return current locks
:return: json object
"""
return self._get("locks")
2021-03-01 18:50:39 +00:00
def delete_lock(self, lock_id):
"""Delete (disable) lock from the database.
:param lock_id: ID for the lock to delete
:return: json object
"""
2021-11-11 11:06:18 +00:00
return self._delete(f"locks/{lock_id}")
2021-03-01 18:50:39 +00:00
2019-04-26 07:27:20 +00:00
def daily(self, days=None):
"""Return the profits for each day, and amount of trades.
2019-11-20 12:20:39 +00:00
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 07:27:20 +00:00
"""
return self._get("daily", params={"timescale": days} if days else None)
2019-04-26 08:06:46 +00:00
def edge(self):
2019-11-20 12:20:39 +00:00
"""Return information about edge.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 08:06:46 +00:00
"""
return self._get("edge")
2019-04-26 07:55:52 +00:00
def profit(self):
2019-11-20 12:20:39 +00:00
"""Return the profit summary.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 07:55:52 +00:00
"""
return self._get("profit")
2020-12-07 14:07:08 +00:00
def stats(self):
"""Return the stats report (durations, sell-reasons).
:return: json object
"""
return self._get("stats")
2019-04-26 08:03:54 +00:00
def performance(self):
2019-11-20 12:20:39 +00:00
"""Return the performance of the different coins.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 08:03:54 +00:00
"""
return self._get("performance")
2019-04-26 07:55:52 +00:00
def status(self):
2019-11-20 12:20:39 +00:00
"""Get the status of open trades.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 07:55:52 +00:00
"""
return self._get("status")
2019-04-26 08:06:46 +00:00
def version(self):
2019-11-20 12:20:39 +00:00
"""Return the version of the bot.
2019-06-23 20:10:37 +00:00
:return: json object containing the version
2019-04-26 08:06:46 +00:00
"""
return self._get("version")
2019-11-17 13:56:08 +00:00
def show_config(self):
2023-01-31 06:25:44 +00:00
""" Returns part of the configuration, relevant for trading operations.
2019-11-17 13:56:08 +00:00
:return: json object containing the version
"""
return self._get("show_config")
2021-03-02 08:54:00 +00:00
def ping(self):
"""simple ping"""
configstatus = self.show_config()
if not configstatus:
return {"status": "not_running"}
elif configstatus['state'] == "running":
2021-03-02 08:54:00 +00:00
return {"status": "pong"}
else:
2021-03-02 09:19:33 +00:00
return {"status": "not_running"}
2020-08-14 13:44:52 +00:00
def logs(self, limit=None):
"""Show latest logs.
:param limit: Limits log messages to the last <limit> logs. No limit to get the entire log.
2020-08-14 13:44:52 +00:00
:return: json object
"""
return self._get("logs", params={"limit": limit} if limit else 0)
def trades(self, limit=None, offset=None):
"""Return trades history, sorted by id
2020-04-05 14:14:02 +00:00
:param limit: Limits trades to the X last trades. Max 500 trades.
:param offset: Offset by this amount of trades.
2020-04-05 14:14:02 +00:00
:return: json object
"""
params = {}
if limit:
params['limit'] = limit
if offset:
params['offset'] = offset
return self._get("trades", params)
2020-04-05 14:14:02 +00:00
def trade(self, trade_id):
"""Return specific trade
:param trade_id: Specify which trade to get.
:return: json object
"""
2021-11-11 11:06:18 +00:00
return self._get(f"trade/{trade_id}")
2020-08-04 17:57:28 +00:00
def delete_trade(self, trade_id):
"""Delete trade from the database.
Tries to close open orders. Requires manual handling of this asset on the exchange.
:param trade_id: Deletes the trade with this ID from the database.
:return: json object
"""
2021-11-11 11:06:18 +00:00
return self._delete(f"trades/{trade_id}")
2020-08-04 17:57:28 +00:00
2023-01-31 06:24:19 +00:00
def cancel_open_order(self, trade_id):
"""Cancel open order for trade.
:param trade_id: Cancels open orders for this trade.
:return: json object
"""
return self._delete(f"trades/{trade_id}/open-order")
2019-04-26 07:55:52 +00:00
def whitelist(self):
2019-11-20 12:20:39 +00:00
"""Show the current whitelist.
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 07:55:52 +00:00
"""
return self._get("whitelist")
def blacklist(self, *args):
2019-11-20 12:20:39 +00:00
"""Show the current blacklist.
2019-04-26 07:55:52 +00:00
:param add: List of coins to add (example: "BNB/BTC")
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 07:55:52 +00:00
"""
if not args:
return self._get("blacklist")
else:
return self._post("blacklist", data={"blacklist": args})
2019-04-26 10:50:13 +00:00
def forcebuy(self, pair, price=None):
2019-11-20 12:20:39 +00:00
"""Buy an asset.
2019-04-26 10:50:13 +00:00
:param pair: Pair to buy (ETH/BTC)
:param price: Optional - price to buy
2019-06-23 20:10:37 +00:00
:return: json object of the trade
2019-04-26 10:50:13 +00:00
"""
data = {"pair": pair,
"price": price
}
return self._post("forcebuy", data=data)
def forceenter(self, pair, side, price=None):
2022-01-26 19:07:58 +00:00
"""Force entering a trade
:param pair: Pair to buy (ETH/BTC)
:param side: 'long' or 'short'
:param price: Optional - price to buy
:return: json object of the trade
"""
data = {"pair": pair,
"side": side,
"price": price,
}
return self._post("forceenter", data=data)
2022-01-26 19:07:58 +00:00
2022-08-02 17:53:10 +00:00
def forceexit(self, tradeid, ordertype=None, amount=None):
2022-04-10 13:56:29 +00:00
"""Force-exit a trade.
2019-11-20 12:20:39 +00:00
2019-04-26 10:50:13 +00:00
:param tradeid: Id of the trade (can be received via status command)
2022-08-02 17:53:10 +00:00
:param ordertype: Order type to use (must be market or limit)
:param amount: Amount to sell. Full sell if not given
2019-06-23 20:10:37 +00:00
:return: json object
2019-04-26 10:50:13 +00:00
"""
2022-08-02 17:53:10 +00:00
return self._post("forceexit", data={
"tradeid": tradeid,
"ordertype": ordertype,
"amount": amount,
})
2019-04-26 10:50:13 +00:00
def strategies(self):
"""Lists available strategies
:return: json object
"""
return self._get("strategies")
2020-09-17 05:53:22 +00:00
def strategy(self, strategy):
"""Get strategy details
:param strategy: Strategy class name
:return: json object
"""
return self._get(f"strategy/{strategy}")
def plot_config(self):
"""Return plot configuration if the strategy defines one.
:return: json object
"""
return self._get("plot_config")
def available_pairs(self, timeframe=None, stake_currency=None):
"""Return available pair (backtest data) based on timeframe / stake_currency selection
:param timeframe: Only pairs with this timeframe available.
:param stake_currency: Only pairs that include this timeframe
:return: json object
"""
return self._get("available_pairs", params={
"stake_currency": stake_currency if timeframe else '',
"timeframe": timeframe if timeframe else '',
})
def pair_candles(self, pair, timeframe, limit=None):
"""Return live dataframe for <pair><timeframe>.
:param pair: Pair to get data for
:param timeframe: Only pairs with this timeframe available.
:param limit: Limit result to the last n candles.
:return: json object
"""
params = {
"pair": pair,
"timeframe": timeframe,
}
if limit:
params['limit'] = limit
return self._get("pair_candles", params=params)
2023-03-12 14:24:27 +00:00
def pair_history(self, pair, timeframe, strategy, timerange=None):
"""Return historic, analyzed dataframe
:param pair: Pair to get data for
:param timeframe: Only pairs with this timeframe available.
:param strategy: Strategy to analyze and get values for
:param timerange: Timerange to get data for (same format than --timerange endpoints)
:return: json object
"""
return self._get("pair_history", params={
"pair": pair,
"timeframe": timeframe,
"strategy": strategy,
"timerange": timerange if timerange else '',
})
def sysinfo(self):
"""Provides system information (CPU, RAM usage)
:return: json object
"""
return self._get("sysinfo")
2019-04-25 18:32:10 +00:00
def health(self):
"""Provides a quick health check of the running bot.
:return: json object
"""
return self._get("health")
2019-04-25 18:32:10 +00:00
2019-04-04 19:07:44 +00:00
def add_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("command",
help="Positional argument defining the command to execute.",
nargs="?"
)
2019-04-26 07:55:52 +00:00
parser.add_argument('--show',
help='Show possible methods with this client',
dest='show',
action='store_true',
default=False
)
2019-04-09 04:40:15 +00:00
parser.add_argument('-c', '--config',
help='Specify configuration file (default: %(default)s). ',
dest='config',
type=str,
metavar='PATH',
default='config.json'
)
2019-04-26 07:12:03 +00:00
parser.add_argument("command_arguments",
help="Positional arguments for the parameters for [command]",
nargs="*",
default=[]
)
2019-04-04 19:07:44 +00:00
args = parser.parse_args()
return vars(args)
2019-04-09 04:40:15 +00:00
def load_config(configfile):
file = Path(configfile)
if file.is_file():
with file.open("r") as f:
config = rapidjson.load(f, parse_mode=rapidjson.PM_COMMENTS |
rapidjson.PM_TRAILING_COMMAS)
return config
else:
logger.warning(f"Could not load config file {file}.")
sys.exit(1)
2019-04-09 04:40:15 +00:00
2019-05-18 08:24:01 +00:00
def print_commands():
2019-05-18 08:29:38 +00:00
# Print dynamic help for the different commands using the commands doc-strings
2019-05-18 08:24:01 +00:00
client = FtRestClient(None)
2019-11-20 12:20:39 +00:00
print("Possible commands:\n")
2019-05-18 08:24:01 +00:00
for x, y in inspect.getmembers(client):
if not x.startswith('_'):
2019-11-20 18:54:00 +00:00
doc = re.sub(':return:.*', '', getattr(client, x).__doc__, flags=re.MULTILINE).rstrip()
print(f"{x}\n\t{doc}\n")
2019-04-04 19:07:44 +00:00
2019-04-26 07:55:52 +00:00
2019-05-18 08:24:01 +00:00
def main(args):
if args.get("show"):
2019-05-18 08:24:01 +00:00
print_commands()
sys.exit()
2019-04-26 07:55:52 +00:00
2020-08-26 18:52:09 +00:00
config = load_config(args['config'])
2021-05-21 04:18:16 +00:00
url = config.get('api_server', {}).get('listen_ip_address', '127.0.0.1')
2020-08-26 18:52:09 +00:00
port = config.get('api_server', {}).get('listen_port', '8080')
username = config.get('api_server', {}).get('username')
password = config.get('api_server', {}).get('password')
2019-05-25 12:14:09 +00:00
2019-04-09 04:40:15 +00:00
server_url = f"http://{url}:{port}"
2019-05-25 12:14:09 +00:00
client = FtRestClient(server_url, username, password)
2019-04-09 04:40:15 +00:00
2019-04-26 07:27:20 +00:00
m = [x for x, y in inspect.getmembers(client) if not x.startswith('_')]
command = args["command"]
if command not in m:
logger.error(f"Command {command} not defined")
2019-05-18 08:24:01 +00:00
print_commands()
2019-04-26 07:27:20 +00:00
return
2021-02-03 08:27:54 +00:00
print(json.dumps(getattr(client, command)(*args["command_arguments"])))
2019-04-04 19:07:44 +00:00
2021-02-02 20:59:48 +00:00
2019-04-04 19:07:44 +00:00
if __name__ == "__main__":
args = add_arguments()
main(args)