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
|
2019-11-17 13:40:59 +00:00
|
|
|
import inspect
|
2019-04-09 04:40:15 +00:00
|
|
|
import json
|
2019-11-20 18:54:00 +00:00
|
|
|
import re
|
2019-04-04 19:07:44 +00:00
|
|
|
import logging
|
2019-11-17 13:40:59 +00:00
|
|
|
import sys
|
2019-04-09 04:40:15 +00:00
|
|
|
from pathlib import Path
|
2019-11-17 13:40:59 +00:00
|
|
|
from urllib.parse import urlencode, urlparse, urlunparse
|
2019-04-04 19:07:44 +00:00
|
|
|
|
2019-11-17 13:40:59 +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
|
|
|
|
|
|
|
|
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()
|
2019-05-25 12:25:36 +00:00
|
|
|
self._session.auth = (username, password)
|
2019-04-25 18:32:10 +00:00
|
|
|
|
2019-04-26 07:10:23 +00:00
|
|
|
def _call(self, method, apipath, params: dict = None, data=None, files=None):
|
2019-04-25 18:32:10 +00:00
|
|
|
|
|
|
|
if str(method).upper() not in ('GET', 'POST', 'PUT', 'DELETE'):
|
|
|
|
raise ValueError('invalid method <{0}>'.format(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:
|
2019-05-25 12:25:36 +00:00
|
|
|
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")
|
|
|
|
|
2019-04-26 07:27:20 +00:00
|
|
|
def _get(self, apipath, params: dict = None):
|
|
|
|
return self._call("GET", apipath, params=params)
|
|
|
|
|
2020-08-04 17:57:28 +00:00
|
|
|
def _delete(self, apipath, params: dict = None):
|
|
|
|
return self._call("DELETE", apipath, params=params)
|
|
|
|
|
2019-04-26 07:27:20 +00:00
|
|
|
def _post(self, apipath, params: dict = None, data: dict = None):
|
|
|
|
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):
|
2020-06-10 17:44:34 +00:00
|
|
|
"""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")
|
|
|
|
|
2020-06-10 17:44:34 +00:00
|
|
|
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
|
|
|
"""
|
2020-06-10 17:44:34 +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")
|
|
|
|
|
|
|
|
def daily(self, days=None):
|
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("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")
|
|
|
|
|
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):
|
|
|
|
"""
|
|
|
|
Returns part of the configuration, relevant for trading operations.
|
|
|
|
:return: json object containing the version
|
|
|
|
"""
|
|
|
|
return self._get("show_config")
|
|
|
|
|
2020-04-07 17:52:34 +00:00
|
|
|
def trades(self, limit=None):
|
2020-04-06 09:00:31 +00:00
|
|
|
"""Return trades history.
|
2020-04-05 14:14:02 +00:00
|
|
|
|
2020-04-07 17:52:34 +00:00
|
|
|
:param limit: Limits trades to the X last trades. No limit to get all the trades.
|
2020-04-05 14:14:02 +00:00
|
|
|
:return: json object
|
|
|
|
"""
|
2020-04-06 09:00:31 +00:00
|
|
|
return self._get("trades", params={"limit": limit} if limit else 0)
|
2020-04-05 14:14:02 +00:00
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
return self._delete("trades/{}".format(trade_id))
|
|
|
|
|
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 forcesell(self, tradeid):
|
2019-11-20 12:20:39 +00:00
|
|
|
"""Force-sell a trade.
|
|
|
|
|
2019-04-26 10:50:13 +00:00
|
|
|
:param tradeid: Id of the trade (can be received via status command)
|
2019-06-23 20:10:37 +00:00
|
|
|
:return: json object
|
2019-04-26 10:50:13 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
return self._post("forcesell", data={"tradeid": tradeid})
|
|
|
|
|
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",
|
2019-11-17 13:40:59 +00:00
|
|
|
help="Positional argument defining the command to execute.",
|
|
|
|
nargs="?"
|
|
|
|
)
|
2019-04-19 04:55:38 +00:00
|
|
|
|
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:
|
2019-11-17 13:40:59 +00:00
|
|
|
config = rapidjson.load(f, parse_mode=rapidjson.PM_COMMENTS |
|
|
|
|
rapidjson.PM_TRAILING_COMMAS)
|
2019-04-19 04:55:38 +00:00
|
|
|
return config
|
2019-11-17 13:40:59 +00:00
|
|
|
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):
|
|
|
|
|
2019-11-17 13:40:59 +00:00
|
|
|
if args.get("show"):
|
2019-05-18 08:24:01 +00:00
|
|
|
print_commands()
|
2019-11-17 13:40:59 +00:00
|
|
|
sys.exit()
|
2019-04-26 07:55:52 +00:00
|
|
|
|
2019-04-09 04:40:15 +00:00
|
|
|
config = load_config(args["config"])
|
|
|
|
url = config.get("api_server", {}).get("server_url", "127.0.0.1")
|
|
|
|
port = config.get("api_server", {}).get("listen_port", "8080")
|
2019-05-25 12:14:09 +00:00
|
|
|
username = config.get("api_server", {}).get("username")
|
|
|
|
password = config.get("api_server", {}).get("password")
|
|
|
|
|
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
|
|
|
|
|
|
|
|
print(getattr(client, command)(*args["command_arguments"]))
|
|
|
|
|
2019-04-04 19:07:44 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
args = add_arguments()
|
|
|
|
main(args)
|