Merge pull request #3138 from orkblutt/develop

trades history RPC
This commit is contained in:
Matthias 2020-04-08 08:23:13 +02:00 committed by GitHub
commit 68a5e0c51b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 232 additions and 56 deletions

View File

@ -188,7 +188,7 @@ class Trade(_DECL_BASE):
fee_close = Column(Float, nullable=False, default=0.0)
open_rate = Column(Float)
open_rate_requested = Column(Float)
# open_trade_price - calcuated via _calc_open_trade_price
# open_trade_price - calculated via _calc_open_trade_price
open_trade_price = Column(Float)
close_rate = Column(Float)
close_rate_requested = Column(Float)
@ -233,6 +233,9 @@ class Trade(_DECL_BASE):
return {
'trade_id': self.id,
'pair': self.pair,
'is_open': self.is_open,
'fee_open': self.fee_open,
'fee_close': self.fee_close,
'open_date_hum': arrow.get(self.open_date).humanize(),
'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
'close_date_hum': (arrow.get(self.close_date).humanize()
@ -240,14 +243,24 @@ class Trade(_DECL_BASE):
'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S")
if self.close_date else None),
'open_rate': self.open_rate,
'open_rate_requested': self.open_rate_requested,
'open_trade_price': self.open_trade_price,
'close_rate': self.close_rate,
'close_rate_requested': self.close_rate_requested,
'amount': round(self.amount, 8),
'stake_amount': round(self.stake_amount, 8),
'close_profit': self.close_profit,
'sell_reason': self.sell_reason,
'stop_loss': self.stop_loss,
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
'initial_stop_loss': self.initial_stop_loss,
'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100
if self.initial_stop_loss_pct else None),
'min_rate': self.min_rate,
'max_rate': self.max_rate,
'strategy': self.strategy,
'ticker_interval': self.ticker_interval,
'open_order_id': self.open_order_id,
}
def adjust_min_max_rates(self, current_price: float) -> None:

View File

@ -173,7 +173,8 @@ class ApiServer(RPC):
view_func=self._show_config, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/ping', 'ping',
view_func=self._ping, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/trades', 'trades',
view_func=self._trades, methods=['GET'])
# Combined actions and infos
self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist,
methods=['GET', 'POST'])
@ -358,6 +359,18 @@ class ApiServer(RPC):
self._config.get('fiat_display_currency', ''))
return self.rest_dump(results)
@require_login
@rpc_catch_errors
def _trades(self):
"""
Handler for /trades.
Returns the X last trades in json format
"""
limit = int(request.args.get('limit', 0))
results = self._rpc_trade_history(limit)
return self.rest_dump(results)
@require_login
@rpc_catch_errors
def _whitelist(self):

View File

@ -226,6 +226,20 @@ class RPC:
for key, value in profit_days.items()
]
def _rpc_trade_history(self, limit: int) -> Dict:
""" Returns the X last trades """
if limit > 0:
trades = Trade.get_trades().order_by(Trade.id.desc()).limit(limit)
else:
trades = Trade.get_trades().order_by(Trade.id.desc()).all()
output = [trade.to_json() for trade in trades]
return {
"trades": output,
"trades_count": len(output)
}
def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
""" Returns cumulative profit statistics """

View File

@ -156,6 +156,14 @@ class FtRestClient():
"""
return self._get("show_config")
def trades(self, limit=None):
"""Return trades history.
:param limit: Limits trades to the X last trades. No limit to get all the trades.
:return: json object
"""
return self._get("trades", params={"limit": limit} if limit else 0)
def whitelist(self):
"""Show the current whitelist.

View File

@ -166,6 +166,52 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
freqtrade.exchange.refresh_latest_ohlcv = lambda p: None
def create_mock_trades(fee):
"""
Create some fake trades ...
"""
# Simulate dry_run entries
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='dry_run_buy_12345'
)
Trade.session.add(trade)
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
close_rate=0.128,
close_profit=0.005,
exchange='bittrex',
is_open=False,
open_order_id='dry_run_sell_12345'
)
Trade.session.add(trade)
# Simulate prod entry
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='prod_buy_12345'
)
Trade.session.add(trade)
@pytest.fixture(autouse=True)
def patch_coingekko(mocker) -> None:
"""

View File

@ -15,7 +15,7 @@ from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
load_backtest_data, load_trades,
load_trades_from_db)
from freqtrade.data.history import load_data, load_pair_history
from tests.test_persistence import create_mock_trades
from tests.conftest import create_mock_trades
def test_load_backtest_data(testdatadir):

View File

@ -13,7 +13,7 @@ from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State
from tests.conftest import get_patched_freqtradebot, patch_get_signal
from tests.conftest import get_patched_freqtradebot, patch_get_signal, create_mock_trades
# Functions for recurrent object patching
@ -49,6 +49,18 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'base_currency': 'BTC',
'open_date': ANY,
'open_date_hum': ANY,
'is_open': ANY,
'fee_open': ANY,
'fee_close': ANY,
'open_rate_requested': ANY,
'open_trade_price': ANY,
'close_rate_requested': ANY,
'sell_reason': ANY,
'min_rate': ANY,
'max_rate': ANY,
'strategy': ANY,
'ticker_interval': ANY,
'open_order_id': ANY,
'close_date': None,
'close_date_hum': None,
'open_rate': 1.098e-05,
@ -76,6 +88,18 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'base_currency': 'BTC',
'open_date': ANY,
'open_date_hum': ANY,
'is_open': ANY,
'fee_open': ANY,
'fee_close': ANY,
'open_rate_requested': ANY,
'open_trade_price': ANY,
'close_rate_requested': ANY,
'sell_reason': ANY,
'min_rate': ANY,
'max_rate': ANY,
'strategy': ANY,
'ticker_interval': ANY,
'open_order_id': ANY,
'close_date': None,
'close_date_hum': None,
'open_rate': 1.098e-05,
@ -187,6 +211,32 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency)
def test_rpc_trade_history(mocker, default_conf, markets, fee):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets)
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
trades = rpc._rpc_trade_history(2)
assert len(trades['trades']) == 2
assert trades['trades_count'] == 2
assert isinstance(trades['trades'][0], dict)
assert isinstance(trades['trades'][1], dict)
trades = rpc._rpc_trade_history(0)
assert len(trades['trades']) == 3
assert trades['trades_count'] == 3
# The first trade is for ETH ... sorting is descending
assert trades['trades'][-1]['pair'] == 'ETH/BTC'
assert trades['trades'][0]['pair'] == 'ETC/BTC'
assert trades['trades'][1]['pair'] == 'ETC/BTC'
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
limit_buy_order, limit_sell_order, mocker) -> None:
mocker.patch.multiple(

View File

@ -13,7 +13,7 @@ from freqtrade.__init__ import __version__
from freqtrade.persistence import Trade
from freqtrade.rpc.api_server import BASE_URI, ApiServer
from freqtrade.state import State
from tests.conftest import get_patched_freqtradebot, log_has, patch_get_signal
from tests.conftest import get_patched_freqtradebot, log_has, patch_get_signal, create_mock_trades
_TEST_USER = "FreqTrader"
_TEST_PASS = "SuperSecurePassword1!"
@ -302,6 +302,30 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
assert rc.json[0][0] == str(datetime.utcnow().date())
def test_api_trades(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets)
)
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
assert len(rc.json) == 2
assert rc.json['trades_count'] == 0
create_mock_trades(fee)
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
assert len(rc.json['trades']) == 3
assert rc.json['trades_count'] == 3
rc = client_get(client, f"{BASE_URI}/trades?limit=2")
assert_response(rc)
assert len(rc.json['trades']) == 2
assert rc.json['trades_count'] == 2
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
@ -444,7 +468,21 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'stake_amount': 0.001,
'stop_loss': 0.0,
'stop_loss_pct': None,
'trade_id': 1}]
'trade_id': 1,
'close_rate_requested': None,
'current_rate': 1.099e-05,
'fee_close': 0.0025,
'fee_open': 0.0025,
'open_date': ANY,
'is_open': True,
'max_rate': 0.0,
'min_rate': None,
'open_order_id': ANY,
'open_rate_requested': 1.098e-05,
'open_trade_price': 0.0010025,
'sell_reason': None,
'strategy': 'DefaultStrategy',
'ticker_interval': 5}]
def test_api_version(botclient):
@ -533,7 +571,21 @@ def test_api_forcebuy(botclient, mocker, fee):
'stake_amount': 1,
'stop_loss': None,
'stop_loss_pct': None,
'trade_id': None}
'trade_id': None,
'close_profit': None,
'close_rate_requested': None,
'fee_close': 0.0025,
'fee_open': 0.0025,
'is_open': False,
'max_rate': None,
'min_rate': None,
'open_order_id': '123456',
'open_rate_requested': None,
'open_trade_price': 0.2460546025,
'sell_reason': None,
'strategy': None,
'ticker_interval': None
}
def test_api_forcesell(botclient, mocker, ticker, fee, markets):

View File

@ -9,53 +9,7 @@ from sqlalchemy import create_engine
from freqtrade import constants
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade, clean_dry_run_db, init
from tests.conftest import log_has
def create_mock_trades(fee):
"""
Create some fake trades ...
"""
# Simulate dry_run entries
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='dry_run_buy_12345'
)
Trade.session.add(trade)
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
close_rate=0.128,
close_profit=0.005,
exchange='bittrex',
is_open=False,
open_order_id='dry_run_sell_12345'
)
Trade.session.add(trade)
# Simulate prod entry
trade = Trade(
pair='ETC/BTC',
stake_amount=0.001,
amount=123.0,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
exchange='bittrex',
open_order_id='prod_buy_12345'
)
Trade.session.add(trade)
from tests.conftest import log_has, create_mock_trades
def test_init_create_session(default_conf):
@ -777,18 +731,31 @@ def test_to_json(default_conf, fee):
assert result == {'trade_id': None,
'pair': 'ETH/BTC',
'is_open': None,
'open_date_hum': '2 hours ago',
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
'open_order_id': 'dry_run_buy_12345',
'close_date_hum': None,
'close_date': None,
'open_rate': 0.123,
'open_rate_requested': None,
'open_trade_price': 15.1668225,
'fee_close': 0.0025,
'fee_open': 0.0025,
'close_rate': None,
'close_rate_requested': None,
'amount': 123.0,
'stake_amount': 0.001,
'close_profit': None,
'sell_reason': None,
'stop_loss': None,
'stop_loss_pct': None,
'initial_stop_loss': None,
'initial_stop_loss_pct': None}
'initial_stop_loss_pct': None,
'min_rate': None,
'max_rate': None,
'strategy': None,
'ticker_interval': None}
# Simulate dry_run entries
trade = Trade(
@ -819,7 +786,20 @@ def test_to_json(default_conf, fee):
'stop_loss': None,
'stop_loss_pct': None,
'initial_stop_loss': None,
'initial_stop_loss_pct': None}
'initial_stop_loss_pct': None,
'close_profit': None,
'close_rate_requested': None,
'fee_close': 0.0025,
'fee_open': 0.0025,
'is_open': None,
'max_rate': None,
'min_rate': None,
'open_order_id': None,
'open_rate_requested': None,
'open_trade_price': 12.33075,
'sell_reason': None,
'strategy': None,
'ticker_interval': None}
def test_stoploss_reinitialization(default_conf, fee):