Merge branch 'develop' into split_btanalysis_load_trades

This commit is contained in:
Matthias 2019-06-24 07:15:14 +02:00
commit eba7327058
15 changed files with 197 additions and 134 deletions

View File

@ -6,7 +6,7 @@ This page explains how to prepare your environment for running the bot.
Before running your bot in production you will need to setup few Before running your bot in production you will need to setup few
external API. In production mode, the bot will require valid Exchange API external API. In production mode, the bot will require valid Exchange API
credentials. We also reccomend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot) (optional but recommended). credentials. We also recommend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot) (optional but recommended).
- [Setup your exchange account](#setup-your-exchange-account) - [Setup your exchange account](#setup-your-exchange-account)

View File

@ -87,9 +87,9 @@ class Arguments(object):
) )
parser.add_argument( parser.add_argument(
'-c', '--config', '-c', '--config',
help="Specify configuration file (default: %(default)s). " help=f'Specify configuration file (default: {constants.DEFAULT_CONFIG}). '
"Multiple --config options may be used. " f'Multiple --config options may be used. '
"Can be set to '-' to read config from stdin.", f'Can be set to `-` to read config from stdin.',
dest='config', dest='config',
action='append', action='append',
metavar='PATH', metavar='PATH',
@ -122,9 +122,9 @@ class Arguments(object):
) )
parser.add_argument( parser.add_argument(
'--dynamic-whitelist', '--dynamic-whitelist',
help='Dynamically generate and update whitelist' help='Dynamically generate and update whitelist '
' based on 24h BaseVolume (default: %(const)s).' 'based on 24h BaseVolume (default: %(const)s). '
' DEPRECATED.', 'DEPRECATED.',
dest='dynamic_whitelist', dest='dynamic_whitelist',
const=constants.DYNAMIC_WHITELIST, const=constants.DYNAMIC_WHITELIST,
type=int, type=int,
@ -133,8 +133,8 @@ class Arguments(object):
) )
parser.add_argument( parser.add_argument(
'--db-url', '--db-url',
help='Override trades database URL, this is useful if dry_run is enabled' help=f'Override trades database URL, this is useful if dry_run is enabled '
' or in custom deployments (default: %(default)s).', f'or in custom deployments (default: {constants.DEFAULT_DB_DRYRUN_URL}.',
dest='db_url', dest='db_url',
metavar='PATH', metavar='PATH',
) )
@ -228,10 +228,10 @@ class Arguments(object):
) )
parser.add_argument( parser.add_argument(
'--export-filename', '--export-filename',
help='Save backtest results to this filename \ help='Save backtest results to this filename '
requires --export to be set as well\ 'requires --export to be set as well. '
Example --export-filename=user_data/backtest_data/backtest_today.json\ 'Example --export-filename=user_data/backtest_data/backtest_today.json '
(default: %(default)s)', '(default: %(default)s)',
default=os.path.join('user_data', 'backtest_data', 'backtest-result.json'), default=os.path.join('user_data', 'backtest_data', 'backtest-result.json'),
dest='exportfilename', dest='exportfilename',
metavar='PATH', metavar='PATH',
@ -246,8 +246,8 @@ class Arguments(object):
parser.add_argument( parser.add_argument(
'--stoplosses', '--stoplosses',
help='Defines a range of stoploss against which edge will assess the strategy ' help='Defines a range of stoploss against which edge will assess the strategy '
'the format is "min,max,step" (without any space).' 'the format is "min,max,step" (without any space). '
'example: --stoplosses=-0.01,-0.1,-0.001', 'Example: --stoplosses=-0.01,-0.1,-0.001',
dest='stoploss_range', dest='stoploss_range',
) )
@ -289,8 +289,8 @@ class Arguments(object):
) )
parser.add_argument( parser.add_argument(
'-s', '--spaces', '-s', '--spaces',
help='Specify which parameters to hyperopt. Space separate list. \ help='Specify which parameters to hyperopt. Space separate list. '
Default: %(default)s.', 'Default: %(default)s.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss'], choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
default='all', default='all',
nargs='+', nargs='+',
@ -467,13 +467,14 @@ class Arguments(object):
) )
parser.add_argument( parser.add_argument(
'--exchange', '--exchange',
help='Exchange name (default: %(default)s). Only valid if no config is provided.', help=f'Exchange name (default: {constants.DEFAULT_EXCHANGE}). '
f'Only valid if no config is provided.',
dest='exchange', dest='exchange',
) )
parser.add_argument( parser.add_argument(
'-t', '--timeframes', '-t', '--timeframes',
help='Specify which tickers to download. Space separated list. \ help=f'Specify which tickers to download. Space separated list. '
Default: %(default)s.', f'Default: {constants.DEFAULT_DOWNLOAD_TICKER_INTERVALS}.',
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
'6h', '8h', '12h', '1d', '3d', '1w'], '6h', '8h', '12h', '1d', '3d', '1w'],
nargs='+', nargs='+',

View File

@ -4,6 +4,7 @@
bot constants bot constants
""" """
DEFAULT_CONFIG = 'config.json' DEFAULT_CONFIG = 'config.json'
DEFAULT_EXCHANGE = 'bittrex'
DYNAMIC_WHITELIST = 20 # pairs DYNAMIC_WHITELIST = 20 # pairs
PROCESS_THROTTLE_SECS = 5 # sec PROCESS_THROTTLE_SECS = 5 # sec
DEFAULT_TICKER_INTERVAL = 5 # min DEFAULT_TICKER_INTERVAL = 5 # min
@ -21,6 +22,7 @@ ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList']
DRY_RUN_WALLET = 999.9 DRY_RUN_WALLET = 999.9
DEFAULT_DOWNLOAD_TICKER_INTERVALS = '1m 5m'
TICKER_INTERVALS = [ TICKER_INTERVALS = [
'1m', '3m', '5m', '15m', '30m', '1m', '3m', '5m', '15m', '30m',

View File

@ -23,7 +23,7 @@ def load_backtest_data(filename) -> pd.DataFrame:
""" """
Load backtest data file. Load backtest data file.
:param filename: pathlib.Path object, or string pointing to the file. :param filename: pathlib.Path object, or string pointing to the file.
:return a dataframe with the analysis results :return: a dataframe with the analysis results
""" """
if isinstance(filename, str): if isinstance(filename, str):
filename = Path(filename) filename = Path(filename)
@ -77,7 +77,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
""" """
Load trades from a DB (using dburl) Load trades from a DB (using dburl)
:param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite) :param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
:returns: Dataframe containing Trades :return: Dataframe containing Trades
""" """
trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS) trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS)
persistence.init(db_url, clean_open_orders=False) persistence.init(db_url, clean_open_orders=False)

View File

@ -63,7 +63,7 @@ def load_tickerdata_file(
timerange: Optional[TimeRange] = None) -> Optional[list]: timerange: Optional[TimeRange] = None) -> Optional[list]:
""" """
Load a pair from file, either .json.gz or .json Load a pair from file, either .json.gz or .json
:return tickerlist or None if unsuccesful :return: tickerlist or None if unsuccesful
""" """
filename = pair_data_filename(datadir, pair, ticker_interval) filename = pair_data_filename(datadir, pair, ticker_interval)
pairdata = misc.file_load_json(filename) pairdata = misc.file_load_json(filename)

View File

@ -53,8 +53,7 @@ class FreqtradeBot(object):
self.rpc: RPCManager = RPCManager(self) self.rpc: RPCManager = RPCManager(self)
exchange_name = self.config.get('exchange', {}).get('name').title() self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
self.exchange = ExchangeResolver(exchange_name, self.config).exchange
self.wallets = Wallets(self.config, self.exchange) self.wallets = Wallets(self.config, self.exchange)
self.dataprovider = DataProvider(self.config, self.exchange) self.dataprovider = DataProvider(self.config, self.exchange)
@ -691,13 +690,22 @@ class FreqtradeBot(object):
# cancelling the current stoploss on exchange first # cancelling the current stoploss on exchange first
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})' logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
'in order to add another one ...', order['id']) 'in order to add another one ...', order['id'])
if self.exchange.cancel_order(order['id'], trade.pair): try:
self.exchange.cancel_order(order['id'], trade.pair)
except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {order['id']} "
f"for pair {trade.pair}")
try:
# creating the new one # creating the new one
stoploss_order_id = self.exchange.stoploss_limit( stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount, pair=trade.pair, amount=trade.amount,
stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99 stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99
)['id'] )['id']
trade.stoploss_order_id = str(stoploss_order_id) trade.stoploss_order_id = str(stoploss_order_id)
except DependencyException:
logger.exception(f"Could create trailing stoploss order "
f"for pair {trade.pair}.")
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool:
if self.edge: if self.edge:
@ -843,7 +851,10 @@ class FreqtradeBot(object):
# First cancelling stoploss on exchange ... # First cancelling stoploss on exchange ...
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
self.exchange.cancel_order(trade.stoploss_order_id, trade.pair) try:
self.exchange.cancel_order(trade.stoploss_order_id, trade.pair)
except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
# Execute sell and update trade record # Execute sell and update trade record
order_id = self.exchange.sell(pair=str(trade.pair), order_id = self.exchange.sell(pair=str(trade.pair),

View File

@ -63,8 +63,7 @@ class Backtesting(object):
self.config['dry_run'] = True self.config['dry_run'] = True
self.strategylist: List[IStrategy] = [] self.strategylist: List[IStrategy] = []
exchange_name = self.config.get('exchange', {}).get('name').title() self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
self.exchange = ExchangeResolver(exchange_name, self.config).exchange
self.fee = self.exchange.get_fee() self.fee = self.exchange.get_fee()
if self.config.get('runmode') != RunMode.HYPEROPT: if self.config.get('runmode') != RunMode.HYPEROPT:

View File

@ -22,6 +22,7 @@ class ExchangeResolver(IResolver):
Load the custom class from config parameter Load the custom class from config parameter
:param config: configuration dictionary :param config: configuration dictionary
""" """
exchange_name = exchange_name.title()
try: try:
self.exchange = self._load_exchange(exchange_name, kwargs={'config': config}) self.exchange = self._load_exchange(exchange_name, kwargs={'config': config})
except ImportError: except ImportError:

View File

@ -158,7 +158,7 @@ class IStrategy(ABC):
""" """
Parses the given ticker history and returns a populated DataFrame Parses the given ticker history and returns a populated DataFrame
add several TA indicators and buy signal to it add several TA indicators and buy signal to it
:return DataFrame with ticker data and indicator data :return: DataFrame with ticker data and indicator data
""" """
pair = str(metadata.get('pair')) pair = str(metadata.get('pair'))
@ -351,7 +351,7 @@ class IStrategy(ABC):
""" """
Based an earlier trade and current price and ROI configuration, decides whether bot should Based an earlier trade and current price and ROI configuration, decides whether bot should
sell. Requires current_profit to be in percent!! sell. Requires current_profit to be in percent!!
:return True if bot should sell at current rate :return: True if bot should sell at current rate
""" """
# Check if time matches and current rate is above threshold # Check if time matches and current rate is above threshold

View File

@ -5,6 +5,7 @@ import re
from copy import deepcopy from copy import deepcopy
from datetime import datetime from datetime import datetime
from functools import reduce from functools import reduce
from pathlib import Path
from typing import List from typing import List
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
@ -60,7 +61,7 @@ def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchang
patch_exchange(mocker, api_mock, id) patch_exchange(mocker, api_mock, id)
config["exchange"]["name"] = id config["exchange"]["name"] = id
try: try:
exchange = ExchangeResolver(id.title(), config).exchange exchange = ExchangeResolver(id, config).exchange
except ImportError: except ImportError:
exchange = Exchange(config) exchange = Exchange(config)
return exchange return exchange
@ -110,11 +111,23 @@ def patch_freqtradebot(mocker, config) -> None:
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
"""
This function patches _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: FreqtradeBot
"""
patch_freqtradebot(mocker, config) patch_freqtradebot(mocker, config)
return FreqtradeBot(config) return FreqtradeBot(config)
def get_patched_worker(mocker, config) -> Worker: def get_patched_worker(mocker, config) -> Worker:
"""
This function patches _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: Worker
"""
patch_freqtradebot(mocker, config) patch_freqtradebot(mocker, config)
return Worker(args=None, config=config) return Worker(args=None, config=config)
@ -865,7 +878,7 @@ def tickers():
@pytest.fixture @pytest.fixture
def result(): def result():
with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file: with Path('freqtrade/tests/testdata/UNITTEST_BTC-1m.json').open('r') as data_file:
return parse_ticker_dataframe(json.load(data_file), '1m', fill_missing=True) return parse_ticker_dataframe(json.load(data_file), '1m', fill_missing=True)
# FIX: # FIX:

View File

@ -124,14 +124,14 @@ def test_exchange_resolver(default_conf, mocker, caplog):
caplog.record_tuples) caplog.record_tuples)
caplog.clear() caplog.clear()
exchange = ExchangeResolver('Kraken', default_conf).exchange exchange = ExchangeResolver('kraken', default_conf).exchange
assert isinstance(exchange, Exchange) assert isinstance(exchange, Exchange)
assert isinstance(exchange, Kraken) assert isinstance(exchange, Kraken)
assert not isinstance(exchange, Binance) assert not isinstance(exchange, Binance)
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.", assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
caplog.record_tuples) caplog.record_tuples)
exchange = ExchangeResolver('Binance', default_conf).exchange exchange = ExchangeResolver('binance', default_conf).exchange
assert isinstance(exchange, Exchange) assert isinstance(exchange, Exchange)
assert isinstance(exchange, Binance) assert isinstance(exchange, Binance)
assert not isinstance(exchange, Kraken) assert not isinstance(exchange, Kraken)

View File

@ -75,14 +75,10 @@ def test_load_strategy_byte64(result):
def test_load_strategy_invalid_directory(result, caplog): def test_load_strategy_invalid_directory(result, caplog):
resolver = StrategyResolver() resolver = StrategyResolver()
extra_dir = path.join('some', 'path') extra_dir = Path.cwd() / 'some/path'
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
assert ( assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog.record_tuples)
'freqtrade.resolvers.strategy_resolver',
logging.WARNING,
'Path "{}" does not exist'.format(extra_dir),
) in caplog.record_tuples
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})

View File

@ -19,47 +19,13 @@ from freqtrade.persistence import Trade
from freqtrade.rpc import RPCMessageType from freqtrade.rpc import RPCMessageType
from freqtrade.state import State from freqtrade.state import State
from freqtrade.strategy.interface import SellCheckTuple, SellType from freqtrade.strategy.interface import SellCheckTuple, SellType
from freqtrade.tests.conftest import (log_has, log_has_re, patch_edge, from freqtrade.tests.conftest import (get_patched_freqtradebot,
patch_exchange, patch_get_signal, get_patched_worker, log_has, log_has_re,
patch_wallet) patch_edge, patch_exchange,
patch_get_signal, patch_wallet)
from freqtrade.worker import Worker from freqtrade.worker import Worker
# Functions for recurrent object patching
def patch_freqtradebot(mocker, config) -> None:
"""
This function patches _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: None
"""
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
patch_exchange(mocker)
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
"""
This function patches _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: FreqtradeBot
"""
patch_freqtradebot(mocker, config)
return FreqtradeBot(config)
def get_patched_worker(mocker, config) -> Worker:
"""
This function patches _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: Worker
"""
patch_freqtradebot(mocker, config)
return Worker(args=None, config=config)
def patch_RPCManager(mocker) -> MagicMock: def patch_RPCManager(mocker) -> MagicMock:
""" """
This function mock RPC manager to avoid repeating this code in almost every tests This function mock RPC manager to avoid repeating this code in almost every tests
@ -1176,6 +1142,77 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
stop_price=0.00002344 * 0.95) stop_price=0.00002344 * 0.95)
def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, caplog,
markets, limit_buy_order,
limit_sell_order) -> None:
# When trailing stoploss is set
stoploss_limit = MagicMock(return_value={'id': 13434334})
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
}),
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
markets=PropertyMock(return_value=markets),
stoploss_limit=stoploss_limit
)
# enabling TSL
default_conf['trailing_stop'] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf)
# enabling stoploss on exchange
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# setting stoploss
freqtrade.strategy.stoploss = -0.05
# setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = "abcd"
trade.stop_loss = 0.2
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime.replace(tzinfo=None)
stoploss_order_hanging = {
'id': "abcd",
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'info': {
'stopPrice': '0.1'
}
}
mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging)
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*",
caplog.record_tuples)
# Still try to create order
assert stoploss_limit.call_count == 1
# Fail creating stoploss order
caplog.clear()
cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_order", MagicMock())
mocker.patch("freqtrade.exchange.Exchange.stoploss_limit", side_effect=DependencyException())
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1
assert log_has_re(r"Could create trailing stoploss order for pair ETH/BTC\..*",
caplog.record_tuples)
def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
markets, limit_buy_order, limit_sell_order) -> None: markets, limit_buy_order, limit_sell_order) -> None:
@ -2108,6 +2145,36 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
} == last_msg } == last_msg
def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee,
markets, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException())
sellmock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
markets=PropertyMock(return_value=markets),
sell=sellmock
)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
Trade.session = MagicMock()
freqtrade.config['dry_run'] = False
trade.stoploss_order_id = "abcd"
freqtrade.execute_sell(trade=trade, limit=1234,
sell_reason=SellType.STOP_LOSS)
assert sellmock.call_count == 1
assert log_has('Could not cancel stoploss order abcd', caplog.record_tuples)
def test_execute_sell_with_stoploss_on_exchange(default_conf, def test_execute_sell_with_stoploss_on_exchange(default_conf,
ticker, fee, ticker_sell_up, ticker, fee, ticker_sell_up,
markets, mocker) -> None: markets, mocker) -> None:

View File

@ -31,7 +31,7 @@ from typing import Any, Dict, List
import pandas as pd import pandas as pd
from freqtrade.arguments import Arguments, TimeRange from freqtrade.arguments import Arguments
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.btanalysis import (extract_trades_of_period, from freqtrade.data.btanalysis import (extract_trades_of_period,
load_backtest_data, load_trades_from_db) load_backtest_data, load_trades_from_db)
@ -43,38 +43,6 @@ from freqtrade.state import RunMode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_tickers_data(strategy, exchange, pairs: List[str], timerange: TimeRange,
datadir: Path, refresh_pairs: bool, live: bool):
"""
Get tickers data for each pairs on live or local, option defined in args
:return: dictionary of tickers. output format: {'pair': tickersdata}
"""
ticker_interval = strategy.ticker_interval
tickers = history.load_data(
datadir=datadir,
pairs=pairs,
ticker_interval=ticker_interval,
refresh_pairs=refresh_pairs,
timerange=timerange,
exchange=exchange,
live=live,
)
# No ticker found, impossible to download, len mismatch
for pair, data in tickers.copy().items():
logger.debug("checking tickers data of pair: %s", pair)
logger.debug("data.empty: %s", data.empty)
logger.debug("len(data): %s", len(data))
if data.empty:
del tickers[pair]
logger.info(
'An issue occured while retreiving data of %s pair, please retry '
'using -l option for live or --refresh-pairs-cached', pair)
return tickers
def generate_dataframe(strategy, tickers, pair) -> pd.DataFrame: def generate_dataframe(strategy, tickers, pair) -> pd.DataFrame:
""" """
Get tickers then Populate strategy indicators and signals, then return the full dataframe Get tickers then Populate strategy indicators and signals, then return the full dataframe
@ -100,8 +68,7 @@ def analyse_and_plot_pairs(config: Dict[str, Any]):
-Generate plot files -Generate plot files
:return: None :return: None
""" """
exchange_name = config.get('exchange', {}).get('name').title() exchange = ExchangeResolver(config.get('exchange', {}).get('name'), config).exchange
exchange = ExchangeResolver(exchange_name, config).exchange
strategy = StrategyResolver(config).strategy strategy = StrategyResolver(config).strategy
if "pairs" in config: if "pairs" in config:
@ -113,10 +80,16 @@ def analyse_and_plot_pairs(config: Dict[str, Any]):
timerange = Arguments.parse_timerange(config["timerange"]) timerange = Arguments.parse_timerange(config["timerange"])
ticker_interval = strategy.ticker_interval ticker_interval = strategy.ticker_interval
tickers = get_tickers_data(strategy, exchange, pairs, timerange, tickers = history.load_data(
datadir=Path(str(config.get("datadir"))), datadir=Path(str(config.get("datadir"))),
refresh_pairs=config.get('refresh_pairs', False), pairs=pairs,
live=config.get("live", False)) ticker_interval=config['ticker_interval'],
refresh_pairs=config.get('refresh_pairs', False),
timerange=timerange,
exchange=exchange,
live=config.get("live", False),
)
pair_counter = 0 pair_counter = 0
for pair, data in tickers.items(): for pair, data in tickers.items():
pair_counter += 1 pair_counter += 1

View File

@ -65,14 +65,14 @@ class FtRestClient():
def start(self): def start(self):
""" """
Start the bot if it's in stopped state. Start the bot if it's in stopped state.
:returns: json object :return: json object
""" """
return self._post("start") return self._post("start")
def stop(self): def stop(self):
""" """
Stop the bot. Use start to restart Stop the bot. Use start to restart
:returns: json object :return: json object
""" """
return self._post("stop") return self._post("stop")
@ -80,77 +80,77 @@ class FtRestClient():
""" """
Stop buying (but handle sells gracefully). Stop buying (but handle sells gracefully).
use reload_conf to reset use reload_conf to reset
:returns: json object :return: json object
""" """
return self._post("stopbuy") return self._post("stopbuy")
def reload_conf(self): def reload_conf(self):
""" """
Reload configuration Reload configuration
:returns: json object :return: json object
""" """
return self._post("reload_conf") return self._post("reload_conf")
def balance(self): def balance(self):
""" """
Get the account balance Get the account balance
:returns: json object :return: json object
""" """
return self._get("balance") return self._get("balance")
def count(self): def count(self):
""" """
Returns the amount of open trades Returns the amount of open trades
:returns: json object :return: json object
""" """
return self._get("count") return self._get("count")
def daily(self, days=None): def daily(self, days=None):
""" """
Returns the amount of open trades Returns the amount of open trades
:returns: json object :return: json object
""" """
return self._get("daily", params={"timescale": days} if days else None) return self._get("daily", params={"timescale": days} if days else None)
def edge(self): def edge(self):
""" """
Returns information about edge Returns information about edge
:returns: json object :return: json object
""" """
return self._get("edge") return self._get("edge")
def profit(self): def profit(self):
""" """
Returns the profit summary Returns the profit summary
:returns: json object :return: json object
""" """
return self._get("profit") return self._get("profit")
def performance(self): def performance(self):
""" """
Returns the performance of the different coins Returns the performance of the different coins
:returns: json object :return: json object
""" """
return self._get("performance") return self._get("performance")
def status(self): def status(self):
""" """
Get the status of open trades Get the status of open trades
:returns: json object :return: json object
""" """
return self._get("status") return self._get("status")
def version(self): def version(self):
""" """
Returns the version of the bot Returns the version of the bot
:returns: json object containing the version :return: json object containing the version
""" """
return self._get("version") return self._get("version")
def whitelist(self): def whitelist(self):
""" """
Show the current whitelist Show the current whitelist
:returns: json object :return: json object
""" """
return self._get("whitelist") return self._get("whitelist")
@ -158,7 +158,7 @@ class FtRestClient():
""" """
Show the current blacklist Show the current blacklist
:param add: List of coins to add (example: "BNB/BTC") :param add: List of coins to add (example: "BNB/BTC")
:returns: json object :return: json object
""" """
if not args: if not args:
return self._get("blacklist") return self._get("blacklist")
@ -170,7 +170,7 @@ class FtRestClient():
Buy an asset Buy an asset
:param pair: Pair to buy (ETH/BTC) :param pair: Pair to buy (ETH/BTC)
:param price: Optional - price to buy :param price: Optional - price to buy
:returns: json object of the trade :return: json object of the trade
""" """
data = {"pair": pair, data = {"pair": pair,
"price": price "price": price
@ -181,7 +181,7 @@ class FtRestClient():
""" """
Force-sell a trade Force-sell a trade
:param tradeid: Id of the trade (can be received via status command) :param tradeid: Id of the trade (can be received via status command)
:returns: json object :return: json object
""" """
return self._post("forcesell", data={"tradeid": tradeid}) return self._post("forcesell", data={"tradeid": tradeid})