diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py
index 7c3a5eb4b..c994eb8c2 100644
--- a/freqtrade/configuration.py
+++ b/freqtrade/configuration.py
@@ -6,7 +6,7 @@ import json
import logging
from argparse import Namespace
from typing import Optional, Dict, Any
-from jsonschema import Draft4Validator, validate
+from jsonschema import Draft4Validator, validate, draft4_format_checker
from jsonschema.exceptions import ValidationError, best_match
import ccxt
@@ -202,7 +202,7 @@ class Configuration(object):
:return: Returns the config if valid, otherwise throw an exception
"""
try:
- validate(conf, constants.CONF_SCHEMA)
+ validate(conf, constants.CONF_SCHEMA, format_checker=draft4_format_checker)
return conf
except ValidationError as exception:
logger.critical(
@@ -210,7 +210,9 @@ class Configuration(object):
exception
)
raise ValidationError(
- best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message
+ best_match(Draft4Validator(constants.CONF_SCHEMA,
+ format_checker=draft4_format_checker)
+ .iter_errors(conf)).message
)
def get_config(self) -> Dict[str, Any]:
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index 5be01f977..8560bf8a3 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -85,6 +85,19 @@ CONF_SCHEMA = {
},
'required': ['enabled', 'token', 'chat_id']
},
+ 'api_server': {
+ 'type': 'object',
+ 'properties': {
+ 'enabled': {'type': 'boolean'},
+ 'listen_ip_address': { "format": "ipv4"},
+ 'listen_port': {
+ 'type': 'integer',
+ "minimum": 1024,
+ "maximum": 65535
+ },
+ },
+ 'required': ['enabled', 'listen_ip_address', 'listen_port']
+ },
'db_url': {'type': 'string'},
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
'internals': {
diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py
new file mode 100644
index 000000000..04150b4d7
--- /dev/null
+++ b/freqtrade/rpc/api_server.py
@@ -0,0 +1,118 @@
+import threading
+import logging
+# import json
+
+from flask import Flask, request
+# from flask_restful import Resource, Api
+from json import dumps
+from freqtrade.rpc.rpc import RPC, RPCException
+from ipaddress import IPv4Address
+
+
+logger = logging.getLogger(__name__)
+app = Flask(__name__)
+
+class ApiServerSuperWrap(RPC):
+ """
+ This class is for REST calls across api server
+ """
+ def __init__(self, freqtrade) -> None:
+ """
+ Init the api server, and init the super class RPC
+ :param freqtrade: Instance of a freqtrade bot
+ :return: None
+ """
+ super().__init__(freqtrade)
+
+ self.interval = 1
+
+ thread = threading.Thread(target=self.run, args=(freqtrade,)) # extra comma as ref ! Tuple
+ thread.daemon = True # Daemonize thread
+ thread.start() # Start the execution
+
+
+ def run(self, freqtrade):
+ """ Method that runs forever """
+ self._config = freqtrade.config
+
+ """
+ Define the application methods here, called by app.add_url_rule
+ each Telegram command should have a like local substitute
+ """
+ # @app.route("/")
+ def hello():
+ # For simple rest server testing via browser
+ # cmds = 'Try uri:/daily?timescale=7 /profit /balance /status
+ # /status /table /performance /count,
+ # /start /stop /help'
+
+ rest_cmds ='Commands implemented:
' \
+ '/daily?timescale=7' \
+ '
' \
+ '/stop' \
+ '
' \
+ '/start'
+ return rest_cmds
+
+ def daily():
+ try:
+ timescale = request.args.get('timescale')
+ logger.info("LocalRPC - Daily Command Called")
+ timescale = int(timescale)
+
+ stats = self._rpc_daily_profit(timescale,
+ self._config['stake_currency'],
+ self._config['fiat_display_currency']
+ )
+
+ stats = dumps(stats, indent=4, sort_keys=True, default=str)
+ return stats
+ except RPCException as e:
+ return e
+
+ def start():
+ """
+ Handler for /start.
+ Starts TradeThread
+ """
+ msg = self._rpc_start()
+ return msg
+
+ def stop():
+ """
+ Handler for /stop.
+ Stops TradeThread
+ """
+ msg = self._rpc_stop()
+ return msg
+
+ ## defines the url rules available on the api server
+ '''
+ First two arguments passed are /URL and 'Label'
+ Label can be used as a shortcut when refactoring
+ '''
+ app.add_url_rule('/', 'hello', view_func=hello, methods=['GET'])
+ app.add_url_rule('/stop', 'stop', view_func=stop, methods=['GET'])
+ app.add_url_rule('/start', 'start', view_func=start, methods=['GET'])
+ app.add_url_rule('/daily', 'daily', view_func=daily, methods=['GET'])
+
+
+ """
+ Section to handle configuration and running of the Rest server
+ also to check and warn if not bound to a loopback, warn on security risk.
+ """
+ rest_ip = self._config['api_server']['listen_ip_address']
+ rest_port = self._config['api_server']['listen_port']
+
+ if not IPv4Address(rest_ip).is_loopback :
+ logger.info("SECURITY WARNING - Local Rest Server listening to external connections")
+ logger.info("SECURITY WARNING - This is insecure please set to your loopback, e.g 127.0.0.1 in config.json")
+
+ # Run the Server
+ logger.info('Starting Local Rest Server')
+ try:
+ app.run(host=rest_ip, port=rest_port)
+ except:
+ logger.exception("Api server failed to start, exception message is:")
+
+
diff --git a/freqtrade/rpc/rest_client.py b/freqtrade/rpc/rest_client.py
new file mode 100755
index 000000000..fa63cf4f7
--- /dev/null
+++ b/freqtrade/rpc/rest_client.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+"""
+Simple command line client into RPC commands
+Can be used as an alternate to Telegram
+"""
+
+import time
+from requests import get
+from sys import argv
+
+
+
+#TODO - use argparse to clean this up
+#TODO - use IP and Port from config.json not hardcode
+
+if len(argv) == 1:
+ print('\nThis script accepts the following arguments')
+ print('- daily (int) - Where int is the number of days to report back. daily 3')
+ print('- start - this will start the trading thread')
+ print('- stop - this will start the trading thread')
+ print('- there will be more....\n')
+
+if len(argv) == 3 and argv[1] == "daily":
+ if str.isnumeric(argv[2]):
+ get_url = 'http://localhost:5002/daily?timescale=' + argv[2]
+ d=get(get_url).json()
+ print(d)
+ else:
+ print("\nThe second argument to daily must be an integer, 1,2,3 etc")
+
+if len(argv) == 2 and argv[1] == "start":
+ get_url = 'http://localhost:5002/start'
+ d = get(get_url).text
+ print(d)
+
+ if "already" not in d:
+ time.sleep(2)
+ d = get(get_url).text
+ print(d)
+
+if len(argv) == 2 and argv[1] == "stop":
+ get_url = 'http://localhost:5002/stop'
+ d = get(get_url).text
+ print(d)
+
+ if "already" not in d:
+ time.sleep(2)
+ d = get(get_url).text
+ print(d)
+
+
+
+
diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py
index 252bbcdd8..9dddcdb80 100644
--- a/freqtrade/rpc/rpc_manager.py
+++ b/freqtrade/rpc/rpc_manager.py
@@ -1,5 +1,5 @@
"""
-This module contains class to manage RPC communications (Telegram, Slack, ...)
+This module contains class to manage RPC communications (Telegram, Slack, Rest ....)
"""
import logging
from typing import List
@@ -23,6 +23,13 @@ class RPCManager(object):
from freqtrade.rpc.telegram import Telegram
self.registered_modules.append(Telegram(freqtrade))
+ # Enable local rest api server for cmd line control
+ if freqtrade.config['api_server'].get('enabled', False):
+ logger.info('Enabling rpc.api_server')
+ from freqtrade.rpc.api_server import ApiServerSuperWrap
+ self.registered_modules.append(ApiServerSuperWrap(freqtrade))
+
+
def cleanup(self) -> None:
""" Stops all enabled rpc modules """
logger.info('Cleaning up rpc modules ...')
diff --git a/requirements.txt b/requirements.txt
index 41e246d50..3c9fc5825 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,3 +23,8 @@ coinmarketcap==5.0.3
# Required for plotting data
#plotly==2.3.0
+
+#Added for local rest client
+Flask==1.0.2
+flask-jsonpify==1.5.0
+flask-restful==0.3.6