Merge remote-tracking branch 'upstream/feature/flask-rest' into feature/flask-rest
This commit is contained in:
commit
ebd26fea43
@ -1,5 +1,5 @@
|
|||||||
""" FreqTrade bot """
|
""" FreqTrade bot """
|
||||||
__version__ = '0.17.0'
|
__version__ = '0.17.1'
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(BaseException):
|
class DependencyException(BaseException):
|
||||||
|
@ -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'
|
||||||
|
)
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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 """
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
52
freqtrade/tests/rpc/test_rpc_apiserver.py
Normal file
52
freqtrade/tests/rpc/test_rpc_apiserver.py
Normal 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"}'
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user