Merge branch 'release/0.14.1'

This commit is contained in:
gcarq 2017-11-09 23:53:10 +01:00
commit de13df6ede
10 changed files with 244 additions and 140 deletions

View File

@ -1,4 +1,5 @@
[run]
omit =
scripts/*
freqtrade/tests/*
freqtrade/vendor/*

View File

@ -1,3 +1,3 @@
__version__ = '0.14.0'
__version__ = '0.14.1'
from . import main

View File

@ -1,13 +1,11 @@
import logging
import time
from datetime import timedelta
import arrow
import talib.abstract as ta
from pandas import DataFrame, to_datetime
from freqtrade import exchange
from freqtrade.exchange import Bittrex, get_ticker_history
from freqtrade.exchange import get_ticker_history
from freqtrade.vendor.qtpylib.indicators import awesome_oscillator
logging.basicConfig(level=logging.DEBUG,
@ -115,53 +113,3 @@ def get_buy_signal(pair: str) -> bool:
signal = latest['buy'] == 1
logger.debug('buy_trigger: %s (pair=%s, signal=%s)', latest['date'], pair, 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)

View File

@ -63,7 +63,12 @@ def validate_pairs(pairs: List[str]) -> None:
:return: None
"""
markets = _API.get_markets()
stake_cur = _CONF['stake_currency']
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:
raise RuntimeError('Pair {} is not available at {}'.format(pair, _API.name.lower()))

View File

@ -5,9 +5,11 @@ from typing import Optional, Dict
import arrow
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.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.pool import StaticPool
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
@ -17,23 +19,25 @@ _CONF = {}
_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,
registers all known command handlers
and starts polling for message updates
:param config: config to use
:param db_url: database connector string for sqlalchemy (Optional)
:param engine: database engine for sqlalchemy (Optional)
:return: None
"""
_CONF.update(config)
if not db_url:
if not engine:
if _CONF.get('dry_run', False):
db_url = 'sqlite://'
engine = create_engine('sqlite://',
connect_args={'check_same_thread': False},
poolclass=StaticPool,
echo=False)
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))
Trade.session = session()
Trade.query = session.query_property()

View File

@ -11,7 +11,7 @@ from telegram import ParseMode, Bot, Update
from telegram.error import NetworkError
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.persistence import Trade
@ -51,6 +51,7 @@ def init(config: dict) -> None:
CommandHandler('performance', _performance),
CommandHandler('count', _count),
CommandHandler('help', _help),
CommandHandler('version', _version),
]
for handle in handles:
_UPDATER.dispatcher.add_handler(handle)
@ -331,27 +332,30 @@ def _forcesell(bot: Bot, update: Update) -> None:
send_msg('`trader is not running`', bot=bot)
return
try:
trade_id = int(update.message.text
.replace('/forcesell', '')
.strip())
trade_id = update.message.text.replace('/forcesell', '').strip()
if trade_id == 'all':
# Execute sell for all open orders
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
trade = Trade.query.filter(and_(
Trade.id == trade_id,
Trade.is_open.is_(True)
)).first()
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
# Get current rate
current_rate = exchange.get_ticker(trade.pair)['bid']
from freqtrade.main import execute_sell
execute_sell(trade, current_rate)
except ValueError:
send_msg('Invalid argument. Usage: `/forcesell <trade_id>`')
logger.warning('/forcesell: Invalid argument received')
@authorized_only
def _performance(bot: Bot, update: Update) -> None:
@ -397,8 +401,12 @@ def _count(bot: Bot, update: Update) -> None:
return
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)
send_msg(message, parse_mode=ParseMode.HTML)
@ -418,15 +426,28 @@ def _help(bot: Bot, update: Update) -> None:
*/status [table]:* `Lists all open trades`
*table :* `will display trades in a table`
*/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`
*/count:* `Show number of trades running compared to allowed number of trades`
*/balance:* `Show account balance per currency`
*/help:* `This help message`
*/version:* `Show version`
"""
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):
"""
Trim the date so it fits on small screens

View 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'])

View File

@ -4,6 +4,7 @@ from unittest.mock import MagicMock
import pytest
import requests
from sqlalchemy import create_engine
from freqtrade.exchange import Exchanges
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(),
get_ticker=ticker,
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()
assert len(trades) == 0
@ -49,7 +50,7 @@ def test_process_exchange_failures(default_conf, ticker, mocker):
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(side_effect=requests.exceptions.RequestException))
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
result = _process()
assert result is False
assert sleep_mock.has_calls()
@ -64,7 +65,7 @@ def test_process_runtime_error(default_conf, ticker, mocker):
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(side_effect=RuntimeError))
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
assert get_state() == State.RUNNING
result = _process()
@ -82,7 +83,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, mocker):
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'),
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()
assert len(trades) == 0
@ -106,7 +107,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
# Save state of current 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.session.add(trade)
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'),
sell=MagicMock(return_value='mocked_limit_sell'))
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
trade = create_trade(15.0)
trade.update(limit_buy_order)
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'))
# Create trade and sell it
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
trade = create_trade(15.0)
trade.update(limit_buy_order)
trade.update(limit_sell_order)

View File

@ -5,21 +5,19 @@ from random import randint
from unittest.mock import MagicMock
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 freqtrade import __version__
from freqtrade.main import init, create_trade
from freqtrade.misc import update_state, State, get_state
from freqtrade.persistence import Trade
from freqtrade.rpc import telegram
from freqtrade.rpc.telegram import (
_status, _status_table, _profit, _forcesell, _performance, _count, _start, _stop, _balance,
authorized_only, _help, is_enabled, send_msg
)
class MagicBot(MagicMock, Bot):
pass
authorized_only, _help, is_enabled, send_msg,
_version)
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',
validate_pairs=MagicMock(),
get_ticker=ticker)
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
update_state(State.STOPPED)
_status(bot=MagicBot(), update=update)
_status(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
update_state(State.RUNNING)
_status(bot=MagicBot(), update=update)
_status(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'no active trade' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@ -111,7 +109,7 @@ def test_status_handle(default_conf, update, ticker, mocker):
Trade.session.flush()
# 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 '[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(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_order_id'))
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
update_state(State.STOPPED)
_status_table(bot=MagicBot(), update=update)
_status_table(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
update_state(State.RUNNING)
_status_table(bot=MagicBot(), update=update)
_status_table(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'no active order' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@ -149,7 +147,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
Trade.session.add(trade)
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])
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',
validate_pairs=MagicMock(),
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 'no closed trade' in msg_mock.call_args_list[0][0][0]
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
trade.update(limit_buy_order)
_profit(bot=MagicBot(), update=update)
_profit(bot=MagicMock(), update=update)
assert msg_mock.call_count == 2
assert 'no closed trade' in msg_mock.call_args_list[-1][0][0]
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.flush()
_profit(bot=MagicBot(), update=update)
_profit(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
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]
@ -215,7 +213,7 @@ def test_forcesell_handle(default_conf, update, ticker, mocker):
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker)
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
# Create some test data
trade = create_trade(15.0)
@ -225,13 +223,41 @@ def test_forcesell_handle(default_conf, update, ticker, mocker):
Trade.session.flush()
update.message.text = '/forcesell 1'
_forcesell(bot=MagicBot(), update=update)
_forcesell(bot=MagicMock(), update=update)
assert msg_mock.call_count == 2
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]
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):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
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)
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock())
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
# Trader is not running
update_state(State.STOPPED)
update.message.text = '/forcesell 1'
_forcesell(bot=MagicBot(), update=update)
_forcesell(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
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()
update_state(State.RUNNING)
update.message.text = '/forcesell'
_forcesell(bot=MagicBot(), update=update)
_forcesell(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
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()
update_state(State.RUNNING)
update.message.text = '/forcesell 123456'
_forcesell(bot=MagicBot(), update=update)
_forcesell(bot=MagicMock(), update=update)
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('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
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',
validate_pairs=MagicMock(),
get_ticker=ticker)
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
# Create some test data
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.flush()
_performance(bot=MagicBot(), update=update)
_performance(bot=MagicMock(), update=update)
assert msg_mock.call_count == 2
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]
@ -315,26 +342,25 @@ def test_count_handle(default_conf, update, ticker, mocker):
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_order_id'))
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
update_state(State.STOPPED)
_count(bot=MagicBot(), update=update)
_count(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
update_state(State.RUNNING)
# Create some test data
trade = create_trade(15.0)
trade2 = create_trade(15.0)
assert trade
assert trade2
Trade.session.add(trade)
Trade.session.add(trade2)
Trade.session.add(create_trade(15.0))
Trade.session.flush()
_count(bot=MagicBot(), update=update)
line = msg_mock.call_args_list[-1][0][0].split("\n")
assert line[2] == '{}/{}'.format(2, default_conf['max_open_trades'])
msg_mock.reset_mock()
_count(bot=MagicMock(), update=update)
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):
@ -347,11 +373,11 @@ def test_performance_handle_invalid(default_conf, update, mocker):
send_msg=msg_mock)
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock())
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
# Trader is not running
update_state(State.STOPPED)
_performance(bot=MagicBot(), update=update)
_performance(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
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',
_CONF=default_conf,
init=MagicMock())
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
update_state(State.STOPPED)
assert get_state() == State.STOPPED
_start(bot=MagicBot(), update=update)
_start(bot=MagicMock(), update=update)
assert get_state() == State.RUNNING
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',
_CONF=default_conf,
init=MagicMock())
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
update_state(State.RUNNING)
assert get_state() == State.RUNNING
_start(bot=MagicBot(), update=update)
_start(bot=MagicMock(), update=update)
assert get_state() == State.RUNNING
assert msg_mock.call_count == 1
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',
_CONF=default_conf,
init=MagicMock())
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
update_state(State.RUNNING)
assert get_state() == State.RUNNING
_stop(bot=MagicBot(), update=update)
_stop(bot=MagicMock(), update=update)
assert get_state() == State.STOPPED
assert msg_mock.call_count == 1
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',
_CONF=default_conf,
init=MagicMock())
init(default_conf, 'sqlite://')
init(default_conf, create_engine('sqlite://'))
update_state(State.STOPPED)
assert get_state() == State.STOPPED
_stop(bot=MagicBot(), update=update)
_stop(bot=MagicMock(), update=update)
assert get_state() == State.STOPPED
assert msg_mock.call_count == 1
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',
get_balances=MagicMock(return_value=mock_balance))
_balance(bot=MagicBot(), update=update)
_balance(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert '*Currency*: BTC' 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(),
send_msg=msg_mock)
_help(bot=MagicBot(), update=update)
_help(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
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):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.main.telegram',

51
scripts/plot_dataframe.py Executable file
View 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')