Merge remote-tracking branch 'upstream/feature/flask-rest' into feature/flask-rest

This commit is contained in:
Matthias 2018-11-25 14:10:08 +01:00
commit ebd26fea43
15 changed files with 210 additions and 200 deletions

View File

@ -1,5 +1,5 @@
""" FreqTrade bot """ """ FreqTrade bot """
__version__ = '0.17.0' __version__ = '0.17.1'
class DependencyException(BaseException): class DependencyException(BaseException):

View File

@ -334,3 +334,10 @@ class Arguments(object):
nargs='+', nargs='+',
dest='timeframes', dest='timeframes',
) )
self.parser.add_argument(
'--erase',
help='Clean all existing data for the selected exchange/pairs/timeframes',
dest='erase',
action='store_true'
)

View File

@ -93,7 +93,9 @@ class FreqtradeBot(object):
# Log state transition # Log state transition
state = self.state state = self.state
if state != old_state: if state != old_state:
self.rpc.send_msg(f'*Status:* `{state.name.lower()}`') self.rpc.send_msg({
'status': f'{state.name.lower()}'
})
logger.info('Changing state to: %s', state.name) logger.info('Changing state to: %s', state.name)
if state == State.STOPPED: if state == State.STOPPED:
@ -169,9 +171,9 @@ class FreqtradeBot(object):
except OperationalException: except OperationalException:
tb = traceback.format_exc() tb = traceback.format_exc()
hint = 'Issue `/start` if you think it is safe to restart.' hint = 'Issue `/start` if you think it is safe to restart.'
self.rpc.send_msg( self.rpc.send_msg({
f'*Status:* OperationalException:\n```\n{tb}```{hint}' 'status': f'OperationalException:\n```\n{tb}```{hint}'
) })
logger.exception('OperationalException. Stopping trader ...') logger.exception('OperationalException. Stopping trader ...')
self.state = State.STOPPED self.state = State.STOPPED
return state_changed return state_changed
@ -356,11 +358,12 @@ class FreqtradeBot(object):
) )
# Create trade entity and return # Create trade entity and return
self.rpc.send_msg( self.rpc.send_msg({
f"""*{exc_name}:* Buying [{pair_s}]({pair_url}) \ 'status':
with limit `{buy_limit:.8f} ({stake_amount:.6f} \ f"""*{exc_name}:* Buying [{pair_s}]({pair_url}) \
{stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`""" with limit `{buy_limit:.8f} ({stake_amount:.6f} \
) {stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`"""
})
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
trade = Trade( trade = Trade(
@ -540,7 +543,9 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
Trade.session.delete(trade) Trade.session.delete(trade)
Trade.session.flush() Trade.session.flush()
logger.info('Buy order timeout for %s.', trade) logger.info('Buy order timeout for %s.', trade)
self.rpc.send_msg(f'*Timeout:* Unfilled buy order for {pair_s} cancelled') self.rpc.send_msg({
'status': f'Unfilled buy order for {pair_s} cancelled due to timeout'
})
return True return True
# if trade is partially complete, edit the stake details for the trade # if trade is partially complete, edit the stake details for the trade
@ -549,7 +554,9 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
trade.stake_amount = trade.amount * trade.open_rate trade.stake_amount = trade.amount * trade.open_rate
trade.open_order_id = None trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade) logger.info('Partial buy order timeout for %s.', trade)
self.rpc.send_msg(f'*Timeout:* Remaining buy order for {pair_s} cancelled') self.rpc.send_msg({
'status': f'Remaining buy order for {pair_s} cancelled due to timeout'
})
return False return False
# FIX: 20180110, should cancel_order() be cond. or unconditionally called? # FIX: 20180110, should cancel_order() be cond. or unconditionally called?
@ -567,7 +574,9 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
trade.close_date = None trade.close_date = None
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
self.rpc.send_msg(f'*Timeout:* Unfilled sell order for {pair_s} cancelled') self.rpc.send_msg({
'status': f'Unfilled sell order for {pair_s} cancelled due to timeout'
})
logger.info('Sell order timeout for %s.', trade) logger.info('Sell order timeout for %s.', trade)
return True return True
@ -627,5 +636,5 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
) )
# Send the message # Send the message
self.rpc.send_msg(message) self.rpc.send_msg({'status': message})
Trade.session.flush() Trade.session.flush()

View File

@ -59,7 +59,9 @@ def main(sysargv: List[str]) -> None:
logger.exception('Fatal exception!') logger.exception('Fatal exception!')
finally: finally:
if freqtrade: if freqtrade:
freqtrade.rpc.send_msg('*Status:* `Process died ...`') freqtrade.rpc.send_msg({
'status': 'process died'
})
freqtrade.cleanup() freqtrade.cleanup()
sys.exit(return_code) sys.exit(return_code)
@ -73,11 +75,9 @@ def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot:
# Create new instance # Create new instance
freqtrade = FreqtradeBot(Configuration(args).get_config()) freqtrade = FreqtradeBot(Configuration(args).get_config())
freqtrade.rpc.send_msg( freqtrade.rpc.send_msg({
'*Status:* `Config reloaded ...`'.format( 'status': 'config reloaded'
freqtrade.state.name.lower() })
)
)
return freqtrade return freqtrade

View File

@ -16,11 +16,11 @@ app = Flask(__name__)
class ApiServer(RPC): 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 runs api server and provides rpc.rpc functionality to it
This class starts a none blocking thread the api server runs within\ This class starts a none blocking thread the api server runs within
""" """
def __init__(self, freqtrade) -> None: def __init__(self, freqtrade) -> None:
""" """
Init the api server, and init the super class RPC Init the api server, and init the super class RPC

View File

@ -1,8 +1,8 @@
""" """
This module contains class to manage RPC communications (Telegram, Slack, ....) This module contains class to manage RPC communications (Telegram, Slack, Rest ....)
""" """
import logging import logging
from typing import List from typing import List, Dict
from freqtrade.rpc.rpc import RPC from freqtrade.rpc.rpc import RPC
@ -18,11 +18,17 @@ class RPCManager(object):
self.registered_modules: List[RPC] = [] self.registered_modules: List[RPC] = []
# Enable telegram # Enable telegram
if freqtrade.config['telegram'].get('enabled', False): if freqtrade.config.get('telegram', {}).get('enabled', False):
logger.info('Enabling rpc.telegram ...') logger.info('Enabling rpc.telegram ...')
from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import Telegram
self.registered_modules.append(Telegram(freqtrade)) self.registered_modules.append(Telegram(freqtrade))
# Enable local rest api server for cmd line control
if freqtrade.config.get('api_server', {}).get('enabled', False):
logger.info('Enabling rpc.api_server')
from freqtrade.rpc.api_server import ApiServer
self.registered_modules.append(ApiServer(freqtrade))
def cleanup(self) -> None: def cleanup(self) -> None:
""" Stops all enabled rpc modules """ """ Stops all enabled rpc modules """
logger.info('Cleaning up rpc modules ...') logger.info('Cleaning up rpc modules ...')
@ -32,11 +38,14 @@ class RPCManager(object):
mod.cleanup() mod.cleanup()
del mod del mod
def send_msg(self, msg: str) -> None: def send_msg(self, msg: Dict[str, str]) -> None:
""" """
Send given markdown message to all registered rpc modules Send given message to all registered rpc modules.
:param msg: message A message consists of one or more key value pairs of strings.
:return: None e.g.:
{
'status': 'stopping bot'
}
""" """
logger.info('Sending rpc message: %s', msg) logger.info('Sending rpc message: %s', msg)
for mod in self.registered_modules: for mod in self.registered_modules:

View File

@ -4,7 +4,7 @@
This module manage Telegram communication This module manage Telegram communication
""" """
import logging import logging
from typing import Any, Callable from typing import Any, Callable, Dict
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
@ -58,10 +58,6 @@ def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Call
class Telegram(RPC): class Telegram(RPC):
""" This class handles all telegram communication """ """ This class handles all telegram communication """
@property
def name(self) -> str:
return "telegram"
def __init__(self, freqtrade) -> None: def __init__(self, freqtrade) -> None:
""" """
Init the Telegram call, and init the super class RPC Init the Telegram call, and init the super class RPC
@ -117,9 +113,9 @@ class Telegram(RPC):
""" """
self._updater.stop() self._updater.stop()
def send_msg(self, msg: str) -> None: def send_msg(self, msg: Dict[str, str]) -> None:
""" Send a message to telegram channel """ """ Send a message to telegram channel """
self._send_msg(msg) self._send_msg('*Status:* `{status}`'.format(**msg))
@authorized_only @authorized_only
def _status(self, bot: Bot, update: Update) -> None: def _status(self, bot: Bot, update: Update) -> None:

View File

@ -83,6 +83,15 @@ def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> Non
) )
def patch_apiserver(mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.api_server.ApiServer',
run=MagicMock(),
register_rest_other=MagicMock(),
register_rest_rpc_urls=MagicMock(),
)
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def default_conf(): def default_conf():
""" Returns validated configuration suitable for most tests """ """ Returns validated configuration suitable for most tests """

View File

@ -7,6 +7,7 @@ Unit test file for rpc/rpc.py
from datetime import datetime from datetime import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
import arrow
import pytest import pytest
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
@ -40,6 +41,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
get_markets=markets get_markets=markets
) )
now = arrow.utcnow()
now_mock = mocker.patch('freqtrade.freqtradebot.datetime', MagicMock())
now_mock.utcnow = lambda: now.datetime
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
@ -58,7 +63,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
'trade_id': 1, 'trade_id': 1,
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'date': 'just now', 'open_date': now,
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'close_rate': None, 'close_rate': None,
'current_rate': 1.098e-05, 'current_rate': 1.098e-05,
@ -69,39 +74,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
} == results[0] } == results[0]
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
"""
Test rpc_status_table() method
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
)
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_status_table()
freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*no active order*'):
rpc._rpc_status_table()
freqtradebot.create_trade()
result = rpc._rpc_status_table()
assert 'just now' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].all()
def test_rpc_daily_profit(default_conf, update, ticker, fee, def test_rpc_daily_profit(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None: limit_buy_order, limit_sell_order, markets, mocker) -> None:
""" """

View File

@ -0,0 +1,52 @@
"""
Unit test file for rpc/api_server.py
"""
from unittest.mock import MagicMock
from freqtrade.rpc.api_server import ApiServer
from freqtrade.state import State
from freqtrade.tests.conftest import get_patched_freqtradebot, patch_apiserver
def test__init__(default_conf, mocker):
"""
Test __init__() method
"""
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
assert apiserver._config == default_conf
def test_start_endpoint(default_conf, mocker):
"""Test /start endpoint"""
patch_apiserver(mocker)
bot = get_patched_freqtradebot(mocker, default_conf)
apiserver = ApiServer(bot)
bot.state = State.STOPPED
assert bot.state == State.STOPPED
result = apiserver.start()
assert result == '{"status": "starting trader ..."}'
assert bot.state == State.RUNNING
result = apiserver.start()
assert result == '{"status": "already running"}'
def test_stop_endpoint(default_conf, mocker):
"""Test /stop endpoint"""
patch_apiserver(mocker)
bot = get_patched_freqtradebot(mocker, default_conf)
apiserver = ApiServer(bot)
bot.state = State.RUNNING
assert bot.state == State.RUNNING
result = apiserver.stop()
assert result == '{"status": "stopping trader ..."}'
assert bot.state == State.STOPPED
result = apiserver.stop()
assert result == '{"status": "already stopped"}'

View File

@ -102,9 +102,9 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
freqtradebot = get_patched_freqtradebot(mocker, conf) freqtradebot = get_patched_freqtradebot(mocker, conf)
rpc_manager = RPCManager(freqtradebot) rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg('test') rpc_manager.send_msg({'status': 'test'})
assert log_has('Sending rpc message: test', caplog.record_tuples) assert log_has("Sending rpc message: {'status': 'test'}", caplog.record_tuples)
assert telegram_mock.call_count == 0 assert telegram_mock.call_count == 0
@ -117,7 +117,7 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot) rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg('test') rpc_manager.send_msg({'status': 'test'})
assert log_has('Sending rpc message: test', caplog.record_tuples) assert log_has("Sending rpc message: {'status': 'test'}", caplog.record_tuples)
assert telegram_mock.call_count == 1 assert telegram_mock.call_count == 1

View File

@ -185,65 +185,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
) )
def test_status(default_conf, update, mocker, fee, ticker, markets) -> None: def test_status_handle(default_conf, markets, update, ticker, fee, mocker) -> None:
"""
Test _status() method
"""
update.message.chat.id = 123
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
conf['telegram']['chat_id'] = 123
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_pair_detail_url=MagicMock(),
get_fee=fee,
get_markets=markets
)
msg_mock = MagicMock()
status_table = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_rpc_trade_status=MagicMock(return_value=[{
'trade_id': 1,
'pair': 'ETH/BTC',
'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'date': 'just now',
'open_rate': 1.099e-05,
'close_rate': None,
'current_rate': 1.098e-05,
'amount': 90.99181074,
'close_profit': None,
'current_profit': -0.59,
'open_order': '(limit buy rem=0.00000000)'
}]),
_status_table=status_table,
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(conf)
telegram = Telegram(freqtradebot)
# Create some test data
for _ in range(3):
freqtradebot.create_trade()
telegram._status(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
update.message.text = MagicMock()
update.message.text.replace = MagicMock(return_value='table 2 3')
telegram._status(bot=MagicMock(), update=update)
assert status_table.call_count == 1
def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
""" """
Test _status() method Test _status() method
""" """
@ -290,7 +232,8 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0] assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None: def test_status_table_handle(
default_conf, markets, limit_buy_order, update, ticker, fee, mocker) -> None:
""" """
Test _status_table() method Test _status_table() method
""" """
@ -300,7 +243,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee, get_fee=fee,
get_markets=markets get_markets=markets
) )
@ -326,7 +270,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
telegram._status_table(bot=MagicMock(), update=update) telegram._status_table(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'no active order' in msg_mock.call_args_list[0][0][0] assert 'no active trade' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock() msg_mock.reset_mock()
# Create some test data # Create some test data
@ -756,12 +700,13 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
telegram._forcesell(bot=MagicMock(), update=update) telegram._forcesell(bot=MagicMock(), update=update)
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] last_call = rpc_mock.call_args_list[-1][0][0]['status']
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in last_call
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in last_call
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in last_call
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in last_call
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in last_call
assert '0.919 USD' in last_call
def test_forcesell_down_handle(default_conf, update, ticker, fee, def test_forcesell_down_handle(default_conf, update, ticker, fee,
@ -801,13 +746,14 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
update.message.text = '/forcesell 1' update.message.text = '/forcesell 1'
telegram._forcesell(bot=MagicMock(), update=update) telegram._forcesell(bot=MagicMock(), update=update)
last_call = rpc_mock.call_args_list[-1][0][0]['status']
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in last_call
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in last_call
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in last_call
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in last_call
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in last_call
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0] assert '-0.824 USD' in last_call
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None: def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
@ -841,9 +787,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
assert rpc_mock.call_count == 4 assert rpc_mock.call_count == 4
for args in rpc_mock.call_args_list: for args in rpc_mock.call_args_list:
assert '0.00001098' in args[0][0] assert '0.00001098' in args[0][0]['status']
assert 'loss: -0.59%, -0.00000591 BTC' in args[0][0] assert 'loss: -0.59%, -0.00000591 BTC' in args[0][0]['status']
assert '-0.089 USD' in args[0][0] assert '-0.089 USD' in args[0][0]['status']
def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:

View File

@ -725,7 +725,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
result = freqtrade._process() result = freqtrade._process()
assert result is False assert result is False
assert freqtrade.state == State.STOPPED assert freqtrade.state == State.STOPPED
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0] assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status']
def test_process_trade_handling( def test_process_trade_handling(
@ -1345,13 +1345,14 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] last_call = rpc_mock.call_args_list[-1][0][0]['status']
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in last_call
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in last_call
assert 'Profit' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in last_call
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'Profit' in last_call
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in last_call
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in last_call
assert '0.919 USD' in last_call
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None: def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None:
@ -1387,12 +1388,13 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] last_call = rpc_mock.call_args_list[-1][0][0]['status']
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in last_call
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in last_call
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in last_call
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in last_call
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in last_call
assert '-0.824 USD' in last_call
def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
@ -1429,12 +1431,13 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] last_call = rpc_mock.call_args_list[-1][0][0]['status']
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in last_call
assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in last_call
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in last_call
assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in last_call
assert 'USD' not in rpc_mock.call_args_list[-1][0][0] assert '(profit: 6.11%, 0.00006126)' in last_call
assert 'USD' not in last_call
def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
@ -1471,10 +1474,11 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in rpc_mock.call_args_list[-1][0][0] last_call = rpc_mock.call_args_list[-1][0][0]['status']
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in last_call
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '[ETH/BTC]' in last_call
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in last_call
assert 'loss: -5.48%, -0.00005492' in last_call
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,

View File

@ -1,4 +1,4 @@
ccxt==1.14.256 ccxt==1.14.257
SQLAlchemy==1.2.8 SQLAlchemy==1.2.8
python-telegram-bot==10.1.0 python-telegram-bot==10.1.0
arrow==0.12.1 arrow==0.12.1
@ -23,3 +23,7 @@ coinmarketcap==5.0.3
# Required for plotting data # Required for plotting data
#plotly==2.7.0 #plotly==2.7.0
#Added for local rest client
Flask==1.0.2
flask-restful==0.3.6

View File

@ -3,11 +3,14 @@
"""This script generate json data from bittrex""" """This script generate json data from bittrex"""
import json import json
import sys import sys
import os from pathlib import Path
import arrow import arrow
from freqtrade import (arguments, misc) from freqtrade import arguments
from freqtrade.arguments import TimeRange
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.optimize import download_backtesting_testdata
DEFAULT_DL_PATH = 'user_data/data' DEFAULT_DL_PATH = 'user_data/data'
@ -17,25 +20,27 @@ args = arguments.parse_args()
timeframes = args.timeframes timeframes = args.timeframes
dl_path = os.path.join(DEFAULT_DL_PATH, args.exchange) dl_path = Path(DEFAULT_DL_PATH).joinpath(args.exchange)
if args.export: if args.export:
dl_path = args.export dl_path = Path(args.export)
if not os.path.isdir(dl_path): if not dl_path.is_dir():
sys.exit(f'Directory {dl_path} does not exist.') sys.exit(f'Directory {dl_path} does not exist.')
pairs_file = args.pairs_file if args.pairs_file else os.path.join(dl_path, 'pairs.json') pairs_file = Path(args.pairs_file) if args.pairs_file else dl_path.joinpath('pairs.json')
if not os.path.isfile(pairs_file): if not pairs_file.exists():
sys.exit(f'No pairs file found with path {pairs_file}.') sys.exit(f'No pairs file found with path {pairs_file}.')
with open(pairs_file) as file: with pairs_file.open() as file:
PAIRS = list(set(json.load(file))) PAIRS = list(set(json.load(file)))
PAIRS.sort() PAIRS.sort()
since_time = None
timerange = TimeRange()
if args.days: if args.days:
since_time = arrow.utcnow().shift(days=-args.days).timestamp * 1000 time_since = arrow.utcnow().shift(days=-args.days).strftime("%Y%m%d")
timerange = arguments.parse_timerange(f'{time_since}-')
print(f'About to download pairs: {PAIRS} to {dl_path}') print(f'About to download pairs: {PAIRS} to {dl_path}')
@ -59,21 +64,18 @@ for pair in PAIRS:
print(f"skipping pair {pair}") print(f"skipping pair {pair}")
continue continue
for tick_interval in timeframes: for tick_interval in timeframes:
print(f'downloading pair {pair}, interval {tick_interval}')
data = exchange.get_ticker_history(pair, tick_interval, since_ms=since_time)
if not data:
print('\tNo data was downloaded')
break
print('\tData was downloaded for period %s - %s' % (
arrow.get(data[0][0] / 1000).format(),
arrow.get(data[-1][0] / 1000).format()))
# save data
pair_print = pair.replace('/', '_') pair_print = pair.replace('/', '_')
filename = f'{pair_print}-{tick_interval}.json' filename = f'{pair_print}-{tick_interval}.json'
misc.file_dump_json(os.path.join(dl_path, filename), data) dl_file = dl_path.joinpath(filename)
if args.erase and dl_file.exists():
print(f'Deleting existing data for pair {pair}, interval {tick_interval}')
dl_file.unlink()
print(f'downloading pair {pair}, interval {tick_interval}')
download_backtesting_testdata(str(dl_path), exchange=exchange,
pair=pair,
tick_interval=tick_interval,
timerange=timerange)
if pairs_not_available: if pairs_not_available: