Merge branch 'release/0.14.1'
This commit is contained in:
commit
de13df6ede
@ -1,4 +1,5 @@
|
|||||||
[run]
|
[run]
|
||||||
omit =
|
omit =
|
||||||
|
scripts/*
|
||||||
freqtrade/tests/*
|
freqtrade/tests/*
|
||||||
freqtrade/vendor/*
|
freqtrade/vendor/*
|
@ -1,3 +1,3 @@
|
|||||||
__version__ = '0.14.0'
|
__version__ = '0.14.1'
|
||||||
|
|
||||||
from . import main
|
from . import main
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import talib.abstract as ta
|
import talib.abstract as ta
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
from freqtrade import exchange
|
from freqtrade.exchange import get_ticker_history
|
||||||
from freqtrade.exchange import Bittrex, get_ticker_history
|
|
||||||
from freqtrade.vendor.qtpylib.indicators import awesome_oscillator
|
from freqtrade.vendor.qtpylib.indicators import awesome_oscillator
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG,
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
@ -115,53 +113,3 @@ def get_buy_signal(pair: str) -> bool:
|
|||||||
signal = latest['buy'] == 1
|
signal = latest['buy'] == 1
|
||||||
logger.debug('buy_trigger: %s (pair=%s, signal=%s)', latest['date'], pair, signal)
|
logger.debug('buy_trigger: %s (pair=%s, signal=%s)', latest['date'], pair, signal)
|
||||||
return signal
|
return signal
|
||||||
|
|
||||||
|
|
||||||
def plot_analyzed_dataframe(pair: str) -> None:
|
|
||||||
"""
|
|
||||||
Calls analyze() and plots the returned dataframe
|
|
||||||
:param pair: pair as str
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
import matplotlib
|
|
||||||
matplotlib.use("Qt5Agg")
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
# Init Bittrex to use public API
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
|
||||||
dataframe = analyze_ticker(pair)
|
|
||||||
|
|
||||||
# Two subplots sharing x axis
|
|
||||||
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
|
|
||||||
fig.suptitle(pair, fontsize=14, fontweight='bold')
|
|
||||||
ax1.plot(dataframe.index.values, dataframe['close'], label='close')
|
|
||||||
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
|
|
||||||
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA')
|
|
||||||
ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA')
|
|
||||||
ax1.plot(dataframe.index.values, dataframe['blower'], '-.', label='BB low')
|
|
||||||
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy')
|
|
||||||
ax1.legend()
|
|
||||||
|
|
||||||
ax2.plot(dataframe.index.values, dataframe['adx'], label='ADX')
|
|
||||||
ax2.plot(dataframe.index.values, dataframe['mfi'], label='MFI')
|
|
||||||
# ax2.plot(dataframe.index.values, [25] * len(dataframe.index.values))
|
|
||||||
ax2.legend()
|
|
||||||
|
|
||||||
ax3.plot(dataframe.index.values, dataframe['fastk'], label='k')
|
|
||||||
ax3.plot(dataframe.index.values, dataframe['fastd'], label='d')
|
|
||||||
ax3.plot(dataframe.index.values, [20] * len(dataframe.index.values))
|
|
||||||
ax3.legend()
|
|
||||||
|
|
||||||
# Fine-tune figure; make subplots close to each other and hide x ticks for
|
|
||||||
# all but bottom plot.
|
|
||||||
fig.subplots_adjust(hspace=0)
|
|
||||||
plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False)
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Install PYQT5==5.9 manually if you want to test this helper function
|
|
||||||
while True:
|
|
||||||
for p in ['BTC_ANT', 'BTC_ETH', 'BTC_GNT', 'BTC_ETC']:
|
|
||||||
plot_analyzed_dataframe(p)
|
|
||||||
time.sleep(60)
|
|
||||||
|
@ -63,7 +63,12 @@ def validate_pairs(pairs: List[str]) -> None:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
markets = _API.get_markets()
|
markets = _API.get_markets()
|
||||||
|
stake_cur = _CONF['stake_currency']
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
|
if not pair.startswith(stake_cur):
|
||||||
|
raise RuntimeError(
|
||||||
|
'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur)
|
||||||
|
)
|
||||||
if pair not in markets:
|
if pair not in markets:
|
||||||
raise RuntimeError('Pair {} is not available at {}'.format(pair, _API.name.lower()))
|
raise RuntimeError('Pair {} is not available at {}'.format(pair, _API.name.lower()))
|
||||||
|
|
||||||
|
@ -5,9 +5,11 @@ from typing import Optional, Dict
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, create_engine
|
from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, create_engine
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm.scoping import scoped_session
|
from sqlalchemy.orm.scoping import scoped_session
|
||||||
from sqlalchemy.orm.session import sessionmaker
|
from sqlalchemy.orm.session import sessionmaker
|
||||||
|
from sqlalchemy.pool import StaticPool
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG,
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
@ -17,23 +19,25 @@ _CONF = {}
|
|||||||
_DECL_BASE = declarative_base()
|
_DECL_BASE = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
def init(config: dict, db_url: Optional[str] = None) -> None:
|
def init(config: dict, engine: Optional[Engine] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Initializes this module with the given config,
|
Initializes this module with the given config,
|
||||||
registers all known command handlers
|
registers all known command handlers
|
||||||
and starts polling for message updates
|
and starts polling for message updates
|
||||||
:param config: config to use
|
:param config: config to use
|
||||||
:param db_url: database connector string for sqlalchemy (Optional)
|
:param engine: database engine for sqlalchemy (Optional)
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
_CONF.update(config)
|
_CONF.update(config)
|
||||||
if not db_url:
|
if not engine:
|
||||||
if _CONF.get('dry_run', False):
|
if _CONF.get('dry_run', False):
|
||||||
db_url = 'sqlite://'
|
engine = create_engine('sqlite://',
|
||||||
|
connect_args={'check_same_thread': False},
|
||||||
|
poolclass=StaticPool,
|
||||||
|
echo=False)
|
||||||
else:
|
else:
|
||||||
db_url = 'sqlite:///tradesv3.sqlite'
|
engine = create_engine('sqlite:///tradesv3.sqlite')
|
||||||
|
|
||||||
engine = create_engine(db_url, echo=False)
|
|
||||||
session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True))
|
session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True))
|
||||||
Trade.session = session()
|
Trade.session = session()
|
||||||
Trade.query = session.query_property()
|
Trade.query = session.query_property()
|
||||||
|
@ -11,7 +11,7 @@ from telegram import ParseMode, Bot, Update
|
|||||||
from telegram.error import NetworkError
|
from telegram.error import NetworkError
|
||||||
from telegram.ext import CommandHandler, Updater
|
from telegram.ext import CommandHandler, Updater
|
||||||
|
|
||||||
from freqtrade import exchange
|
from freqtrade import exchange, __version__
|
||||||
from freqtrade.misc import get_state, State, update_state
|
from freqtrade.misc import get_state, State, update_state
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
@ -51,6 +51,7 @@ def init(config: dict) -> None:
|
|||||||
CommandHandler('performance', _performance),
|
CommandHandler('performance', _performance),
|
||||||
CommandHandler('count', _count),
|
CommandHandler('count', _count),
|
||||||
CommandHandler('help', _help),
|
CommandHandler('help', _help),
|
||||||
|
CommandHandler('version', _version),
|
||||||
]
|
]
|
||||||
for handle in handles:
|
for handle in handles:
|
||||||
_UPDATER.dispatcher.add_handler(handle)
|
_UPDATER.dispatcher.add_handler(handle)
|
||||||
@ -331,27 +332,30 @@ def _forcesell(bot: Bot, update: Update) -> None:
|
|||||||
send_msg('`trader is not running`', bot=bot)
|
send_msg('`trader is not running`', bot=bot)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
trade_id = update.message.text.replace('/forcesell', '').strip()
|
||||||
trade_id = int(update.message.text
|
if trade_id == 'all':
|
||||||
.replace('/forcesell', '')
|
# Execute sell for all open orders
|
||||||
.strip())
|
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
||||||
|
# Get current rate
|
||||||
|
current_rate = exchange.get_ticker(trade.pair)['bid']
|
||||||
|
from freqtrade.main import execute_sell
|
||||||
|
execute_sell(trade, current_rate)
|
||||||
|
return
|
||||||
|
|
||||||
# Query for trade
|
# Query for trade
|
||||||
trade = Trade.query.filter(and_(
|
trade = Trade.query.filter(and_(
|
||||||
Trade.id == trade_id,
|
Trade.id == trade_id,
|
||||||
Trade.is_open.is_(True)
|
Trade.is_open.is_(True)
|
||||||
)).first()
|
)).first()
|
||||||
if not trade:
|
if not trade:
|
||||||
send_msg('There is no open trade with ID: `{}`'.format(trade_id))
|
send_msg('Invalid argument. See `/help` to view usage')
|
||||||
|
logger.warning('/forcesell: Invalid argument received')
|
||||||
return
|
return
|
||||||
# Get current rate
|
# Get current rate
|
||||||
current_rate = exchange.get_ticker(trade.pair)['bid']
|
current_rate = exchange.get_ticker(trade.pair)['bid']
|
||||||
from freqtrade.main import execute_sell
|
from freqtrade.main import execute_sell
|
||||||
execute_sell(trade, current_rate)
|
execute_sell(trade, current_rate)
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
send_msg('Invalid argument. Usage: `/forcesell <trade_id>`')
|
|
||||||
logger.warning('/forcesell: Invalid argument received')
|
|
||||||
|
|
||||||
|
|
||||||
@authorized_only
|
@authorized_only
|
||||||
def _performance(bot: Bot, update: Update) -> None:
|
def _performance(bot: Bot, update: Update) -> None:
|
||||||
@ -397,8 +401,12 @@ def _count(bot: Bot, update: Update) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
message = '<b>Count:</b>\ncurrent/max\n{}/{}\n'.format(len(trades), _CONF['max_open_trades'])
|
|
||||||
|
|
||||||
|
message = tabulate({
|
||||||
|
'current': [len(trades)],
|
||||||
|
'max': [_CONF['max_open_trades']]
|
||||||
|
}, headers=['current', 'max'], tablefmt='simple')
|
||||||
|
message = "<pre>{}</pre>".format(message)
|
||||||
logger.debug(message)
|
logger.debug(message)
|
||||||
send_msg(message, parse_mode=ParseMode.HTML)
|
send_msg(message, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
@ -418,15 +426,28 @@ def _help(bot: Bot, update: Update) -> None:
|
|||||||
*/status [table]:* `Lists all open trades`
|
*/status [table]:* `Lists all open trades`
|
||||||
*table :* `will display trades in a table`
|
*table :* `will display trades in a table`
|
||||||
*/profit:* `Lists cumulative profit from all finished trades`
|
*/profit:* `Lists cumulative profit from all finished trades`
|
||||||
*/forcesell <trade_id>:* `Instantly sells the given trade, regardless of profit`
|
*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, regardless of profit`
|
||||||
*/performance:* `Show performance of each finished trade grouped by pair`
|
*/performance:* `Show performance of each finished trade grouped by pair`
|
||||||
*/count:* `Show number of trades running compared to allowed number of trades`
|
*/count:* `Show number of trades running compared to allowed number of trades`
|
||||||
*/balance:* `Show account balance per currency`
|
*/balance:* `Show account balance per currency`
|
||||||
*/help:* `This help message`
|
*/help:* `This help message`
|
||||||
|
*/version:* `Show version`
|
||||||
"""
|
"""
|
||||||
send_msg(message, bot=bot)
|
send_msg(message, bot=bot)
|
||||||
|
|
||||||
|
|
||||||
|
@authorized_only
|
||||||
|
def _version(bot: Bot, update: Update) -> None:
|
||||||
|
"""
|
||||||
|
Handler for /version.
|
||||||
|
Show version information
|
||||||
|
:param bot: telegram bot
|
||||||
|
:param update: message update
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
send_msg('*Version:* `{}`'.format(__version__), bot=bot)
|
||||||
|
|
||||||
|
|
||||||
def shorten_date(date):
|
def shorten_date(date):
|
||||||
"""
|
"""
|
||||||
Trim the date so it fits on small screens
|
Trim the date so it fits on small screens
|
||||||
|
34
freqtrade/tests/test_exchange.py
Normal file
34
freqtrade/tests/test_exchange.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from freqtrade.exchange import validate_pairs
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_pairs(default_conf, mocker):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.get_markets = MagicMock(return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT'])
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_pairs_not_available(default_conf, mocker):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.get_markets = MagicMock(return_value=[])
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
with pytest.raises(RuntimeError, match=r'not available'):
|
||||||
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_pairs_not_compatible(default_conf, mocker):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.get_markets = MagicMock(return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT'])
|
||||||
|
default_conf['stake_currency'] = 'ETH'
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
with pytest.raises(RuntimeError, match=r'not compatible'):
|
||||||
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
|
|
@ -4,6 +4,7 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
from freqtrade.exchange import Exchanges
|
from freqtrade.exchange import Exchanges
|
||||||
from freqtrade.main import create_trade, handle_trade, close_trade_if_fulfilled, init, \
|
from freqtrade.main import create_trade, handle_trade, close_trade_if_fulfilled, init, \
|
||||||
@ -20,7 +21,7 @@ def test_process_trade_creation(default_conf, ticker, mocker):
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value='mocked_limit_buy'))
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
assert len(trades) == 0
|
assert len(trades) == 0
|
||||||
@ -49,7 +50,7 @@ def test_process_exchange_failures(default_conf, ticker, mocker):
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(side_effect=requests.exceptions.RequestException))
|
buy=MagicMock(side_effect=requests.exceptions.RequestException))
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
result = _process()
|
result = _process()
|
||||||
assert result is False
|
assert result is False
|
||||||
assert sleep_mock.has_calls()
|
assert sleep_mock.has_calls()
|
||||||
@ -64,7 +65,7 @@ def test_process_runtime_error(default_conf, ticker, mocker):
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(side_effect=RuntimeError))
|
buy=MagicMock(side_effect=RuntimeError))
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
assert get_state() == State.RUNNING
|
assert get_state() == State.RUNNING
|
||||||
|
|
||||||
result = _process()
|
result = _process()
|
||||||
@ -82,7 +83,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, mocker):
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||||
get_order=MagicMock(return_value=limit_buy_order))
|
get_order=MagicMock(return_value=limit_buy_order))
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
assert len(trades) == 0
|
assert len(trades) == 0
|
||||||
@ -106,7 +107,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
|
|||||||
# Save state of current whitelist
|
# Save state of current whitelist
|
||||||
whitelist = copy.deepcopy(default_conf['exchange']['pair_whitelist'])
|
whitelist = copy.deepcopy(default_conf['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
trade = create_trade(15.0)
|
trade = create_trade(15.0)
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
@ -167,7 +168,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
|
|||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||||
sell=MagicMock(return_value='mocked_limit_sell'))
|
sell=MagicMock(return_value='mocked_limit_sell'))
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
trade = create_trade(15.0)
|
trade = create_trade(15.0)
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
@ -197,7 +198,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
|||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value='mocked_limit_buy'))
|
||||||
|
|
||||||
# Create trade and sell it
|
# Create trade and sell it
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
trade = create_trade(15.0)
|
trade = create_trade(15.0)
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
trade.update(limit_sell_order)
|
trade.update(limit_sell_order)
|
||||||
|
@ -5,21 +5,19 @@ from random import randint
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from telegram import Bot, Update, Message, Chat
|
from sqlalchemy import create_engine
|
||||||
|
from telegram import Update, Message, Chat
|
||||||
from telegram.error import NetworkError
|
from telegram.error import NetworkError
|
||||||
|
|
||||||
|
from freqtrade import __version__
|
||||||
from freqtrade.main import init, create_trade
|
from freqtrade.main import init, create_trade
|
||||||
from freqtrade.misc import update_state, State, get_state
|
from freqtrade.misc import update_state, State, get_state
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc import telegram
|
from freqtrade.rpc import telegram
|
||||||
from freqtrade.rpc.telegram import (
|
from freqtrade.rpc.telegram import (
|
||||||
_status, _status_table, _profit, _forcesell, _performance, _count, _start, _stop, _balance,
|
_status, _status_table, _profit, _forcesell, _performance, _count, _start, _stop, _balance,
|
||||||
authorized_only, _help, is_enabled, send_msg
|
authorized_only, _help, is_enabled, send_msg,
|
||||||
)
|
_version)
|
||||||
|
|
||||||
|
|
||||||
class MagicBot(MagicMock, Bot):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def test_is_enabled(default_conf, mocker):
|
def test_is_enabled(default_conf, mocker):
|
||||||
@ -90,16 +88,16 @@ def test_status_handle(default_conf, update, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
_status(bot=MagicBot(), update=update)
|
_status(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
|
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
_status(bot=MagicBot(), update=update)
|
_status(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'no active trade' 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()
|
||||||
@ -111,7 +109,7 @@ def test_status_handle(default_conf, update, ticker, mocker):
|
|||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
# Trigger status while we have a fulfilled order for the open trade
|
# Trigger status while we have a fulfilled order for the open trade
|
||||||
_status(bot=MagicBot(), update=update)
|
_status(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
assert msg_mock.call_count == 2
|
assert msg_mock.call_count == 2
|
||||||
assert '[BTC_ETH]' in msg_mock.call_args_list[-1][0][0]
|
assert '[BTC_ETH]' in msg_mock.call_args_list[-1][0][0]
|
||||||
@ -130,15 +128,15 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_order_id'))
|
buy=MagicMock(return_value='mocked_order_id'))
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
_status_table(bot=MagicBot(), update=update)
|
_status_table(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
|
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
_status_table(bot=MagicBot(), update=update)
|
_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 order' in msg_mock.call_args_list[0][0][0]
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
@ -149,7 +147,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
|
|||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
_status_table(bot=MagicBot(), update=update)
|
_status_table(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
text = re.sub('</?pre>', '', msg_mock.call_args_list[-1][0][0])
|
text = re.sub('</?pre>', '', msg_mock.call_args_list[-1][0][0])
|
||||||
line = text.split("\n")
|
line = text.split("\n")
|
||||||
@ -171,9 +169,9 @@ def test_profit_handle(default_conf, update, ticker, limit_buy_order, limit_sell
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
_profit(bot=MagicBot(), update=update)
|
_profit(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'no closed trade' in msg_mock.call_args_list[0][0][0]
|
assert 'no closed trade' in msg_mock.call_args_list[0][0][0]
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
@ -185,7 +183,7 @@ def test_profit_handle(default_conf, update, ticker, limit_buy_order, limit_sell
|
|||||||
# Simulate fulfilled LIMIT_BUY order for trade
|
# Simulate fulfilled LIMIT_BUY order for trade
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
|
|
||||||
_profit(bot=MagicBot(), update=update)
|
_profit(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 2
|
assert msg_mock.call_count == 2
|
||||||
assert 'no closed trade' in msg_mock.call_args_list[-1][0][0]
|
assert 'no closed trade' in msg_mock.call_args_list[-1][0][0]
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
@ -198,7 +196,7 @@ def test_profit_handle(default_conf, update, ticker, limit_buy_order, limit_sell
|
|||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
_profit(bot=MagicBot(), update=update)
|
_profit(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*ROI:* `1.50701325 (10.05%)`' in msg_mock.call_args_list[-1][0][0]
|
assert '*ROI:* `1.50701325 (10.05%)`' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert 'Best Performing:* `BTC_ETH: 10.05%`' in msg_mock.call_args_list[-1][0][0]
|
assert 'Best Performing:* `BTC_ETH: 10.05%`' in msg_mock.call_args_list[-1][0][0]
|
||||||
@ -215,7 +213,7 @@ def test_forcesell_handle(default_conf, update, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
trade = create_trade(15.0)
|
trade = create_trade(15.0)
|
||||||
@ -225,13 +223,41 @@ def test_forcesell_handle(default_conf, update, ticker, mocker):
|
|||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
update.message.text = '/forcesell 1'
|
update.message.text = '/forcesell 1'
|
||||||
_forcesell(bot=MagicBot(), update=update)
|
_forcesell(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
assert msg_mock.call_count == 2
|
assert msg_mock.call_count == 2
|
||||||
assert 'Selling [BTC/ETH]' in msg_mock.call_args_list[-1][0][0]
|
assert 'Selling [BTC/ETH]' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '0.07256061 (profit: ~-0.64%)' in msg_mock.call_args_list[-1][0][0]
|
assert '0.07256061 (profit: ~-0.64%)' in msg_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_forcesell_all_handle(default_conf, update, ticker, mocker):
|
||||||
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch.multiple('freqtrade.main.telegram',
|
||||||
|
_CONF=default_conf,
|
||||||
|
init=MagicMock(),
|
||||||
|
send_msg=msg_mock)
|
||||||
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=ticker)
|
||||||
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
|
# Create some test data
|
||||||
|
for _ in range(4):
|
||||||
|
Trade.session.add(create_trade(15.0))
|
||||||
|
Trade.session.flush()
|
||||||
|
|
||||||
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
|
update.message.text = '/forcesell all'
|
||||||
|
_forcesell(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
|
assert msg_mock.call_count == 4
|
||||||
|
for args in msg_mock.call_args_list:
|
||||||
|
assert '0.07256061 (profit: ~-0.64%)' in args[0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_forcesell_handle_invalid(default_conf, update, mocker):
|
def test_forcesell_handle_invalid(default_conf, update, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
|
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
|
||||||
@ -242,12 +268,12 @@ def test_forcesell_handle_invalid(default_conf, update, mocker):
|
|||||||
send_msg=msg_mock)
|
send_msg=msg_mock)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock())
|
validate_pairs=MagicMock())
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Trader is not running
|
# Trader is not running
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
update.message.text = '/forcesell 1'
|
update.message.text = '/forcesell 1'
|
||||||
_forcesell(bot=MagicBot(), update=update)
|
_forcesell(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
@ -255,7 +281,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker):
|
|||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
update.message.text = '/forcesell'
|
update.message.text = '/forcesell'
|
||||||
_forcesell(bot=MagicBot(), update=update)
|
_forcesell(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Invalid argument' in msg_mock.call_args_list[0][0][0]
|
assert 'Invalid argument' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
@ -263,12 +289,13 @@ def test_forcesell_handle_invalid(default_conf, update, mocker):
|
|||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
update.message.text = '/forcesell 123456'
|
update.message.text = '/forcesell 123456'
|
||||||
_forcesell(bot=MagicBot(), update=update)
|
_forcesell(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'no open trade' in msg_mock.call_args_list[0][0][0]
|
assert 'Invalid argument.' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_performance_handle(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
|
def test_performance_handle(
|
||||||
|
default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
|
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
@ -279,7 +306,7 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order, limit
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
trade = create_trade(15.0)
|
trade = create_trade(15.0)
|
||||||
@ -296,7 +323,7 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order, limit
|
|||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
_performance(bot=MagicBot(), update=update)
|
_performance(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 2
|
assert msg_mock.call_count == 2
|
||||||
assert 'Performance' in msg_mock.call_args_list[-1][0][0]
|
assert 'Performance' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '<code>BTC_ETH\t10.05%</code>' in msg_mock.call_args_list[-1][0][0]
|
assert '<code>BTC_ETH\t10.05%</code>' in msg_mock.call_args_list[-1][0][0]
|
||||||
@ -315,26 +342,25 @@ def test_count_handle(default_conf, update, ticker, mocker):
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_order_id'))
|
buy=MagicMock(return_value='mocked_order_id'))
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
_count(bot=MagicBot(), update=update)
|
_count(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
trade = create_trade(15.0)
|
Trade.session.add(create_trade(15.0))
|
||||||
trade2 = create_trade(15.0)
|
|
||||||
assert trade
|
|
||||||
assert trade2
|
|
||||||
Trade.session.add(trade)
|
|
||||||
Trade.session.add(trade2)
|
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
_count(bot=MagicBot(), update=update)
|
msg_mock.reset_mock()
|
||||||
line = msg_mock.call_args_list[-1][0][0].split("\n")
|
_count(bot=MagicMock(), update=update)
|
||||||
assert line[2] == '{}/{}'.format(2, default_conf['max_open_trades'])
|
|
||||||
|
msg = '<pre> current max\n--------- -----\n 1 {}</pre>'.format(
|
||||||
|
default_conf['max_open_trades']
|
||||||
|
)
|
||||||
|
assert msg in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_performance_handle_invalid(default_conf, update, mocker):
|
def test_performance_handle_invalid(default_conf, update, mocker):
|
||||||
@ -347,11 +373,11 @@ def test_performance_handle_invalid(default_conf, update, mocker):
|
|||||||
send_msg=msg_mock)
|
send_msg=msg_mock)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock())
|
validate_pairs=MagicMock())
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Trader is not running
|
# Trader is not running
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
_performance(bot=MagicBot(), update=update)
|
_performance(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
@ -366,10 +392,10 @@ def test_start_handle(default_conf, update, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
_CONF=default_conf,
|
_CONF=default_conf,
|
||||||
init=MagicMock())
|
init=MagicMock())
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
assert get_state() == State.STOPPED
|
assert get_state() == State.STOPPED
|
||||||
_start(bot=MagicBot(), update=update)
|
_start(bot=MagicMock(), update=update)
|
||||||
assert get_state() == State.RUNNING
|
assert get_state() == State.RUNNING
|
||||||
assert msg_mock.call_count == 0
|
assert msg_mock.call_count == 0
|
||||||
|
|
||||||
@ -384,10 +410,10 @@ def test_start_handle_already_running(default_conf, update, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
_CONF=default_conf,
|
_CONF=default_conf,
|
||||||
init=MagicMock())
|
init=MagicMock())
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
assert get_state() == State.RUNNING
|
assert get_state() == State.RUNNING
|
||||||
_start(bot=MagicBot(), update=update)
|
_start(bot=MagicMock(), update=update)
|
||||||
assert get_state() == State.RUNNING
|
assert get_state() == State.RUNNING
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'already running' in msg_mock.call_args_list[0][0][0]
|
assert 'already running' in msg_mock.call_args_list[0][0][0]
|
||||||
@ -403,10 +429,10 @@ def test_stop_handle(default_conf, update, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
_CONF=default_conf,
|
_CONF=default_conf,
|
||||||
init=MagicMock())
|
init=MagicMock())
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
assert get_state() == State.RUNNING
|
assert get_state() == State.RUNNING
|
||||||
_stop(bot=MagicBot(), update=update)
|
_stop(bot=MagicMock(), update=update)
|
||||||
assert get_state() == State.STOPPED
|
assert get_state() == State.STOPPED
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Stopping trader' in msg_mock.call_args_list[0][0][0]
|
assert 'Stopping trader' in msg_mock.call_args_list[0][0][0]
|
||||||
@ -422,10 +448,10 @@ def test_stop_handle_already_stopped(default_conf, update, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
_CONF=default_conf,
|
_CONF=default_conf,
|
||||||
init=MagicMock())
|
init=MagicMock())
|
||||||
init(default_conf, 'sqlite://')
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
assert get_state() == State.STOPPED
|
assert get_state() == State.STOPPED
|
||||||
_stop(bot=MagicBot(), update=update)
|
_stop(bot=MagicMock(), update=update)
|
||||||
assert get_state() == State.STOPPED
|
assert get_state() == State.STOPPED
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'already stopped' in msg_mock.call_args_list[0][0][0]
|
assert 'already stopped' in msg_mock.call_args_list[0][0][0]
|
||||||
@ -454,7 +480,7 @@ def test_balance_handle(default_conf, update, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
get_balances=MagicMock(return_value=mock_balance))
|
get_balances=MagicMock(return_value=mock_balance))
|
||||||
|
|
||||||
_balance(bot=MagicBot(), update=update)
|
_balance(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*Currency*: BTC' in msg_mock.call_args_list[0][0][0]
|
assert '*Currency*: BTC' in msg_mock.call_args_list[0][0][0]
|
||||||
assert 'Balance' in msg_mock.call_args_list[0][0][0]
|
assert 'Balance' in msg_mock.call_args_list[0][0][0]
|
||||||
@ -468,11 +494,24 @@ def test_help_handle(default_conf, update, mocker):
|
|||||||
init=MagicMock(),
|
init=MagicMock(),
|
||||||
send_msg=msg_mock)
|
send_msg=msg_mock)
|
||||||
|
|
||||||
_help(bot=MagicBot(), update=update)
|
_help(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*/help:* `This help message`' in msg_mock.call_args_list[0][0][0]
|
assert '*/help:* `This help message`' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_handle(default_conf, update, mocker):
|
||||||
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch.multiple('freqtrade.main.telegram',
|
||||||
|
_CONF=default_conf,
|
||||||
|
init=MagicMock(),
|
||||||
|
send_msg=msg_mock)
|
||||||
|
|
||||||
|
_version(bot=MagicMock(), update=update)
|
||||||
|
assert msg_mock.call_count == 1
|
||||||
|
assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg(default_conf, mocker):
|
def test_send_msg(default_conf, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch.multiple('freqtrade.main.telegram',
|
mocker.patch.multiple('freqtrade.main.telegram',
|
||||||
|
51
scripts/plot_dataframe.py
Executable file
51
scripts/plot_dataframe.py
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import matplotlib # Install PYQT5 manually if you want to test this helper function
|
||||||
|
matplotlib.use("Qt5Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
from freqtrade import exchange, analyze
|
||||||
|
|
||||||
|
|
||||||
|
def plot_analyzed_dataframe(pair: str) -> None:
|
||||||
|
"""
|
||||||
|
Calls analyze() and plots the returned dataframe
|
||||||
|
:param pair: pair as str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Init Bittrex to use public API
|
||||||
|
exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
|
||||||
|
dataframe = analyze.analyze_ticker(pair)
|
||||||
|
|
||||||
|
# Two subplots sharing x axis
|
||||||
|
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
|
||||||
|
fig.suptitle(pair, fontsize=14, fontweight='bold')
|
||||||
|
ax1.plot(dataframe.index.values, dataframe['close'], label='close')
|
||||||
|
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
|
||||||
|
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA')
|
||||||
|
ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA')
|
||||||
|
ax1.plot(dataframe.index.values, dataframe['blower'], '-.', label='BB low')
|
||||||
|
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy')
|
||||||
|
ax1.legend()
|
||||||
|
|
||||||
|
ax2.plot(dataframe.index.values, dataframe['adx'], label='ADX')
|
||||||
|
ax2.plot(dataframe.index.values, dataframe['mfi'], label='MFI')
|
||||||
|
# ax2.plot(dataframe.index.values, [25] * len(dataframe.index.values))
|
||||||
|
ax2.legend()
|
||||||
|
|
||||||
|
ax3.plot(dataframe.index.values, dataframe['fastk'], label='k')
|
||||||
|
ax3.plot(dataframe.index.values, dataframe['fastd'], label='d')
|
||||||
|
ax3.plot(dataframe.index.values, [20] * len(dataframe.index.values))
|
||||||
|
ax3.legend()
|
||||||
|
|
||||||
|
# Fine-tune figure; make subplots close to each other and hide x ticks for
|
||||||
|
# all but bottom plot.
|
||||||
|
fig.subplots_adjust(hspace=0)
|
||||||
|
plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
plot_analyzed_dataframe('BTC_ETH')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user