Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	config.json.example
#	docs/configuration.md
#	freqtrade/constants.py
#	freqtrade/freqtradebot.py
#	freqtrade/main.py
#	freqtrade/rpc/rpc.py
#	freqtrade/tests/rpc/test_rpc_manager.py
#	freqtrade/tests/rpc/test_rpc_telegram.py
#	requirements.txt
This commit is contained in:
creslinux
2018-07-18 09:04:24 +00:00
10 changed files with 167 additions and 361 deletions

View File

@@ -3,10 +3,9 @@ This module contains class to define a RPC communications
"""
import logging
from abc import abstractmethod
from datetime import timedelta, datetime, date
from datetime import date, datetime, timedelta
from decimal import Decimal
from enum import Enum
from typing import Dict, Any, List
from typing import Any, Dict, List, Tuple
import arrow
import sqlalchemy as sql
@@ -20,15 +19,6 @@ from freqtrade.state import State
logger = logging.getLogger(__name__)
class RPCMessageType(Enum):
STATUS_NOTIFICATION = 'status'
BUY_NOTIFICATION = 'buy'
SELL_NOTIFICATION = 'sell'
def __repr__(self):
return self.value
class RPCException(Exception):
"""
Should be raised with a rpc-formatted message in an _rpc_* method
@@ -36,12 +26,7 @@ class RPCException(Exception):
raise RPCException('*Status:* `no active trade`')
"""
def __init__(self, message: str) -> None:
super().__init__(self)
self.message = message
def __str__(self):
return self.message
pass
class RPC(object):
@@ -56,20 +41,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 send_msg(self, msg: Dict[str, str]) -> None:
def name(self) -> str:
""" Returns the lowercase name of this module """
@abstractmethod
def send_msg(self, msg: str) -> None:
""" Sends a message to all registered rpc modules """
def _rpc_trade_status(self) -> List[Dict[str, Any]]:
def _rpc_trade_status(self) -> List[str]:
"""
Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is
a remotely exposed function
@@ -77,11 +62,11 @@ class RPC(object):
# Fetch open trade
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
raise RPCException('*Status:* `trader is not running`')
elif not trades:
raise RPCException('no active trade')
raise RPCException('*Status:* `no active trade`')
else:
results = []
result = []
for trade in trades:
order = None
if trade.open_order_id:
@@ -91,29 +76,39 @@ class RPC(object):
current_profit = trade.calc_profit_percent(current_rate)
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
if trade.close_profit else None)
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),
open_rate=trade.open_rate,
close_rate=trade.close_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
close_profit=fmt_close_profit,
current_profit=round(current_profit * 100, 2),
open_order='({} {} rem={:.8f})'.format(
order['type'], order['side'], order['remaining']
) if order else None,
))
return results
market_url = self._freqtrade.exchange.get_pair_detail_url(trade.pair)
trade_date = arrow.get(trade.open_date).humanize()
open_rate = trade.open_rate
close_rate = trade.close_rate
amount = round(trade.amount, 8)
current_profit = round(current_profit * 100, 2)
open_order = ''
if order:
order_type = order['type']
order_side = order['side']
order_rem = order['remaining']
open_order = f'({order_type} {order_side} rem={order_rem:.8f})'
message = f"*Trade ID:* `{trade.id}`\n" \
f"*Current Pair:* [{trade.pair}]({market_url})\n" \
f"*Open Since:* `{trade_date}`\n" \
f"*Amount:* `{amount}`\n" \
f"*Open Rate:* `{open_rate:.8f}`\n" \
f"*Close Rate:* `{close_rate}`\n" \
f"*Current Rate:* `{current_rate:.8f}`\n" \
f"*Close Profit:* `{fmt_close_profit}`\n" \
f"*Current Profit:* `{current_profit:.2f}%`\n" \
f"*Open Order:* `{open_order}`"\
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')
raise RPCException('*Status:* `trader is not running`')
elif not trades:
raise RPCException('no active order')
raise RPCException('*Status:* `no active order`')
else:
trades_list = []
for trade in trades:
@@ -139,7 +134,7 @@ class RPC(object):
profit_days: Dict[date, Dict] = {}
if not (isinstance(timescale, int) and timescale > 0):
raise RPCException('timescale must be an integer greater than 0')
raise RPCException('*Daily [n]:* `must be an integer greater than 0`')
fiat = self._freqtrade.fiat_converter
for day in range(0, timescale):
@@ -219,7 +214,7 @@ class RPC(object):
.order_by(sql.text('profit_sum DESC')).first()
if not best_pair:
raise RPCException('no closed trade')
raise RPCException('*Status:* `no closed trade`')
bp_pair, bp_rate = best_pair
@@ -227,26 +222,26 @@ class RPC(object):
# doing this will utilize its caching functionallity, instead we reinitialize it here
fiat = self._freqtrade.fiat_converter
# Prepare data to display
profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
profit_closed_coin = round(sum(profit_closed_coin), 8)
profit_closed_percent = round(nan_to_num(mean(profit_closed_percent)) * 100, 2)
profit_closed_fiat = fiat.convert_amount(
profit_closed_coin_sum,
profit_closed_coin,
stake_currency,
fiat_display_currency
)
profit_all_coin_sum = round(sum(profit_all_coin), 8)
profit_all_coin = round(sum(profit_all_coin), 8)
profit_all_percent = round(nan_to_num(mean(profit_all_percent)) * 100, 2)
profit_all_fiat = fiat.convert_amount(
profit_all_coin_sum,
profit_all_coin,
stake_currency,
fiat_display_currency
)
num = float(len(durations) or 1)
return {
'profit_closed_coin': profit_closed_coin_sum,
'profit_closed_coin': profit_closed_coin,
'profit_closed_percent': profit_closed_percent,
'profit_closed_fiat': profit_closed_fiat,
'profit_all_coin': profit_all_coin_sum,
'profit_all_coin': profit_all_coin,
'profit_all_percent': profit_all_percent,
'profit_all_fiat': profit_all_fiat,
'trade_count': len(trades),
@@ -257,7 +252,7 @@ class RPC(object):
'best_rate': round(bp_rate * 100, 2),
}
def _rpc_balance(self, fiat_display_currency: str) -> Dict:
def _rpc_balance(self, fiat_display_currency: str) -> Tuple[List[Dict], float, str, float]:
""" Returns current account balance per crypto """
output = []
total = 0.0
@@ -274,47 +269,45 @@ class RPC(object):
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
est_btc: float = rate * balance['total']
total = total + est_btc
output.append({
'currency': coin,
'available': balance['free'],
'balance': balance['total'],
'pending': balance['used'],
'est_btc': est_btc,
})
output.append(
{
'currency': coin,
'available': balance['free'],
'balance': balance['total'],
'pending': balance['used'],
'est_btc': est_btc
}
)
if total == 0.0:
raise RPCException('all balances are zero')
raise RPCException('`All balances are zero.`')
fiat = self._freqtrade.fiat_converter
symbol = fiat_display_currency
value = fiat.convert_amount(total, 'BTC', symbol)
return {
'currencies': output,
'total': total,
'symbol': symbol,
'value': value,
}
return output, total, symbol, value
def _rpc_start(self) -> Dict[str, str]:
def _rpc_start(self) -> str:
""" Handler for start """
if self._freqtrade.state == State.RUNNING:
return {'status': 'already running'}
return '*Status:* `already running`'
self._freqtrade.state = State.RUNNING
return {'status': 'starting trader ...'}
return '`Starting trader ...`'
def _rpc_stop(self) -> Dict[str, str]:
def _rpc_stop(self) -> str:
""" Handler for stop """
if self._freqtrade.state == State.RUNNING:
self._freqtrade.state = State.STOPPED
return {'status': 'stopping trader ...'}
return '`Stopping trader ...`'
return {'status': 'already stopped'}
return '*Status:* `already stopped`'
def _rpc_reload_conf(self) -> Dict[str, str]:
def _rpc_reload_conf(self) -> str:
""" Handler for reload_conf. """
self._freqtrade.state = State.RELOAD_CONF
return {'status': 'reloading config ...'}
return '*Status:* `Reloading config ...`'
# FIX: no test for this!!!!
def _rpc_forcesell(self, trade_id) -> None:
"""
Handler for forcesell <id>.
@@ -348,7 +341,7 @@ class RPC(object):
# ---- EOF def _exec_forcesell ----
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
raise RPCException('`trader is not running`')
if trade_id == 'all':
# Execute sell for all open orders
@@ -365,7 +358,7 @@ class RPC(object):
).first()
if not trade:
logger.warning('forcesell: Invalid argument received')
raise RPCException('invalid argument')
raise RPCException('Invalid argument.')
_exec_forcesell(trade)
Trade.session.flush()
@@ -376,7 +369,7 @@ class RPC(object):
Shows a performance statistic from finished trades
"""
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
raise RPCException('`trader is not running`')
pair_rates = Trade.session.query(Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum'),
@@ -393,6 +386,6 @@ class RPC(object):
def _rpc_count(self) -> List[Trade]:
""" Returns the number of trades running """
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
raise RPCException('`trader is not running`')
return Trade.query.filter(Trade.is_open.is_(True)).all()