rpc.py
Added RPCExcpetion if trades is empty on trade.query api_server Return e on exception created server 500 error. Returned text for client, and sent e to logger Added profit and status table functions. Will look to add unit tests for these two
This commit is contained in:
parent
63c16b7f83
commit
95ba016558
@ -1,32 +1,26 @@
|
||||
import json
|
||||
import threading
|
||||
import logging
|
||||
# import json
|
||||
from typing import Dict
|
||||
|
||||
from flask import request
|
||||
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
|
||||
from freqtrade.rpc.api_server_common import MyApiApp
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
"""
|
||||
api server routes that do not need access to rpc.rpc
|
||||
are held within api_server_common.api_server
|
||||
"""
|
||||
app = MyApiApp(__name__)
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
class ApiServer(RPC):
|
||||
"""
|
||||
This class is for REST calls across api server
|
||||
This class runs api server and provides rpc.rpc functionality to it
|
||||
|
||||
This class starts a none blocking thread the api server runs within
|
||||
Any routes that require access to rpc.rpc defs are held within this
|
||||
class.
|
||||
|
||||
Any routes that do not require access to rpc.rcp should be registered
|
||||
in api_server_common.MyApiApp
|
||||
This class starts a none blocking thread the api server runs within\
|
||||
"""
|
||||
def __init__(self, freqtrade) -> None:
|
||||
"""
|
||||
@ -39,11 +33,20 @@ class ApiServer(RPC):
|
||||
self._config = freqtrade.config
|
||||
|
||||
# Register application handling
|
||||
self.register_rest_other()
|
||||
self.register_rest_rpc_urls()
|
||||
|
||||
thread = threading.Thread(target=self.run, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def register_rest_other(self):
|
||||
"""
|
||||
Registers flask app URLs that are not calls to functionality in rpc.rpc.
|
||||
:return:
|
||||
"""
|
||||
app.register_error_handler(404, self.page_not_found)
|
||||
app.add_url_rule('/', 'hello', view_func=self.hello, methods=['GET'])
|
||||
|
||||
def register_rest_rpc_urls(self):
|
||||
"""
|
||||
Registers flask app URLs that are calls to functonality in rpc.rpc.
|
||||
@ -55,6 +58,9 @@ class ApiServer(RPC):
|
||||
app.add_url_rule('/stop', 'stop', view_func=self.stop, methods=['GET'])
|
||||
app.add_url_rule('/start', 'start', view_func=self.start, methods=['GET'])
|
||||
app.add_url_rule('/daily', 'daily', view_func=self.daily, methods=['GET'])
|
||||
app.add_url_rule('/profit', 'profit', view_func=self.profit, methods=['GET'])
|
||||
app.add_url_rule('/status_table', 'status_table',
|
||||
view_func=self.status_table, methods=['GET'])
|
||||
|
||||
def run(self):
|
||||
""" Method that runs flask app in its own thread forever """
|
||||
@ -82,17 +88,47 @@ class ApiServer(RPC):
|
||||
def cleanup(self) -> None:
|
||||
pass
|
||||
|
||||
def send_msg(self, msg: str) -> None:
|
||||
def send_msg(self, msg: Dict[str, str]) -> None:
|
||||
pass
|
||||
|
||||
"""
|
||||
Define the application methods here, called by app.add_url_rule
|
||||
each Telegram command should have a like local substitute
|
||||
"""
|
||||
def stop_api(self):
|
||||
""" For calling shutdown_api_server over via api server HTTP"""
|
||||
self.shutdown_api_server()
|
||||
return 'Api Server shutting down... '
|
||||
|
||||
def page_not_found(self, error):
|
||||
"""
|
||||
Return "404 not found", 404.
|
||||
"""
|
||||
return json.dumps({
|
||||
'status': 'error',
|
||||
'reason': '''There's no API call for %s''' % request.base_url,
|
||||
'code': 404
|
||||
}), 404
|
||||
|
||||
def hello(self):
|
||||
"""
|
||||
None critical but helpful default index page.
|
||||
|
||||
That lists URLs added to the flask server.
|
||||
This may be deprecated at any time.
|
||||
:return: index.html
|
||||
"""
|
||||
rest_cmds = 'Commands implemented: <br>' \
|
||||
'<a href=/daily?timescale=7>Show 7 days of stats</a>' \
|
||||
'<br>' \
|
||||
'<a href=/stop>Stop the Trade thread</a>' \
|
||||
'<br>' \
|
||||
'<a href=/start>Start the Traded thread</a>' \
|
||||
'<br>' \
|
||||
'<a href=/profit>Show profit summary</a>' \
|
||||
'<br>' \
|
||||
'<a href=/status_table>Show status table - Open trades</a>' \
|
||||
'<br>' \
|
||||
'<a href=/paypal> 404 page does not exist</a>' \
|
||||
'<br>'
|
||||
|
||||
return rest_cmds
|
||||
|
||||
def daily(self):
|
||||
"""
|
||||
@ -102,6 +138,7 @@ class ApiServer(RPC):
|
||||
"""
|
||||
try:
|
||||
timescale = request.args.get('timescale')
|
||||
logger.info("LocalRPC - Daily Command Called")
|
||||
timescale = int(timescale)
|
||||
|
||||
stats = self._rpc_daily_profit(timescale,
|
||||
@ -109,10 +146,45 @@ class ApiServer(RPC):
|
||||
self._config['fiat_display_currency']
|
||||
)
|
||||
|
||||
stats = dumps(stats, indent=4, sort_keys=True, default=str)
|
||||
return stats
|
||||
return json.dumps(stats, indent=4, sort_keys=True, default=str)
|
||||
except RPCException as e:
|
||||
return e
|
||||
logger.exception("API Error querying daily:", e)
|
||||
return "Error querying daily"
|
||||
|
||||
def profit(self):
|
||||
"""
|
||||
Handler for /profit.
|
||||
|
||||
Returns a cumulative profit statistics
|
||||
:return: stats
|
||||
"""
|
||||
try:
|
||||
logger.info("LocalRPC - Profit Command Called")
|
||||
|
||||
stats = self._rpc_trade_statistics(self._config['stake_currency'],
|
||||
self._config['fiat_display_currency']
|
||||
)
|
||||
|
||||
return json.dumps(stats, indent=4, sort_keys=True, default=str)
|
||||
except RPCException as e:
|
||||
logger.exception("API Error calling profit", e)
|
||||
return "Error querying closed trades - maybe there are none"
|
||||
|
||||
def status_table(self):
|
||||
"""
|
||||
Handler for /status table.
|
||||
|
||||
Returns the current TradeThread status in table format
|
||||
:return: results
|
||||
"""
|
||||
try:
|
||||
results = self._rpc_trade_status()
|
||||
return json.dumps(results, indent=4, sort_keys=True, default=str)
|
||||
|
||||
except RPCException as e:
|
||||
logger.exception("API Error calling status table", e)
|
||||
return "Error querying open trades - maybe there are none."
|
||||
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
|
@ -5,14 +5,12 @@ import logging
|
||||
from abc import abstractmethod
|
||||
from datetime import datetime, timedelta, date
|
||||
from decimal import Decimal
|
||||
from typing import Dict, Any, List
|
||||
from typing import Dict, List, Any
|
||||
|
||||
import arrow
|
||||
import sqlalchemy as sql
|
||||
from numpy import mean, nan_to_num
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.misc import shorten_date
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.state import State
|
||||
|
||||
@ -51,20 +49,20 @@ class RPC(object):
|
||||
"""
|
||||
self._freqtrade = freqtrade
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
""" Returns the lowercase name of the implementation """
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
@abstractmethod
|
||||
def cleanup(self) -> None:
|
||||
""" Cleanup pending module resources """
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
""" Returns the lowercase name of this module """
|
||||
|
||||
@abstractmethod
|
||||
def send_msg(self, msg: str) -> None:
|
||||
def send_msg(self, msg: Dict[str, str]) -> None:
|
||||
""" Sends a message to all registered rpc modules """
|
||||
|
||||
def _rpc_trade_status(self) -> List[str]:
|
||||
def _rpc_trade_status(self) -> List[Dict]:
|
||||
"""
|
||||
Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is
|
||||
a remotely exposed function
|
||||
@ -76,7 +74,7 @@ class RPC(object):
|
||||
elif not trades:
|
||||
raise RPCException('no active trade')
|
||||
else:
|
||||
result = []
|
||||
results = []
|
||||
for trade in trades:
|
||||
order = None
|
||||
if trade.open_order_id:
|
||||
@ -87,21 +85,12 @@ class RPC(object):
|
||||
fmt_close_profit = '{:.2f}%'.format(
|
||||
round(trade.close_profit * 100, 2)
|
||||
) if trade.close_profit else None
|
||||
message = "*Trade ID:* `{trade_id}`\n" \
|
||||
"*Current Pair:* [{pair}]({market_url})\n" \
|
||||
"*Open Since:* `{date}`\n" \
|
||||
"*Amount:* `{amount}`\n" \
|
||||
"*Open Rate:* `{open_rate:.8f}`\n" \
|
||||
"*Close Rate:* `{close_rate}`\n" \
|
||||
"*Current Rate:* `{current_rate:.8f}`\n" \
|
||||
"*Close Profit:* `{close_profit}`\n" \
|
||||
"*Current Profit:* `{current_profit:.2f}%`\n" \
|
||||
"*Open Order:* `{open_order}`"\
|
||||
.format(
|
||||
|
||||
results.append(dict(
|
||||
trade_id=trade.id,
|
||||
pair=trade.pair,
|
||||
market_url=self._freqtrade.exchange.get_pair_detail_url(trade.pair),
|
||||
date=arrow.get(trade.open_date).humanize(),
|
||||
open_date=arrow.get(trade.open_date),
|
||||
open_rate=trade.open_rate,
|
||||
close_rate=trade.close_rate,
|
||||
current_rate=current_rate,
|
||||
@ -111,32 +100,8 @@ class RPC(object):
|
||||
open_order='({} {} rem={:.8f})'.format(
|
||||
order['type'], order['side'], order['remaining']
|
||||
) if order else None,
|
||||
)
|
||||
result.append(message)
|
||||
return result
|
||||
|
||||
def _rpc_status_table(self) -> DataFrame:
|
||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||
if self._freqtrade.state != State.RUNNING:
|
||||
raise RPCException('trader is not running')
|
||||
elif not trades:
|
||||
raise RPCException('no active order')
|
||||
else:
|
||||
trades_list = []
|
||||
for trade in trades:
|
||||
# calculate profit and send message to user
|
||||
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
|
||||
trades_list.append([
|
||||
trade.id,
|
||||
trade.pair,
|
||||
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
|
||||
'{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate))
|
||||
])
|
||||
|
||||
columns = ['ID', 'Pair', 'Since', 'Profit']
|
||||
df_statuses = DataFrame.from_records(trades_list, columns=columns)
|
||||
df_statuses = df_statuses.set_index(columns[0])
|
||||
return df_statuses
|
||||
))
|
||||
return results
|
||||
|
||||
def _rpc_daily_profit(
|
||||
self, timescale: int,
|
||||
@ -190,6 +155,9 @@ class RPC(object):
|
||||
""" Returns cumulative profit statistics """
|
||||
trades = Trade.query.order_by(Trade.id).all()
|
||||
|
||||
if not trades:
|
||||
raise RPCException('No trades found')
|
||||
|
||||
profit_all_coin = []
|
||||
profit_all_percent = []
|
||||
profit_closed_coin = []
|
||||
|
Loading…
Reference in New Issue
Block a user