Merge branch 'develop' into feat/stop_loss
This commit is contained in:
commit
ed2a1becef
@ -6,7 +6,10 @@
|
|||||||
"ticker_interval" : "5m",
|
"ticker_interval" : "5m",
|
||||||
"dry_run": false,
|
"dry_run": false,
|
||||||
"trailing_stop": false,
|
"trailing_stop": false,
|
||||||
"unfilledtimeout": 600,
|
"unfilledtimeout": {
|
||||||
|
"buy": 10,
|
||||||
|
"sell": 30
|
||||||
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0
|
"ask_last_balance": 0.0
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,10 @@
|
|||||||
"0": 0.04
|
"0": 0.04
|
||||||
},
|
},
|
||||||
"stoploss": -0.10,
|
"stoploss": -0.10,
|
||||||
"unfilledtimeout": 600,
|
"unfilledtimeout": {
|
||||||
|
"buy": 10,
|
||||||
|
"sell": 30
|
||||||
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0
|
"ask_last_balance": 0.0
|
||||||
},
|
},
|
||||||
|
@ -70,6 +70,34 @@ Where `-s TestStrategy` refers to the class name within the strategy file `test_
|
|||||||
python3 ./freqtrade/main.py backtesting --export trades
|
python3 ./freqtrade/main.py backtesting --export trades
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The exported trades can be read using the following code for manual analysis, or can be used by the plotting script `plot_dataframe.py` in the scripts folder.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
filename=Path('user_data/backtest_data/backtest-result.json')
|
||||||
|
|
||||||
|
with filename.open() as file:
|
||||||
|
data = json.load(file)
|
||||||
|
|
||||||
|
columns = ["pair", "profit", "opents", "closets", "index", "duration",
|
||||||
|
"open_rate", "close_rate", "open_at_end"]
|
||||||
|
df = pd.DataFrame(data, columns=columns)
|
||||||
|
|
||||||
|
df['opents'] = pd.to_datetime(df['opents'],
|
||||||
|
unit='s',
|
||||||
|
utc=True,
|
||||||
|
infer_datetime_format=True
|
||||||
|
)
|
||||||
|
df['closets'] = pd.to_datetime(df['closets'],
|
||||||
|
unit='s',
|
||||||
|
utc=True,
|
||||||
|
infer_datetime_format=True
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
#### Exporting trades to file specifying a custom filename
|
#### Exporting trades to file specifying a custom filename
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -27,7 +27,8 @@ The table below will list all configuration parameters.
|
|||||||
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
||||||
| `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
|
| `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
|
||||||
| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached.
|
| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached.
|
||||||
| `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled.
|
| `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
|
||||||
|
| `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
|
||||||
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
||||||
| `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
|
| `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
|
||||||
| `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode.
|
| `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode.
|
||||||
|
@ -63,7 +63,13 @@ CONF_SCHEMA = {
|
|||||||
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
|
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
|
||||||
'trailing_stop': {'type': 'boolean'},
|
'trailing_stop': {'type': 'boolean'},
|
||||||
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||||
'unfilledtimeout': {'type': 'integer', 'minimum': 0},
|
'unfilledtimeout': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'buy': {'type': 'number', 'minimum': 3},
|
||||||
|
'sell': {'type': 'number', 'minimum': 10}
|
||||||
|
}
|
||||||
|
},
|
||||||
'bid_strategy': {
|
'bid_strategy': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
@ -160,7 +160,7 @@ class FreqtradeBot(object):
|
|||||||
|
|
||||||
if 'unfilledtimeout' in self.config:
|
if 'unfilledtimeout' in self.config:
|
||||||
# Check and handle any timed out open orders
|
# Check and handle any timed out open orders
|
||||||
self.check_handle_timedout(self.config['unfilledtimeout'])
|
self.check_handle_timedout()
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
except TemporaryError as error:
|
except TemporaryError as error:
|
||||||
@ -277,11 +277,14 @@ class FreqtradeBot(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
min_stake_amounts = []
|
min_stake_amounts = []
|
||||||
if 'cost' in market['limits'] and 'min' in market['limits']['cost']:
|
limits = market['limits']
|
||||||
min_stake_amounts.append(market['limits']['cost']['min'])
|
if ('cost' in limits and 'min' in limits['cost']
|
||||||
|
and limits['cost']['min'] is not None):
|
||||||
|
min_stake_amounts.append(limits['cost']['min'])
|
||||||
|
|
||||||
if 'amount' in market['limits'] and 'min' in market['limits']['amount']:
|
if ('amount' in limits and 'min' in limits['amount']
|
||||||
min_stake_amounts.append(market['limits']['amount']['min'] * price)
|
and limits['amount']['min'] is not None):
|
||||||
|
min_stake_amounts.append(limits['amount']['min'] * price)
|
||||||
|
|
||||||
if not min_stake_amounts:
|
if not min_stake_amounts:
|
||||||
return None
|
return None
|
||||||
@ -492,13 +495,16 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
|||||||
logger.info('Found no sell signals for whitelisted currencies. Trying again..')
|
logger.info('Found no sell signals for whitelisted currencies. Trying again..')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_handle_timedout(self, timeoutvalue: int) -> None:
|
def check_handle_timedout(self) -> None:
|
||||||
"""
|
"""
|
||||||
Check if any orders are timed out and cancel if neccessary
|
Check if any orders are timed out and cancel if neccessary
|
||||||
:param timeoutvalue: Number of minutes until order is considered timed out
|
:param timeoutvalue: Number of minutes until order is considered timed out
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
timeoutthreashold = arrow.utcnow().shift(minutes=-timeoutvalue).datetime
|
buy_timeout = self.config['unfilledtimeout']['buy']
|
||||||
|
sell_timeout = self.config['unfilledtimeout']['sell']
|
||||||
|
buy_timeoutthreashold = arrow.utcnow().shift(minutes=-buy_timeout).datetime
|
||||||
|
sell_timeoutthreashold = arrow.utcnow().shift(minutes=-sell_timeout).datetime
|
||||||
|
|
||||||
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
|
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
|
||||||
try:
|
try:
|
||||||
@ -521,9 +527,11 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
|||||||
if int(order['remaining']) == 0:
|
if int(order['remaining']) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if order['side'] == 'buy' and ordertime < timeoutthreashold:
|
# Check if trade is still actually open
|
||||||
|
if order['status'] == 'open':
|
||||||
|
if order['side'] == 'buy' and ordertime < buy_timeoutthreashold:
|
||||||
self.handle_timedout_limit_buy(trade, order)
|
self.handle_timedout_limit_buy(trade, order)
|
||||||
elif order['side'] == 'sell' and ordertime < timeoutthreashold:
|
elif order['side'] == 'sell' and ordertime < sell_timeoutthreashold:
|
||||||
self.handle_timedout_limit_sell(trade, order)
|
self.handle_timedout_limit_sell(trade, order)
|
||||||
|
|
||||||
# FIX: 20180110, why is cancel.order unconditionally here, whereas
|
# FIX: 20180110, why is cancel.order unconditionally here, whereas
|
||||||
|
@ -38,6 +38,8 @@ class BacktestResult(NamedTuple):
|
|||||||
close_index: int
|
close_index: int
|
||||||
trade_duration: float
|
trade_duration: float
|
||||||
open_at_end: bool
|
open_at_end: bool
|
||||||
|
open_rate: float
|
||||||
|
close_rate: float
|
||||||
|
|
||||||
|
|
||||||
class Backtesting(object):
|
class Backtesting(object):
|
||||||
@ -116,11 +118,10 @@ class Backtesting(object):
|
|||||||
|
|
||||||
def _store_backtest_result(self, recordfilename: Optional[str], results: DataFrame) -> None:
|
def _store_backtest_result(self, recordfilename: Optional[str], results: DataFrame) -> None:
|
||||||
|
|
||||||
records = [(trade_entry.pair, trade_entry.profit_percent,
|
records = [(t.pair, t.profit_percent, t.open_time.timestamp(),
|
||||||
trade_entry.open_time.timestamp(),
|
t.close_time.timestamp(), t.open_index - 1, t.trade_duration,
|
||||||
trade_entry.close_time.timestamp(),
|
t.open_rate, t.close_rate, t.open_at_end)
|
||||||
trade_entry.open_index - 1, trade_entry.trade_duration)
|
for index, t in results.iterrows()]
|
||||||
for index, trade_entry in results.iterrows()]
|
|
||||||
|
|
||||||
if records:
|
if records:
|
||||||
logger.info('Dumping backtest results to %s', recordfilename)
|
logger.info('Dumping backtest results to %s', recordfilename)
|
||||||
@ -159,7 +160,9 @@ class Backtesting(object):
|
|||||||
trade_duration=(sell_row.date - buy_row.date).seconds // 60,
|
trade_duration=(sell_row.date - buy_row.date).seconds // 60,
|
||||||
open_index=buy_row.Index,
|
open_index=buy_row.Index,
|
||||||
close_index=sell_row.Index,
|
close_index=sell_row.Index,
|
||||||
open_at_end=False
|
open_at_end=False,
|
||||||
|
open_rate=buy_row.close,
|
||||||
|
close_rate=sell_row.close
|
||||||
)
|
)
|
||||||
if partial_ticker:
|
if partial_ticker:
|
||||||
# no sell condition found - trade stil open at end of backtest period
|
# no sell condition found - trade stil open at end of backtest period
|
||||||
@ -172,7 +175,9 @@ class Backtesting(object):
|
|||||||
trade_duration=(sell_row.date - buy_row.date).seconds // 60,
|
trade_duration=(sell_row.date - buy_row.date).seconds // 60,
|
||||||
open_index=buy_row.Index,
|
open_index=buy_row.Index,
|
||||||
close_index=sell_row.Index,
|
close_index=sell_row.Index,
|
||||||
open_at_end=True
|
open_at_end=True,
|
||||||
|
open_rate=buy_row.close,
|
||||||
|
close_rate=sell_row.close
|
||||||
)
|
)
|
||||||
logger.debug('Force_selling still open trade %s with %s perc - %s', btr.pair,
|
logger.debug('Force_selling still open trade %s with %s perc - %s', btr.pair,
|
||||||
btr.profit_percent, btr.profit_abs)
|
btr.profit_percent, btr.profit_abs)
|
||||||
|
@ -100,7 +100,10 @@ def default_conf():
|
|||||||
"0": 0.04
|
"0": 0.04
|
||||||
},
|
},
|
||||||
"stoploss": -0.10,
|
"stoploss": -0.10,
|
||||||
"unfilledtimeout": 600,
|
"unfilledtimeout": {
|
||||||
|
"buy": 10,
|
||||||
|
"sell": 30
|
||||||
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0
|
"ask_last_balance": 0.0
|
||||||
},
|
},
|
||||||
|
@ -14,13 +14,29 @@ from freqtrade.exchange import Exchange, API_RETRY_COUNT
|
|||||||
from freqtrade.tests.conftest import log_has, get_patched_exchange
|
from freqtrade.tests.conftest import log_has, get_patched_exchange
|
||||||
|
|
||||||
|
|
||||||
|
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
|
||||||
|
"""Function to test ccxt exception handling """
|
||||||
|
|
||||||
|
with pytest.raises(TemporaryError):
|
||||||
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
getattr(exchange, fun)(**kwargs)
|
||||||
|
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
getattr(exchange, fun)(**kwargs)
|
||||||
|
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_init(default_conf, mocker, caplog):
|
def test_init(default_conf, mocker, caplog):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
get_patched_exchange(mocker, default_conf)
|
get_patched_exchange(mocker, default_conf)
|
||||||
assert log_has('Instance is running with dry_run enabled', caplog.record_tuples)
|
assert log_has('Instance is running with dry_run enabled', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_init_exception(default_conf):
|
def test_init_exception(default_conf, mocker):
|
||||||
default_conf['exchange']['name'] = 'wrong_exchange_name'
|
default_conf['exchange']['name'] = 'wrong_exchange_name'
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
@ -28,6 +44,13 @@ def test_init_exception(default_conf):
|
|||||||
match='Exchange {} is not supported'.format(default_conf['exchange']['name'])):
|
match='Exchange {} is not supported'.format(default_conf['exchange']['name'])):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
default_conf['exchange']['name'] = 'binance'
|
||||||
|
with pytest.raises(
|
||||||
|
OperationalException,
|
||||||
|
match='Exchange {} is not supported'.format(default_conf['exchange']['name'])):
|
||||||
|
mocker.patch("ccxt.binance", MagicMock(side_effect=AttributeError))
|
||||||
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_pairs(default_conf, mocker):
|
def test_validate_pairs(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
@ -97,6 +120,20 @@ def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
|
|||||||
Exchange(conf)
|
Exchange(conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exchangehas(default_conf, mocker):
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
|
assert not exchange.exchange_has('ASDFASDF')
|
||||||
|
api_mock = MagicMock()
|
||||||
|
|
||||||
|
type(api_mock).has = PropertyMock(return_value={'deadbeef': True})
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
assert exchange.exchange_has("deadbeef")
|
||||||
|
|
||||||
|
type(api_mock).has = PropertyMock(return_value={'deadbeef': False})
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
assert not exchange.exchange_has("deadbeef")
|
||||||
|
|
||||||
|
|
||||||
def test_buy_dry_run(default_conf, mocker):
|
def test_buy_dry_run(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
@ -216,6 +253,11 @@ def test_get_balance_prod(default_conf, mocker):
|
|||||||
|
|
||||||
exchange.get_balance(currency='BTC')
|
exchange.get_balance(currency='BTC')
|
||||||
|
|
||||||
|
with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'):
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={}))
|
||||||
|
exchange.get_balance(currency='BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_get_balances_dry_run(default_conf, mocker):
|
def test_get_balances_dry_run(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
@ -243,17 +285,8 @@ def test_get_balances_prod(default_conf, mocker):
|
|||||||
assert exchange.get_balances()['1ST']['total'] == 10.0
|
assert exchange.get_balances()['1ST']['total'] == 10.0
|
||||||
assert exchange.get_balances()['1ST']['used'] == 0.0
|
assert exchange.get_balances()['1ST']['used'] == 0.0
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
|
"get_balances", "fetch_balance")
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_balances()
|
|
||||||
assert api_mock.fetch_balance.call_count == API_RETRY_COUNT + 1
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
|
||||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_balances()
|
|
||||||
assert api_mock.fetch_balance.call_count == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_tickers(default_conf, mocker):
|
def test_get_tickers(default_conf, mocker):
|
||||||
@ -282,15 +315,8 @@ def test_get_tickers(default_conf, mocker):
|
|||||||
assert tickers['BCH/BTC']['bid'] == 0.6
|
assert tickers['BCH/BTC']['bid'] == 0.6
|
||||||
assert tickers['BCH/BTC']['ask'] == 0.5
|
assert tickers['BCH/BTC']['ask'] == 0.5
|
||||||
|
|
||||||
with pytest.raises(TemporaryError): # test retrier
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NetworkError)
|
"get_tickers", "fetch_tickers")
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_tickers()
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
|
||||||
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.BaseError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_tickers()
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported)
|
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported)
|
||||||
@ -345,15 +371,9 @@ def test_get_ticker(default_conf, mocker):
|
|||||||
exchange.get_ticker(pair='ETH/BTC', refresh=False)
|
exchange.get_ticker(pair='ETH/BTC', refresh=False)
|
||||||
assert api_mock.fetch_ticker.call_count == 0
|
assert api_mock.fetch_ticker.call_count == 0
|
||||||
|
|
||||||
with pytest.raises(TemporaryError): # test retrier
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
|
"get_ticker", "fetch_ticker",
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
pair='ETH/BTC', refresh=True)
|
||||||
exchange.get_ticker(pair='ETH/BTC', refresh=True)
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
|
||||||
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.BaseError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_ticker(pair='ETH/BTC', refresh=True)
|
|
||||||
|
|
||||||
api_mock.fetch_ticker = MagicMock(return_value={})
|
api_mock.fetch_ticker = MagicMock(return_value={})
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
@ -416,17 +436,14 @@ def test_get_ticker_history(default_conf, mocker):
|
|||||||
assert ticks[0][4] == 9
|
assert ticks[0][4] == 9
|
||||||
assert ticks[0][5] == 10
|
assert ticks[0][5] == 10
|
||||||
|
|
||||||
with pytest.raises(TemporaryError): # test retrier
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError)
|
"get_ticker_history", "fetch_ohlcv",
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
|
||||||
# new symbol to get around cache
|
|
||||||
exchange.get_ticker_history('ABCD/BTC', default_conf['ticker_interval'])
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException, match=r'Exchange .* does not support.*'):
|
||||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError)
|
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
# new symbol to get around cache
|
exchange.get_ticker_history(pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
|
||||||
exchange.get_ticker_history('EFGH/BTC', default_conf['ticker_interval'])
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_ticker_history_sort(default_conf, mocker):
|
def test_get_ticker_history_sort(default_conf, mocker):
|
||||||
@ -515,24 +532,15 @@ def test_cancel_order(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
|
assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
|
||||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
|
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.cancel_order(order_id='_', pair='TKN/BTC')
|
|
||||||
assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
|
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
exchange.cancel_order(order_id='_', pair='TKN/BTC')
|
exchange.cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
|
assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError)
|
"cancel_order", "cancel_order",
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
order_id='_', pair='TKN/BTC')
|
||||||
exchange.cancel_order(order_id='_', pair='TKN/BTC')
|
|
||||||
assert api_mock.cancel_order.call_count == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_order(default_conf, mocker):
|
def test_get_order(default_conf, mocker):
|
||||||
@ -550,23 +558,15 @@ def test_get_order(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
assert exchange.get_order('X', 'TKN/BTC') == 456
|
assert exchange.get_order('X', 'TKN/BTC') == 456
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
|
||||||
assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
|
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
|
assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError)
|
'get_order', 'fetch_order',
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
order_id='_', pair='TKN/BTC')
|
||||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
|
||||||
assert api_mock.fetch_order.call_count == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_name(default_conf, mocker):
|
def test_name(default_conf, mocker):
|
||||||
@ -651,19 +651,12 @@ def test_get_trades_for_order(default_conf, mocker):
|
|||||||
assert len(orders) == 1
|
assert len(orders) == 1
|
||||||
assert orders[0]['price'] == 165
|
assert orders[0]['price'] == 165
|
||||||
|
|
||||||
# test Exceptions
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
with pytest.raises(OperationalException):
|
'get_trades_for_order', 'fetch_my_trades',
|
||||||
api_mock = MagicMock()
|
order_id=order_id, pair='LTC/BTC', since=since)
|
||||||
api_mock.fetch_my_trades = MagicMock(side_effect=ccxt.BaseError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
|
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
|
||||||
api_mock = MagicMock()
|
assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == []
|
||||||
api_mock.fetch_my_trades = MagicMock(side_effect=ccxt.NetworkError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
|
|
||||||
assert api_mock.fetch_my_trades.call_count == API_RETRY_COUNT + 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_markets(default_conf, mocker, markets):
|
def test_get_markets(default_conf, mocker, markets):
|
||||||
@ -677,19 +670,8 @@ def test_get_markets(default_conf, mocker, markets):
|
|||||||
assert ret[0]["id"] == "ethbtc"
|
assert ret[0]["id"] == "ethbtc"
|
||||||
assert ret[0]["symbol"] == "ETH/BTC"
|
assert ret[0]["symbol"] == "ETH/BTC"
|
||||||
|
|
||||||
# test Exceptions
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
with pytest.raises(OperationalException):
|
'get_markets', 'fetch_markets')
|
||||||
api_mock = MagicMock()
|
|
||||||
api_mock.fetch_markets = MagicMock(side_effect=ccxt.BaseError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_markets()
|
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
|
||||||
api_mock = MagicMock()
|
|
||||||
api_mock.fetch_markets = MagicMock(side_effect=ccxt.NetworkError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_markets()
|
|
||||||
assert api_mock.fetch_markets.call_count == API_RETRY_COUNT + 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_fee(default_conf, mocker):
|
def test_get_fee(default_conf, mocker):
|
||||||
@ -704,19 +686,8 @@ def test_get_fee(default_conf, mocker):
|
|||||||
|
|
||||||
assert exchange.get_fee() == 0.025
|
assert exchange.get_fee() == 0.025
|
||||||
|
|
||||||
# test Exceptions
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||||
with pytest.raises(OperationalException):
|
'get_fee', 'calculate_fee')
|
||||||
api_mock = MagicMock()
|
|
||||||
api_mock.calculate_fee = MagicMock(side_effect=ccxt.BaseError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_fee()
|
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
|
||||||
api_mock = MagicMock()
|
|
||||||
api_mock.calculate_fee = MagicMock(side_effect=ccxt.NetworkError)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
exchange.get_fee()
|
|
||||||
assert api_mock.calculate_fee.call_count == API_RETRY_COUNT + 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_amount_lots(default_conf, mocker):
|
def test_get_amount_lots(default_conf, mocker):
|
||||||
|
@ -627,9 +627,13 @@ def test_backtest_record(default_conf, fee, mocker):
|
|||||||
Arrow(2017, 11, 14, 22, 10, 00).datetime,
|
Arrow(2017, 11, 14, 22, 10, 00).datetime,
|
||||||
Arrow(2017, 11, 14, 22, 43, 00).datetime,
|
Arrow(2017, 11, 14, 22, 43, 00).datetime,
|
||||||
Arrow(2017, 11, 14, 22, 58, 00).datetime],
|
Arrow(2017, 11, 14, 22, 58, 00).datetime],
|
||||||
|
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
|
||||||
|
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
|
||||||
"open_index": [1, 119, 153, 185],
|
"open_index": [1, 119, 153, 185],
|
||||||
"close_index": [118, 151, 184, 199],
|
"close_index": [118, 151, 184, 199],
|
||||||
"trade_duration": [123, 34, 31, 14]})
|
"trade_duration": [123, 34, 31, 14],
|
||||||
|
"open_at_end": [False, False, False, True]
|
||||||
|
})
|
||||||
backtesting._store_backtest_result("backtest-result.json", results)
|
backtesting._store_backtest_result("backtest-result.json", results)
|
||||||
assert len(results) == 4
|
assert len(results) == 4
|
||||||
# Assert file_dump_json was only called once
|
# Assert file_dump_json was only called once
|
||||||
@ -640,12 +644,16 @@ def test_backtest_record(default_conf, fee, mocker):
|
|||||||
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
|
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
|
||||||
# Below follows just a typecheck of the schema/type of trade-records
|
# Below follows just a typecheck of the schema/type of trade-records
|
||||||
oix = None
|
oix = None
|
||||||
for (pair, profit, date_buy, date_sell, buy_index, dur) in records:
|
for (pair, profit, date_buy, date_sell, buy_index, dur,
|
||||||
|
openr, closer, open_at_end) in records:
|
||||||
assert pair == 'UNITTEST/BTC'
|
assert pair == 'UNITTEST/BTC'
|
||||||
isinstance(profit, float)
|
assert isinstance(profit, float)
|
||||||
# FIX: buy/sell should be converted to ints
|
# FIX: buy/sell should be converted to ints
|
||||||
isinstance(date_buy, str)
|
assert isinstance(date_buy, float)
|
||||||
isinstance(date_sell, str)
|
assert isinstance(date_sell, float)
|
||||||
|
assert isinstance(openr, float)
|
||||||
|
assert isinstance(closer, float)
|
||||||
|
assert isinstance(open_at_end, bool)
|
||||||
isinstance(buy_index, pd._libs.tslib.Timestamp)
|
isinstance(buy_index, pd._libs.tslib.Timestamp)
|
||||||
if oix:
|
if oix:
|
||||||
assert buy_index > oix
|
assert buy_index > oix
|
||||||
|
@ -348,6 +348,34 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
|||||||
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
||||||
assert result is None
|
assert result is None
|
||||||
|
|
||||||
|
# no cost Min
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {
|
||||||
|
'cost': {"min": None},
|
||||||
|
'amount': {}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
# no amount Min
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {
|
||||||
|
'cost': {},
|
||||||
|
'amount': {"min": None}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
# empty 'cost'/'amount' section
|
# empty 'cost'/'amount' section
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.exchange.Exchange.get_markets',
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
@ -1124,7 +1152,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe
|
|||||||
Trade.session.add(trade_buy)
|
Trade.session.add(trade_buy)
|
||||||
|
|
||||||
# check it does cancel buy orders over the time limit
|
# check it does cancel buy orders over the time limit
|
||||||
freqtrade.check_handle_timedout(600)
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||||
@ -1165,7 +1193,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
|||||||
Trade.session.add(trade_sell)
|
Trade.session.add(trade_sell)
|
||||||
|
|
||||||
# check it does cancel sell orders over the time limit
|
# check it does cancel sell orders over the time limit
|
||||||
freqtrade.check_handle_timedout(600)
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
assert trade_sell.is_open is True
|
assert trade_sell.is_open is True
|
||||||
@ -1205,7 +1233,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
|||||||
|
|
||||||
# check it does cancel buy orders over the time limit
|
# check it does cancel buy orders over the time limit
|
||||||
# note this is for a partially-complete buy order
|
# note this is for a partially-complete buy order
|
||||||
freqtrade.check_handle_timedout(600)
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||||
@ -1256,7 +1284,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
|
|||||||
'recent call last):\n.*'
|
'recent call last):\n.*'
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.check_handle_timedout(600)
|
freqtrade.check_handle_timedout()
|
||||||
assert filter(regexp.match, caplog.record_tuples)
|
assert filter(regexp.match, caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
@ -1599,6 +1627,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke
|
|||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
@ -1616,7 +1645,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke
|
|||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, mocker) -> None:
|
def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test sell_profit_only feature when enabled and we have a loss
|
Test sell_profit_only feature when enabled and we have a loss
|
||||||
"""
|
"""
|
||||||
@ -1634,6 +1663,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, mocker) ->
|
|||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
ccxt==1.14.272
|
ccxt==1.14.301
|
||||||
SQLAlchemy==1.2.8
|
SQLAlchemy==1.2.9
|
||||||
python-telegram-bot==10.1.0
|
python-telegram-bot==10.1.0
|
||||||
arrow==0.12.1
|
arrow==0.12.1
|
||||||
cachetools==2.1.0
|
cachetools==2.1.0
|
||||||
|
@ -25,11 +25,13 @@ Example of usage:
|
|||||||
--indicators2 fastk,fastd
|
--indicators2 fastk,fastd
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
import plotly.graph_objs as go
|
import plotly.graph_objs as go
|
||||||
from plotly import tools
|
from plotly import tools
|
||||||
from plotly.offline import plot
|
from plotly.offline import plot
|
||||||
@ -37,7 +39,7 @@ from plotly.offline import plot
|
|||||||
import freqtrade.optimize as optimize
|
import freqtrade.optimize as optimize
|
||||||
from freqtrade import persistence
|
from freqtrade import persistence
|
||||||
from freqtrade.analyze import Analyze
|
from freqtrade.analyze import Analyze
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments, TimeRange
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.optimize.backtesting import setup_configuration
|
from freqtrade.optimize.backtesting import setup_configuration
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
@ -46,6 +48,45 @@ logger = logging.getLogger(__name__)
|
|||||||
_CONF: Dict[str, Any] = {}
|
_CONF: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFrame:
|
||||||
|
trades: pd.DataFrame = pd.DataFrame()
|
||||||
|
if args.db_url:
|
||||||
|
persistence.init(_CONF)
|
||||||
|
columns = ["pair", "profit", "opents", "closets", "open_rate", "close_rate", "duration"]
|
||||||
|
|
||||||
|
trades = pd.DataFrame([(t.pair, t.calc_profit(),
|
||||||
|
t.open_date, t.close_date,
|
||||||
|
t.open_rate, t.close_rate,
|
||||||
|
t.close_date.timestamp() - t.open_date.timestamp())
|
||||||
|
for t in Trade.query.filter(Trade.pair.is_(pair)).all()],
|
||||||
|
columns=columns)
|
||||||
|
|
||||||
|
if args.exportfilename:
|
||||||
|
file = Path(args.exportfilename)
|
||||||
|
# must align with columns in backtest.py
|
||||||
|
columns = ["pair", "profit", "opents", "closets", "index", "duration",
|
||||||
|
"open_rate", "close_rate", "open_at_end"]
|
||||||
|
with file.open() as f:
|
||||||
|
data = json.load(f)
|
||||||
|
trades = pd.DataFrame(data, columns=columns)
|
||||||
|
trades = trades.loc[trades["pair"] == pair]
|
||||||
|
if timerange:
|
||||||
|
if timerange.starttype == 'date':
|
||||||
|
trades = trades.loc[trades["opents"] >= timerange.startts]
|
||||||
|
if timerange.stoptype == 'date':
|
||||||
|
trades = trades.loc[trades["opents"] <= timerange.stopts]
|
||||||
|
|
||||||
|
trades['opents'] = pd.to_datetime(trades['opents'],
|
||||||
|
unit='s',
|
||||||
|
utc=True,
|
||||||
|
infer_datetime_format=True)
|
||||||
|
trades['closets'] = pd.to_datetime(trades['closets'],
|
||||||
|
unit='s',
|
||||||
|
utc=True,
|
||||||
|
infer_datetime_format=True)
|
||||||
|
return trades
|
||||||
|
|
||||||
|
|
||||||
def plot_analyzed_dataframe(args: Namespace) -> None:
|
def plot_analyzed_dataframe(args: Namespace) -> None:
|
||||||
"""
|
"""
|
||||||
Calls analyze() and plots the returned dataframe
|
Calls analyze() and plots the returned dataframe
|
||||||
@ -102,31 +143,32 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
|
|||||||
if tickers == {}:
|
if tickers == {}:
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
if args.db_url and args.exportfilename:
|
||||||
|
logger.critical("Can only specify --db-url or --export-filename")
|
||||||
# Get trades already made from the DB
|
# Get trades already made from the DB
|
||||||
trades: List[Trade] = []
|
trades = load_trades(args, pair, timerange)
|
||||||
if args.db_url:
|
|
||||||
persistence.init(_CONF)
|
|
||||||
trades = Trade.query.filter(Trade.pair.is_(pair)).all()
|
|
||||||
|
|
||||||
dataframes = analyze.tickerdata_to_dataframe(tickers)
|
dataframes = analyze.tickerdata_to_dataframe(tickers)
|
||||||
dataframe = dataframes[pair]
|
dataframe = dataframes[pair]
|
||||||
dataframe = analyze.populate_buy_trend(dataframe)
|
dataframe = analyze.populate_buy_trend(dataframe)
|
||||||
dataframe = analyze.populate_sell_trend(dataframe)
|
dataframe = analyze.populate_sell_trend(dataframe)
|
||||||
|
|
||||||
if len(dataframe.index) > 750:
|
if len(dataframe.index) > args.plot_limit:
|
||||||
logger.warning('Ticker contained more than 750 candles, clipping.')
|
logger.warning('Ticker contained more than %s candles as defined '
|
||||||
|
'with --plot-limit, clipping.', args.plot_limit)
|
||||||
|
dataframe = dataframe.tail(args.plot_limit)
|
||||||
|
trades = trades.loc[trades['opents'] >= dataframe.iloc[0]['date']]
|
||||||
fig = generate_graph(
|
fig = generate_graph(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
trades=trades,
|
trades=trades,
|
||||||
data=dataframe.tail(750),
|
data=dataframe,
|
||||||
args=args
|
args=args
|
||||||
)
|
)
|
||||||
|
|
||||||
plot(fig, filename=os.path.join('user_data', 'freqtrade-plot.html'))
|
plot(fig, filename=str(Path('user_data').joinpath('freqtrade-plot.html')))
|
||||||
|
|
||||||
|
|
||||||
def generate_graph(pair, trades, data, args) -> tools.make_subplots:
|
def generate_graph(pair, trades: pd.DataFrame, data: pd.DataFrame, args) -> tools.make_subplots:
|
||||||
"""
|
"""
|
||||||
Generate the graph from the data generated by Backtesting or from DB
|
Generate the graph from the data generated by Backtesting or from DB
|
||||||
:param pair: Pair to Display on the graph
|
:param pair: Pair to Display on the graph
|
||||||
@ -187,8 +229,8 @@ def generate_graph(pair, trades, data, args) -> tools.make_subplots:
|
|||||||
)
|
)
|
||||||
|
|
||||||
trade_buys = go.Scattergl(
|
trade_buys = go.Scattergl(
|
||||||
x=[t.open_date.isoformat() for t in trades],
|
x=trades["opents"],
|
||||||
y=[t.open_rate for t in trades],
|
y=trades["open_rate"],
|
||||||
mode='markers',
|
mode='markers',
|
||||||
name='trade_buy',
|
name='trade_buy',
|
||||||
marker=dict(
|
marker=dict(
|
||||||
@ -199,8 +241,8 @@ def generate_graph(pair, trades, data, args) -> tools.make_subplots:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
trade_sells = go.Scattergl(
|
trade_sells = go.Scattergl(
|
||||||
x=[t.close_date.isoformat() for t in trades],
|
x=trades["closets"],
|
||||||
y=[t.close_rate for t in trades],
|
y=trades["close_rate"],
|
||||||
mode='markers',
|
mode='markers',
|
||||||
name='trade_sell',
|
name='trade_sell',
|
||||||
marker=dict(
|
marker=dict(
|
||||||
@ -299,11 +341,17 @@ def plot_parse_args(args: List[str]) -> Namespace:
|
|||||||
default='macd',
|
default='macd',
|
||||||
dest='indicators2',
|
dest='indicators2',
|
||||||
)
|
)
|
||||||
|
arguments.parser.add_argument(
|
||||||
|
'--plot-limit',
|
||||||
|
help='Specify tick limit for plotting - too high values cause huge files - '
|
||||||
|
'Default: %(default)s',
|
||||||
|
dest='plot_limit',
|
||||||
|
default=750,
|
||||||
|
type=int,
|
||||||
|
)
|
||||||
arguments.common_args_parser()
|
arguments.common_args_parser()
|
||||||
arguments.optimizer_shared_options(arguments.parser)
|
arguments.optimizer_shared_options(arguments.parser)
|
||||||
arguments.backtesting_options(arguments.parser)
|
arguments.backtesting_options(arguments.parser)
|
||||||
|
|
||||||
return arguments.parse_args()
|
return arguments.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user