Merge 685ee42fbc
into 5da034c313
This commit is contained in:
commit
74a3a822ca
74
Vagrantfile
vendored
Normal file
74
Vagrantfile
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# -*- mode: ruby -*-
|
||||||
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
|
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
||||||
|
# configures the configuration version (we support older styles for
|
||||||
|
# backwards compatibility). Please don't change it unless you know what
|
||||||
|
# you're doing.
|
||||||
|
Vagrant.configure("2") do |config|
|
||||||
|
# The most common configuration options are documented and commented below.
|
||||||
|
# For a complete reference, please see the online documentation at
|
||||||
|
# https://docs.vagrantup.com.
|
||||||
|
|
||||||
|
# Every Vagrant development environment requires a box. You can search for
|
||||||
|
# boxes at https://vagrantcloud.com/search.
|
||||||
|
config.vm.box = "bento/ubuntu-16.04"
|
||||||
|
|
||||||
|
# Disable automatic box update checking. If you disable this, then
|
||||||
|
# boxes will only be checked for updates when the user runs
|
||||||
|
# `vagrant box outdated`. This is not recommended.
|
||||||
|
# config.vm.box_check_update = false
|
||||||
|
|
||||||
|
# Create a forwarded port mapping which allows access to a specific port
|
||||||
|
# within the machine from a port on the host machine. In the example below,
|
||||||
|
# accessing "localhost:8080" will access port 80 on the guest machine.
|
||||||
|
# NOTE: This will enable public access to the opened port
|
||||||
|
# config.vm.network "forwarded_port", guest: 80, host: 8080
|
||||||
|
|
||||||
|
# Create a forwarded port mapping which allows access to a specific port
|
||||||
|
# within the machine from a port on the host machine and only allow access
|
||||||
|
# via 127.0.0.1 to disable public access
|
||||||
|
# config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
|
||||||
|
|
||||||
|
# Create a private network, which allows host-only access to the machine
|
||||||
|
# using a specific IP.
|
||||||
|
# config.vm.network "private_network", ip: "192.168.33.10"
|
||||||
|
|
||||||
|
# Create a public network, which generally matched to bridged network.
|
||||||
|
# Bridged networks make the machine appear as another physical device on
|
||||||
|
# your network.
|
||||||
|
# config.vm.network "public_network"
|
||||||
|
|
||||||
|
# Share an additional folder to the guest VM. The first argument is
|
||||||
|
# the path on the host to the actual folder. The second argument is
|
||||||
|
# the path on the guest to mount the folder. And the optional third
|
||||||
|
# argument is a set of non-required options.
|
||||||
|
config.vm.synced_folder ".", "/vagrant/freqtrade"
|
||||||
|
config.vm.synced_folder ".env", "/vagrant/freqtrade/.env", disabled: true
|
||||||
|
config.vm.synced_folder ".hyperopt", "/vagrant/freqtrade/.hyperopt", disabled: true
|
||||||
|
|
||||||
|
|
||||||
|
# Provider-specific configuration so you can fine-tune various
|
||||||
|
# backing providers for Vagrant. These expose provider-specific options.
|
||||||
|
# Example for VirtualBox:
|
||||||
|
#
|
||||||
|
config.vm.provider "virtualbox" do |vb|
|
||||||
|
# # Display the VirtualBox GUI when booting the machine
|
||||||
|
# vb.gui = true
|
||||||
|
#
|
||||||
|
# Customize the amount of memory on the VM:
|
||||||
|
vb.memory = "4096"
|
||||||
|
vb.cpus = "10"
|
||||||
|
end
|
||||||
|
#
|
||||||
|
# View the documentation for the provider you are using for more
|
||||||
|
# information on available options.
|
||||||
|
|
||||||
|
# Enable provisioning with a shell script. Additional provisioners such as
|
||||||
|
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
||||||
|
# documentation for more information about their specific syntax and use.
|
||||||
|
# config.vm.provision "shell", inline: <<-SHELL
|
||||||
|
# apt-get update
|
||||||
|
# apt-get install -y apache2
|
||||||
|
# SHELL
|
||||||
|
end
|
0
bin/freqtrade
Executable file → Normal file
0
bin/freqtrade
Executable file → Normal file
@ -9,23 +9,22 @@
|
|||||||
"ask_last_balance": 0.0
|
"ask_last_balance": 0.0
|
||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "bittrex",
|
"name": "binance",
|
||||||
"key": "your_echange_key",
|
"key": "your_echange_key",
|
||||||
"secret": "your_echange_secret",
|
"secret": "your_echange_secret",
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"BTC_ETH",
|
"ETH/BTC",
|
||||||
"BTC_LTC",
|
"LTC/BTC",
|
||||||
"BTC_ETC",
|
"ETC/BTC",
|
||||||
"BTC_DASH",
|
"DASH/BTC",
|
||||||
"BTC_ZEC",
|
"ZEC/BTC",
|
||||||
"BTC_XLM",
|
"XLM/BTC",
|
||||||
"BTC_NXT",
|
"POWR/BTC",
|
||||||
"BTC_POWR",
|
"ADA/BTC",
|
||||||
"BTC_ADA",
|
"XMR/BTC"
|
||||||
"BTC_XMR"
|
|
||||||
],
|
],
|
||||||
"pair_blacklist": [
|
"pair_blacklist": [
|
||||||
"BTC_DOGE"
|
"DOGE/BTC"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
|
@ -117,16 +117,16 @@ A backtesting result will look like that:
|
|||||||
====================== BACKTESTING REPORT ================================
|
====================== BACKTESTING REPORT ================================
|
||||||
pair buy count avg profit % total profit BTC avg duration
|
pair buy count avg profit % total profit BTC avg duration
|
||||||
-------- ----------- -------------- ------------------ --------------
|
-------- ----------- -------------- ------------------ --------------
|
||||||
BTC_ETH 56 -0.67 -0.00075455 62.3
|
ETH/BTC 56 -0.67 -0.00075455 62.3
|
||||||
BTC_LTC 38 -0.48 -0.00036315 57.9
|
LTC/BTC 38 -0.48 -0.00036315 57.9
|
||||||
BTC_ETC 42 -1.15 -0.00096469 67.0
|
ETC/BTC 42 -1.15 -0.00096469 67.0
|
||||||
BTC_DASH 72 -0.62 -0.00089368 39.9
|
DASH/BTC 72 -0.62 -0.00089368 39.9
|
||||||
BTC_ZEC 45 -0.46 -0.00041387 63.2
|
ZEC/BTC 45 -0.46 -0.00041387 63.2
|
||||||
BTC_XLM 24 -0.88 -0.00041846 47.7
|
XLM/BTC 24 -0.88 -0.00041846 47.7
|
||||||
BTC_NXT 24 0.68 0.00031833 40.2
|
NXT/BTC 24 0.68 0.00031833 40.2
|
||||||
BTC_POWR 35 0.98 0.00064887 45.3
|
POWR/BTC 35 0.98 0.00064887 45.3
|
||||||
BTC_ADA 43 -0.39 -0.00032292 55.0
|
ADA/BTC 43 -0.39 -0.00032292 55.0
|
||||||
BTC_XMR 40 -0.40 -0.00032181 47.4
|
XMR/BTC 40 -0.40 -0.00032181 47.4
|
||||||
TOTAL 419 -0.41 -0.00348593 52.9
|
TOTAL 419 -0.41 -0.00348593 52.9
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ The table below will list all configuration parameters.
|
|||||||
| `max_open_trades` | 3 | Yes | Number of trades open your bot will have.
|
| `max_open_trades` | 3 | Yes | Number of trades open your bot will have.
|
||||||
| `stake_currency` | BTC | Yes | Crypto-currency used for trading.
|
| `stake_currency` | BTC | Yes | Crypto-currency used for trading.
|
||||||
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged.
|
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged.
|
||||||
| `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes
|
| `ticker_interval` | 5m | No | The ticker interval to use (1m, 5m, 30m, 1h or 1d). Default is 5 minutes (5m).
|
||||||
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
||||||
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
||||||
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
|
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
|
||||||
|
@ -14,3 +14,11 @@ class OperationalException(BaseException):
|
|||||||
Requires manual intervention.
|
Requires manual intervention.
|
||||||
This happens when an exchange returns an unexpected error during runtime.
|
This happens when an exchange returns an unexpected error during runtime.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkException(BaseException):
|
||||||
|
"""
|
||||||
|
Network related error.
|
||||||
|
This could happen when an exchange is congested, unavailable, or the user
|
||||||
|
has networking problems. Usually resolves itself after a time.
|
||||||
|
"""
|
||||||
|
@ -9,6 +9,7 @@ from typing import Dict, List
|
|||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
|
from freqtrade.misc import ticker_interval_to_minutes
|
||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
from freqtrade.strategy.strategy import Strategy
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
|
|||||||
.rename(columns=columns)
|
.rename(columns=columns)
|
||||||
if 'BV' in frame:
|
if 'BV' in frame:
|
||||||
frame.drop('BV', 1, inplace=True)
|
frame.drop('BV', 1, inplace=True)
|
||||||
frame['date'] = to_datetime(frame['date'], utc=True, infer_datetime_format=True)
|
frame['date'] = to_datetime(frame['date'], format='%Y-%m-%dT%H:%M:%S.%f')
|
||||||
frame.sort_values('date', inplace=True)
|
frame.sort_values('date', inplace=True)
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
@ -84,10 +85,11 @@ def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
|
|||||||
|
|
||||||
# FIX: Maybe return False, if an error has occured,
|
# FIX: Maybe return False, if an error has occured,
|
||||||
# Otherwise we might mask an error as an non-signal-scenario
|
# Otherwise we might mask an error as an non-signal-scenario
|
||||||
def get_signal(pair: str, interval: int) -> (bool, bool):
|
def get_signal(pair: str, interval: str) -> (bool, bool):
|
||||||
"""
|
"""
|
||||||
Calculates current signal based several technical analysis indicators
|
Calculates current signal based several technical analysis indicators
|
||||||
:param pair: pair in format BTC_ANT or BTC-ANT
|
:param pair: pair in format ANT/BTC
|
||||||
|
:param interval: interval in string format (1m, 5m, 1h,...)
|
||||||
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
|
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
|
||||||
"""
|
"""
|
||||||
ticker_hist = get_ticker_history(pair, interval)
|
ticker_hist = get_ticker_history(pair, interval)
|
||||||
@ -112,7 +114,7 @@ def get_signal(pair: str, interval: int) -> (bool, bool):
|
|||||||
|
|
||||||
# Check if dataframe is out of date
|
# Check if dataframe is out of date
|
||||||
signal_date = arrow.get(latest['date'])
|
signal_date = arrow.get(latest['date'])
|
||||||
if signal_date < arrow.now() - timedelta(minutes=(interval + 5)):
|
if signal_date < arrow.now() - timedelta(minutes=(ticker_interval_to_minutes(interval) + 5)):
|
||||||
logger.warning('Too old dataframe for pair %s', pair)
|
logger.warning('Too old dataframe for pair %s', pair)
|
||||||
return (False, False) # return False ?
|
return (False, False) # return False ?
|
||||||
|
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
# pragma pylint: disable=W0603
|
# pragma pylint: disable=W0603
|
||||||
""" Cryptocurrency Exchanges support """
|
""" Cryptocurrency Exchanges support """
|
||||||
import enum
|
|
||||||
import logging
|
import logging
|
||||||
import ccxt
|
import ccxt
|
||||||
from random import randint
|
from random import randint
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import requests
|
|
||||||
from cachetools import cached, TTLCache
|
from cachetools import cached, TTLCache
|
||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException, DependencyException, NetworkException
|
||||||
from freqtrade.exchange.interface import Exchange
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Current selected exchange
|
# Current selected exchange
|
||||||
_API: Exchange = None
|
_API: ccxt.Exchange = None
|
||||||
_CONF: dict = {}
|
_CONF: dict = {}
|
||||||
|
|
||||||
|
# Cache for ticker data
|
||||||
|
_TICKER_CACHE: dict = {}
|
||||||
|
|
||||||
# Holds all open sell orders for dry_run
|
# Holds all open sell orders for dry_run
|
||||||
_DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {}
|
_DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {}
|
||||||
|
|
||||||
@ -43,10 +44,10 @@ def init(config: dict) -> None:
|
|||||||
# Find matching class for the given exchange name
|
# Find matching class for the given exchange name
|
||||||
name = exchange_config['name']
|
name = exchange_config['name']
|
||||||
|
|
||||||
# TODO add check for a list of supported exchanges
|
if name not in ccxt.exchanges:
|
||||||
|
raise OperationalException('Exchange {} is not supported'.format(name))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# exchange_class = Exchanges[name.upper()].value
|
|
||||||
_API = getattr(ccxt, name.lower())({
|
_API = getattr(ccxt, name.lower())({
|
||||||
'apiKey': exchange_config.get('key'),
|
'apiKey': exchange_config.get('key'),
|
||||||
'secret': exchange_config.get('secret'),
|
'secret': exchange_config.get('secret'),
|
||||||
@ -54,9 +55,6 @@ def init(config: dict) -> None:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise OperationalException('Exchange {} is not supported'.format(name))
|
raise OperationalException('Exchange {} is not supported'.format(name))
|
||||||
|
|
||||||
# we need load api markets
|
|
||||||
_API.load_markets()
|
|
||||||
|
|
||||||
# Check if all pairs are available
|
# Check if all pairs are available
|
||||||
validate_pairs(config['exchange']['pair_whitelist'])
|
validate_pairs(config['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
@ -69,19 +67,16 @@ def validate_pairs(pairs: List[str]) -> None:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not _API.markets:
|
|
||||||
_API.load_markets()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
markets = _API.markets
|
markets = _API.load_markets()
|
||||||
except requests.exceptions.RequestException as e:
|
except ccxt.BaseError as e:
|
||||||
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
|
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
|
||||||
return
|
return
|
||||||
|
|
||||||
stake_cur = _CONF['stake_currency']
|
stake_cur = _CONF['stake_currency']
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
|
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
|
||||||
pair = pair.replace('_', '/')
|
# pair = pair.replace('_', '/')
|
||||||
|
|
||||||
# TODO: add a support for having coins in BTC/USDT format
|
# TODO: add a support for having coins in BTC/USDT format
|
||||||
if not pair.endswith(stake_cur):
|
if not pair.endswith(stake_cur):
|
||||||
@ -90,10 +85,10 @@ def validate_pairs(pairs: List[str]) -> None:
|
|||||||
)
|
)
|
||||||
if pair not in markets:
|
if pair not in markets:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Pair {} is not available at {}'.format(pair, _API.name.lower()))
|
'Pair {} is not available at {}'.format(pair, _API.id.lower()))
|
||||||
|
|
||||||
|
|
||||||
def buy(pair: str, rate: float, amount: float) -> str:
|
def buy(pair: str, rate: float, amount: float) -> Dict:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
global _DRY_RUN_OPEN_ORDERS
|
global _DRY_RUN_OPEN_ORDERS
|
||||||
order_id = 'dry_run_buy_{}'.format(randint(0, 10**6))
|
order_id = 'dry_run_buy_{}'.format(randint(0, 10**6))
|
||||||
@ -106,12 +101,31 @@ def buy(pair: str, rate: float, amount: float) -> str:
|
|||||||
'opened': arrow.utcnow().datetime,
|
'opened': arrow.utcnow().datetime,
|
||||||
'closed': arrow.utcnow().datetime,
|
'closed': arrow.utcnow().datetime,
|
||||||
}
|
}
|
||||||
return order_id
|
return {'id': order_id}
|
||||||
|
|
||||||
return _API.buy(pair, rate, amount)
|
try:
|
||||||
|
return _API.create_limit_buy_order(pair, amount, rate)
|
||||||
|
except ccxt.InsufficientFunds as e:
|
||||||
|
raise DependencyException(
|
||||||
|
'Insufficient funds to create limit buy order on market {}.'
|
||||||
|
'Tried to buy amount {} at rate {} (total {}).'
|
||||||
|
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
||||||
|
)
|
||||||
|
except ccxt.InvalidOrder as e:
|
||||||
|
raise DependencyException(
|
||||||
|
'Could not create limit buy order on market {}.'
|
||||||
|
'Tried to buy amount {} at rate {} (total {}).'
|
||||||
|
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
||||||
|
)
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
|
raise NetworkException(
|
||||||
|
'Could not place buy order due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
def sell(pair: str, rate: float, amount: float) -> str:
|
def sell(pair: str, rate: float, amount: float) -> Dict:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
global _DRY_RUN_OPEN_ORDERS
|
global _DRY_RUN_OPEN_ORDERS
|
||||||
order_id = 'dry_run_sell_{}'.format(randint(0, 10**6))
|
order_id = 'dry_run_sell_{}'.format(randint(0, 10**6))
|
||||||
@ -124,62 +138,160 @@ def sell(pair: str, rate: float, amount: float) -> str:
|
|||||||
'opened': arrow.utcnow().datetime,
|
'opened': arrow.utcnow().datetime,
|
||||||
'closed': arrow.utcnow().datetime,
|
'closed': arrow.utcnow().datetime,
|
||||||
}
|
}
|
||||||
return order_id
|
return {'id': order_id}
|
||||||
|
|
||||||
return _API.sell(pair, rate, amount)
|
try:
|
||||||
|
return _API.create_limit_sell_order(pair, amount, rate)
|
||||||
|
except ccxt.InsufficientFunds as e:
|
||||||
|
raise DependencyException(
|
||||||
|
'Insufficient funds to create limit sell order on market {}.'
|
||||||
|
'Tried to sell amount {} at rate {} (total {}).'
|
||||||
|
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
||||||
|
)
|
||||||
|
except ccxt.InvalidOrder as e:
|
||||||
|
raise DependencyException(
|
||||||
|
'Could not create limit sell order on market {}.'
|
||||||
|
'Tried to sell amount {} at rate {} (total {}).'
|
||||||
|
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
||||||
|
)
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
|
raise NetworkException(
|
||||||
|
'Could not place sell order due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
def get_balance(currency: str) -> float:
|
def get_balance(currency: str) -> float:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
return 999.9
|
return 999.9
|
||||||
|
|
||||||
return _API.fetch_balance()[currency]
|
# ccxt exception is already handled by get_balances
|
||||||
|
balances = get_balances()
|
||||||
|
return balances[currency]['free']
|
||||||
|
|
||||||
|
|
||||||
def get_balances():
|
def get_balances() -> dict:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
return []
|
return {}
|
||||||
|
|
||||||
return _API.fetch_balance()
|
try:
|
||||||
|
balances = _API.fetch_balance()
|
||||||
|
# Remove additional info from ccxt results
|
||||||
|
balances.pop("info", None)
|
||||||
|
balances.pop("free", None)
|
||||||
|
balances.pop("total", None)
|
||||||
|
balances.pop("used", None)
|
||||||
|
|
||||||
|
return balances
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
|
raise NetworkException(
|
||||||
|
'Could not get balance due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
||||||
return _API.get_ticker(pair, refresh)
|
global _TICKER_CACHE
|
||||||
|
try:
|
||||||
|
if not refresh:
|
||||||
|
if _TICKER_CACHE:
|
||||||
|
return _TICKER_CACHE
|
||||||
|
_TICKER_CACHE = _API.fetch_ticker(pair)
|
||||||
|
return _TICKER_CACHE
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
|
raise NetworkException(
|
||||||
|
'Could not load tickers due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
@cached(TTLCache(maxsize=100, ttl=30))
|
@cached(TTLCache(maxsize=100, ttl=30))
|
||||||
def get_ticker_history(pair: str, tick_interval) -> List[Dict]:
|
def get_ticker_history(pair: str, tick_interval: str) -> List[Dict]:
|
||||||
return _API.get_ticker_history(pair, tick_interval)
|
if 'fetchOHLCV' not in _API.has or not _API.has['fetchOHLCV']:
|
||||||
|
raise OperationalException(
|
||||||
|
'Exhange {} does not support fetching historical candlestick data.'.format(_API.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
history = _API.fetch_ohlcv(pair, timeframe=tick_interval)
|
||||||
|
history_json = []
|
||||||
|
for candlestick in history:
|
||||||
|
history_json.append({
|
||||||
|
'T': datetime.fromtimestamp(candlestick[0]/1000.0).strftime('%Y-%m-%dT%H:%M:%S.%f'),
|
||||||
|
'O': candlestick[1],
|
||||||
|
'H': candlestick[2],
|
||||||
|
'L': candlestick[3],
|
||||||
|
'C': candlestick[4],
|
||||||
|
'V': candlestick[5],
|
||||||
|
})
|
||||||
|
return history_json
|
||||||
|
except IndexError as e:
|
||||||
|
logger.warning('Empty ticker history. Msg %s', str(e))
|
||||||
|
return []
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
|
raise NetworkException(
|
||||||
|
'Could not load ticker history due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
|
||||||
|
|
||||||
|
|
||||||
def cancel_order(order_id: str) -> None:
|
def cancel_order(order_id: str, pair: str) -> None:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
return
|
return
|
||||||
|
|
||||||
return _API.cancel_order(order_id)
|
try:
|
||||||
|
return _API.cancel_order(order_id, pair)
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
|
raise NetworkException(
|
||||||
|
'Could not get order due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.InvalidOrder as e:
|
||||||
|
raise DependencyException(
|
||||||
|
'Could not cancel order. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
def get_order(order_id: str) -> Dict:
|
def get_order(order_id: str, pair: str) -> Dict:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
order = _DRY_RUN_OPEN_ORDERS[order_id]
|
order = _DRY_RUN_OPEN_ORDERS[order_id]
|
||||||
order.update({
|
order.update({
|
||||||
'id': order_id
|
'id': order_id
|
||||||
})
|
})
|
||||||
return order
|
return order
|
||||||
|
try:
|
||||||
return _API.get_order(order_id)
|
return _API.fetch_order(order_id, pair)
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
|
raise NetworkException(
|
||||||
|
'Could not get order due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.InvalidOrder as e:
|
||||||
|
raise DependencyException(
|
||||||
|
'Could not get order. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: reimplement, not part of ccxt
|
||||||
def get_pair_detail_url(pair: str) -> str:
|
def get_pair_detail_url(pair: str) -> str:
|
||||||
return _API.get_pair_detail_url(pair)
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def get_markets() -> List[str]:
|
def get_markets() -> List[dict]:
|
||||||
return _API.get_markets()
|
try:
|
||||||
|
return _API.fetch_markets()
|
||||||
|
except ccxt.NetworkError as e:
|
||||||
def get_market_summaries() -> List[Dict]:
|
raise NetworkException(
|
||||||
return _API.public_get_marketsummaries()['result']
|
'Could not load markets due to networking error. Message: {}'.format(e)
|
||||||
|
)
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
def get_name() -> str:
|
def get_name() -> str:
|
||||||
@ -187,16 +299,8 @@ def get_name() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def get_fee() -> float:
|
def get_fee() -> float:
|
||||||
return _API.fee
|
# validate that markets are loaded before trying to get fee
|
||||||
|
if _API.markets is None or len(_API.markets) == 0:
|
||||||
|
_API.load_markets()
|
||||||
|
|
||||||
|
return _API.calculate_fee('ETH/BTC', '', '', 1, 1)['rate']
|
||||||
def get_wallet_health() -> List[Dict]:
|
|
||||||
data = _API.request('Currencies/GetWalletHealth', api='v2')
|
|
||||||
if not data['success']:
|
|
||||||
raise OperationalException('{}'.format(data['message']))
|
|
||||||
return [{
|
|
||||||
'Currency': entry['Health']['Currency'],
|
|
||||||
'IsActive': entry['Health']['IsActive'],
|
|
||||||
'LastChecked': entry['Health']['LastChecked'],
|
|
||||||
'Notice': entry['Currency'].get('Notice'),
|
|
||||||
} for entry in data['result']]
|
|
||||||
|
@ -1,172 +0,0 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class Exchange(ABC):
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""
|
|
||||||
Name of the exchange.
|
|
||||||
:return: str representation of the class name
|
|
||||||
"""
|
|
||||||
return self.__class__.__name__
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fee(self) -> float:
|
|
||||||
"""
|
|
||||||
Fee for placing an order
|
|
||||||
:return: percentage in float
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def buy(self, pair: str, rate: float, amount: float) -> str:
|
|
||||||
"""
|
|
||||||
Places a limit buy order.
|
|
||||||
:param pair: Pair as str, format: BTC_ETH
|
|
||||||
:param rate: Rate limit for order
|
|
||||||
:param amount: The amount to purchase
|
|
||||||
:return: order_id of the placed buy order
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def sell(self, pair: str, rate: float, amount: float) -> str:
|
|
||||||
"""
|
|
||||||
Places a limit sell order.
|
|
||||||
:param pair: Pair as str, format: BTC_ETH
|
|
||||||
:param rate: Rate limit for order
|
|
||||||
:param amount: The amount to sell
|
|
||||||
:return: order_id of the placed sell order
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_balance(self, currency: str) -> float:
|
|
||||||
"""
|
|
||||||
Gets account balance.
|
|
||||||
:param currency: Currency as str, format: BTC
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_balances(self) -> List[dict]:
|
|
||||||
"""
|
|
||||||
Gets account balances across currencies
|
|
||||||
:return: List of dicts, format: [
|
|
||||||
{
|
|
||||||
'Currency': str,
|
|
||||||
'Balance': float,
|
|
||||||
'Available': float,
|
|
||||||
'Pending': float,
|
|
||||||
}
|
|
||||||
...
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
|
|
||||||
"""
|
|
||||||
Gets ticker for given pair.
|
|
||||||
:param pair: Pair as str, format: BTC_ETC
|
|
||||||
:param refresh: Shall we query a new value or a cached value is enough
|
|
||||||
:return: dict, format: {
|
|
||||||
'bid': float,
|
|
||||||
'ask': float,
|
|
||||||
'last': float
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]:
|
|
||||||
"""
|
|
||||||
Gets ticker history for given pair.
|
|
||||||
:param pair: Pair as str, format: BTC_ETC
|
|
||||||
:param tick_interval: ticker interval in minutes
|
|
||||||
:return: list, format: [
|
|
||||||
{
|
|
||||||
'O': float, (Open)
|
|
||||||
'H': float, (High)
|
|
||||||
'L': float, (Low)
|
|
||||||
'C': float, (Close)
|
|
||||||
'V': float, (Volume)
|
|
||||||
'T': datetime, (Time)
|
|
||||||
'BV': float, (Base Volume)
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_order(self, order_id: str) -> Dict:
|
|
||||||
"""
|
|
||||||
Get order details for the given order_id.
|
|
||||||
:param order_id: ID as str
|
|
||||||
:return: dict, format: {
|
|
||||||
'id': str,
|
|
||||||
'type': str,
|
|
||||||
'pair': str,
|
|
||||||
'opened': str ISO 8601 datetime,
|
|
||||||
'closed': str ISO 8601 datetime,
|
|
||||||
'rate': float,
|
|
||||||
'amount': float,
|
|
||||||
'remaining': int
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def cancel_order(self, order_id: str) -> None:
|
|
||||||
"""
|
|
||||||
Cancels order for given order_id.
|
|
||||||
:param order_id: ID as str
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_pair_detail_url(self, pair: str) -> str:
|
|
||||||
"""
|
|
||||||
Returns the market detail url for the given pair.
|
|
||||||
:param pair: Pair as str, format: BTC_ETC
|
|
||||||
:return: URL as str
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_markets(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
Returns all available markets.
|
|
||||||
:return: List of all available pairs
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_market_summaries(self) -> List[Dict]:
|
|
||||||
"""
|
|
||||||
Returns a 24h market summary for all available markets
|
|
||||||
:return: list, format: [
|
|
||||||
{
|
|
||||||
'MarketName': str,
|
|
||||||
'High': float,
|
|
||||||
'Low': float,
|
|
||||||
'Volume': float,
|
|
||||||
'Last': float,
|
|
||||||
'TimeStamp': datetime,
|
|
||||||
'BaseVolume': float,
|
|
||||||
'Bid': float,
|
|
||||||
'Ask': float,
|
|
||||||
'OpenBuyOrders': int,
|
|
||||||
'OpenSellOrders': int,
|
|
||||||
'PrevDay': float,
|
|
||||||
'Created': datetime
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_wallet_health(self) -> List[Dict]:
|
|
||||||
"""
|
|
||||||
Returns a list of all wallet health information
|
|
||||||
:return: list, format: [
|
|
||||||
{
|
|
||||||
'Currency': str,
|
|
||||||
'IsActive': bool,
|
|
||||||
'LastChecked': str,
|
|
||||||
'Notice': str
|
|
||||||
},
|
|
||||||
...
|
|
||||||
"""
|
|
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import copy
|
import copy
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@ -9,10 +8,9 @@ from datetime import datetime
|
|||||||
from typing import Dict, List, Optional, Any
|
from typing import Dict, List, Optional, Any
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import requests
|
|
||||||
from cachetools import cached, TTLCache
|
from cachetools import cached, TTLCache
|
||||||
|
|
||||||
from freqtrade import (DependencyException, OperationalException, __version__,
|
from freqtrade import (DependencyException, OperationalException, NetworkException, __version__,
|
||||||
exchange, persistence, rpc)
|
exchange, persistence, rpc)
|
||||||
from freqtrade.analyze import get_signal
|
from freqtrade.analyze import get_signal
|
||||||
from freqtrade.fiat_convert import CryptoToFiatConverter
|
from freqtrade.fiat_convert import CryptoToFiatConverter
|
||||||
@ -33,21 +31,23 @@ def refresh_whitelist(whitelist: List[str]) -> List[str]:
|
|||||||
:return: the list of pairs the user wants to trade without the one unavailable or black_listed
|
:return: the list of pairs the user wants to trade without the one unavailable or black_listed
|
||||||
"""
|
"""
|
||||||
sanitized_whitelist = whitelist
|
sanitized_whitelist = whitelist
|
||||||
health = exchange.get_wallet_health()
|
markets = exchange.get_markets()
|
||||||
|
|
||||||
|
markets = [m for m in markets if m['quote'] == _CONF['stake_currency']]
|
||||||
known_pairs = set()
|
known_pairs = set()
|
||||||
for status in health:
|
for market in markets:
|
||||||
pair = '{}_{}'.format(_CONF['stake_currency'], status['Currency'])
|
pair = market['symbol']
|
||||||
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
|
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
|
||||||
if pair not in whitelist or pair in _CONF['exchange'].get('pair_blacklist', []):
|
if pair not in whitelist or pair in _CONF['exchange'].get('pair_blacklist', []):
|
||||||
continue
|
continue
|
||||||
# else the pair is valid
|
# else the pair is valid
|
||||||
known_pairs.add(pair)
|
known_pairs.add(pair)
|
||||||
# Market is not active
|
# Market is not active
|
||||||
if not status['IsActive']:
|
if not market['active']:
|
||||||
sanitized_whitelist.remove(pair)
|
sanitized_whitelist.remove(pair)
|
||||||
logger.info(
|
logger.info(
|
||||||
'Ignoring %s from whitelist (reason: %s).',
|
'Ignoring %s from whitelist. Market is not active.',
|
||||||
pair, status.get('Notice') or 'wallet is not active'
|
pair
|
||||||
)
|
)
|
||||||
|
|
||||||
# We need to remove pairs that are unknown
|
# We need to remove pairs that are unknown
|
||||||
@ -85,7 +85,7 @@ def process_maybe_execute_sell(trade, interval):
|
|||||||
if trade.open_order_id:
|
if trade.open_order_id:
|
||||||
# Update trade with order values
|
# Update trade with order values
|
||||||
logger.info('Got open order for %s', trade)
|
logger.info('Got open order for %s', trade)
|
||||||
trade.update(exchange.get_order(trade.open_order_id))
|
trade.update(exchange.get_order(trade.open_order_id, trade.pair))
|
||||||
|
|
||||||
if trade.is_open and trade.open_order_id is None:
|
if trade.is_open and trade.open_order_id is None:
|
||||||
# Check if we can sell our current pair
|
# Check if we can sell our current pair
|
||||||
@ -126,7 +126,7 @@ def _process(interval: int, nb_assets: Optional[int] = 0) -> bool:
|
|||||||
check_handle_timedout(_CONF['unfilledtimeout'])
|
check_handle_timedout(_CONF['unfilledtimeout'])
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
except (requests.exceptions.RequestException, json.JSONDecodeError) as error:
|
except (NetworkException, DependencyException) as error:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Got %s in _process(), retrying in 30 seconds...',
|
'Got %s in _process(), retrying in 30 seconds...',
|
||||||
error
|
error
|
||||||
@ -149,7 +149,7 @@ def handle_timedout_limit_buy(trade: Trade, order: Dict) -> bool:
|
|||||||
"""Buy timeout - cancel order
|
"""Buy timeout - cancel order
|
||||||
:return: True if order was fully cancelled
|
:return: True if order was fully cancelled
|
||||||
"""
|
"""
|
||||||
exchange.cancel_order(trade.open_order_id)
|
exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
if order['remaining'] == order['amount']:
|
if order['remaining'] == order['amount']:
|
||||||
# if trade is not partially completed, just delete the trade
|
# if trade is not partially completed, just delete the trade
|
||||||
Trade.session.delete(trade)
|
Trade.session.delete(trade)
|
||||||
@ -180,7 +180,7 @@ def handle_timedout_limit_sell(trade: Trade, order: Dict) -> bool:
|
|||||||
"""
|
"""
|
||||||
if order['remaining'] == order['amount']:
|
if order['remaining'] == order['amount']:
|
||||||
# if trade is not partially completed, just cancel the trade
|
# if trade is not partially completed, just cancel the trade
|
||||||
exchange.cancel_order(trade.open_order_id)
|
exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
trade.close_rate = None
|
trade.close_rate = None
|
||||||
trade.close_profit = None
|
trade.close_profit = None
|
||||||
trade.close_date = None
|
trade.close_date = None
|
||||||
@ -205,8 +205,8 @@ def check_handle_timedout(timeoutvalue: int) -> None:
|
|||||||
|
|
||||||
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:
|
||||||
order = exchange.get_order(trade.open_order_id)
|
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||||
except requests.exceptions.RequestException:
|
except (NetworkException, DependencyException):
|
||||||
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
||||||
continue
|
continue
|
||||||
ordertime = arrow.get(order['opened'])
|
ordertime = arrow.get(order['opened'])
|
||||||
@ -229,7 +229,8 @@ def execute_sell(trade: Trade, limit: float) -> None:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
# Execute sell and update trade record
|
# Execute sell and update trade record
|
||||||
order_id = exchange.sell(str(trade.pair), limit, trade.amount)
|
order = exchange.sell(str(trade.pair), limit, trade.amount)
|
||||||
|
order_id = order['id']
|
||||||
trade.open_order_id = order_id
|
trade.open_order_id = order_id
|
||||||
|
|
||||||
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
|
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
|
||||||
@ -301,7 +302,7 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) -
|
|||||||
# Check if time matches and current rate is above threshold
|
# Check if time matches and current rate is above threshold
|
||||||
time_diff = (current_time - trade.open_date).total_seconds() / 60
|
time_diff = (current_time - trade.open_date).total_seconds() / 60
|
||||||
for duration, threshold in sorted(strategy.minimal_roi.items()):
|
for duration, threshold in sorted(strategy.minimal_roi.items()):
|
||||||
if time_diff > float(duration) and current_profit > threshold:
|
if time_diff >= float(duration) and current_profit > threshold:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', float(current_profit) * 100.0)
|
logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', float(current_profit) * 100.0)
|
||||||
@ -402,7 +403,8 @@ def create_trade(stake_amount: float, interval: int) -> bool:
|
|||||||
buy_limit = get_target_bid(exchange.get_ticker(pair))
|
buy_limit = get_target_bid(exchange.get_ticker(pair))
|
||||||
amount = stake_amount / buy_limit
|
amount = stake_amount / buy_limit
|
||||||
|
|
||||||
order_id = exchange.buy(pair, buy_limit, amount)
|
order = exchange.buy(pair, buy_limit, amount)
|
||||||
|
order_id = order['id']
|
||||||
|
|
||||||
fiat_converter = CryptoToFiatConverter()
|
fiat_converter = CryptoToFiatConverter()
|
||||||
stake_amount_fiat = fiat_converter.convert_amount(
|
stake_amount_fiat = fiat_converter.convert_amount(
|
||||||
@ -427,7 +429,7 @@ def create_trade(stake_amount: float, interval: int) -> bool:
|
|||||||
fee=exchange.get_fee(),
|
fee=exchange.get_fee(),
|
||||||
open_rate=buy_limit,
|
open_rate=buy_limit,
|
||||||
open_date=datetime.utcnow(),
|
open_date=datetime.utcnow(),
|
||||||
exchange=exchange.get_name().upper(),
|
exchange=exchange.get_name().lower(),
|
||||||
open_order_id=order_id
|
open_order_id=order_id
|
||||||
)
|
)
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
@ -466,14 +468,13 @@ def gen_pair_whitelist(base_currency: str, key: str = 'BaseVolume') -> List[str]
|
|||||||
:param key: sort key (defaults to 'BaseVolume')
|
:param key: sort key (defaults to 'BaseVolume')
|
||||||
:return: List of pairs
|
:return: List of pairs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
summaries = sorted(
|
pairs = sorted(
|
||||||
(s for s in exchange.get_market_summaries() if s['MarketName'].endswith(base_currency)),
|
[s['symbol'] for s in exchange.get_markets() if s['quote'] == base_currency],
|
||||||
key=lambda s: s.get(key) or 0.0,
|
|
||||||
reverse=True
|
reverse=True
|
||||||
)
|
)
|
||||||
|
|
||||||
return [s['MarketName'].replace('-', '_') for s in summaries]
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
def cleanup() -> None:
|
def cleanup() -> None:
|
||||||
@ -553,7 +554,7 @@ def main(sysargv=sys.argv[1:]) -> int:
|
|||||||
_process,
|
_process,
|
||||||
min_secs=_CONF['internals'].get('process_throttle_secs', 10),
|
min_secs=_CONF['internals'].get('process_throttle_secs', 10),
|
||||||
nb_assets=args.dynamic_whitelist,
|
nb_assets=args.dynamic_whitelist,
|
||||||
interval=int(_CONF.get('ticker_interval', 5))
|
interval=_CONF.get('ticker_interval', '5m')
|
||||||
)
|
)
|
||||||
old_state = new_state
|
old_state = new_state
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -60,6 +60,10 @@ def common_datearray(dfs):
|
|||||||
return np.sort(arr, axis=0)
|
return np.sort(arr, axis=0)
|
||||||
|
|
||||||
|
|
||||||
|
def ticker_interval_to_minutes(interval) -> int:
|
||||||
|
return TICKER_INTERVAL_MINUTES[interval]
|
||||||
|
|
||||||
|
|
||||||
def file_dump_json(filename, data) -> None:
|
def file_dump_json(filename, data) -> None:
|
||||||
with open(filename, 'w') as fp:
|
with open(filename, 'w') as fp:
|
||||||
json.dump(data, fp)
|
json.dump(data, fp)
|
||||||
@ -216,11 +220,10 @@ def backtesting_options(parser: argparse.ArgumentParser) -> None:
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--ticker-interval',
|
'-i', '--ticker-interval',
|
||||||
help='specify ticker interval in minutes (1, 5, 30, 60, 1440)',
|
help='specify ticker interval (1m, 5m, 30m, 1h, 12h)',
|
||||||
dest='ticker_interval',
|
dest='ticker_interval',
|
||||||
default=5,
|
default='5m',
|
||||||
type=int,
|
type=str
|
||||||
metavar='INT',
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--realistic-simulation',
|
'--realistic-simulation',
|
||||||
@ -271,9 +274,8 @@ def hyperopt_options(parser: argparse.ArgumentParser) -> None:
|
|||||||
'-i', '--ticker-interval',
|
'-i', '--ticker-interval',
|
||||||
help='specify ticker interval in minutes (default: 5)',
|
help='specify ticker interval in minutes (default: 5)',
|
||||||
dest='ticker_interval',
|
dest='ticker_interval',
|
||||||
default=5,
|
default='5m',
|
||||||
type=int,
|
type=str,
|
||||||
metavar='INT',
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--timerange',
|
'--timerange',
|
||||||
@ -331,12 +333,26 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None:
|
|||||||
hyperopt_options(hyperopt_cmd)
|
hyperopt_options(hyperopt_cmd)
|
||||||
|
|
||||||
|
|
||||||
|
TICKER_INTERVAL_MINUTES = {
|
||||||
|
'1m': 1,
|
||||||
|
'5m': 5,
|
||||||
|
'15m': 15,
|
||||||
|
'30m': 30,
|
||||||
|
'1h': 60,
|
||||||
|
'2h': 120,
|
||||||
|
'4h': 240,
|
||||||
|
'6h': 360,
|
||||||
|
'12h': 720,
|
||||||
|
'1d': 1440,
|
||||||
|
'1w': 10080,
|
||||||
|
}
|
||||||
|
|
||||||
# Required json-schema for user specified config
|
# Required json-schema for user specified config
|
||||||
CONF_SCHEMA = {
|
CONF_SCHEMA = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'max_open_trades': {'type': 'integer', 'minimum': 1},
|
'max_open_trades': {'type': 'integer', 'minimum': 1},
|
||||||
'ticker_interval': {'type': 'integer', 'enum': [1, 5, 30, 60, 1440]},
|
'ticker_interval': {'type': 'string', 'enum': ['1m', '5m', '30m', '1h', '12h']},
|
||||||
'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']},
|
'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']},
|
||||||
'stake_amount': {'type': 'number', 'minimum': 0.0005},
|
'stake_amount': {'type': 'number', 'minimum': 0.0005},
|
||||||
'fiat_display_currency': {'type': 'string', 'enum': ['AUD', 'BRL', 'CAD', 'CHF',
|
'fiat_display_currency': {'type': 'string', 'enum': ['AUD', 'BRL', 'CAD', 'CHF',
|
||||||
|
@ -33,6 +33,7 @@ def load_tickerdata_file(datadir, pair, ticker_interval, timerange=None):
|
|||||||
:return dict OR empty if unsuccesful
|
:return dict OR empty if unsuccesful
|
||||||
"""
|
"""
|
||||||
path = make_testdata_path(datadir)
|
path = make_testdata_path(datadir)
|
||||||
|
pair = pair.replace('/', '_')
|
||||||
file = '{abspath}/{pair}-{ticker_interval}.json'.format(
|
file = '{abspath}/{pair}-{ticker_interval}.json'.format(
|
||||||
abspath=path,
|
abspath=path,
|
||||||
pair=pair,
|
pair=pair,
|
||||||
@ -56,7 +57,7 @@ def load_tickerdata_file(datadir, pair, ticker_interval, timerange=None):
|
|||||||
return pairdata
|
return pairdata
|
||||||
|
|
||||||
|
|
||||||
def load_data(datadir: str, ticker_interval: int, pairs: Optional[List[str]] = None,
|
def load_data(datadir: str, ticker_interval: str, pairs: Optional[List[str]] = None,
|
||||||
refresh_pairs: Optional[bool] = False, timerange=None) -> Dict[str, List]:
|
refresh_pairs: Optional[bool] = False, timerange=None) -> Dict[str, List]:
|
||||||
"""
|
"""
|
||||||
Loads ticker history data for the given parameters
|
Loads ticker history data for the given parameters
|
||||||
@ -101,13 +102,13 @@ def make_testdata_path(datadir: str) -> str:
|
|||||||
'..', 'tests', 'testdata'))
|
'..', 'tests', 'testdata'))
|
||||||
|
|
||||||
|
|
||||||
def download_pairs(datadir, pairs: List[str], ticker_interval: int) -> bool:
|
def download_pairs(datadir, pairs: List[str], ticker_interval: str) -> bool:
|
||||||
"""For each pairs passed in parameters, download the ticker intervals"""
|
"""For each pairs passed in parameters, download the ticker intervals"""
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
try:
|
try:
|
||||||
download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval)
|
download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
logger.info('Failed to download the pair: "{pair}", Interval: {interval} min'.format(
|
logger.info('Failed to download the pair: "{pair}", Interval: {interval}'.format(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
interval=ticker_interval,
|
interval=ticker_interval,
|
||||||
))
|
))
|
||||||
@ -121,7 +122,7 @@ def file_dump_json(filename, data):
|
|||||||
|
|
||||||
|
|
||||||
# FIX: 20180110, suggest rename interval to tick_interval
|
# FIX: 20180110, suggest rename interval to tick_interval
|
||||||
def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> bool:
|
def download_backtesting_testdata(datadir: str, pair: str, interval: str = '5m') -> bool:
|
||||||
"""
|
"""
|
||||||
Download the latest 1 and 5 ticker intervals from Bittrex for the pairs passed in parameters
|
Download the latest 1 and 5 ticker intervals from Bittrex for the pairs passed in parameters
|
||||||
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
||||||
@ -130,12 +131,12 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
path = make_testdata_path(datadir)
|
path = make_testdata_path(datadir)
|
||||||
logger.info('Download the pair: "{pair}", Interval: {interval} min'.format(
|
logger.info('Downloading the pair: "{pair}", Interval: {interval}'.format(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
interval=interval,
|
interval=interval,
|
||||||
))
|
))
|
||||||
|
|
||||||
filepair = pair.replace("-", "_")
|
filepair = pair.replace("/", "_")
|
||||||
filename = os.path.join(path, '{pair}-{interval}.json'.format(
|
filename = os.path.join(path, '{pair}-{interval}.json'.format(
|
||||||
pair=filepair,
|
pair=filepair,
|
||||||
interval=interval,
|
interval=interval,
|
||||||
@ -151,14 +152,12 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
|
|||||||
logger.debug("Current Start: None")
|
logger.debug("Current Start: None")
|
||||||
logger.debug("Current End: None")
|
logger.debug("Current End: None")
|
||||||
|
|
||||||
new_data = get_ticker_history(pair=pair, tick_interval=int(interval))
|
new_data = get_ticker_history(pair=pair, tick_interval=interval)
|
||||||
for row in new_data:
|
|
||||||
if row not in data:
|
|
||||||
data.append(row)
|
|
||||||
logger.debug("New Start: {}".format(data[1]['T']))
|
|
||||||
logger.debug("New End: {}".format(data[-1:][0]['T']))
|
|
||||||
data = sorted(data, key=lambda data: data['T'])
|
|
||||||
|
|
||||||
misc.file_dump_json(filename, data)
|
logger.debug("New Start: {}".format(new_data[1]['T']))
|
||||||
|
logger.debug("New End: {}".format(new_data[-1]['T']))
|
||||||
|
data = sorted(data, key=lambda data_json: data_json['T'])
|
||||||
|
|
||||||
|
misc.file_dump_json(filename, new_data)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
|
import ccxt
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame, Series
|
from pandas import DataFrame, Series
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
@ -48,7 +49,7 @@ def generate_text_table(
|
|||||||
len(result.index),
|
len(result.index),
|
||||||
result.profit_percent.mean() * 100.0,
|
result.profit_percent.mean() * 100.0,
|
||||||
result.profit_BTC.sum(),
|
result.profit_BTC.sum(),
|
||||||
result.duration.mean() * ticker_interval,
|
result.duration.mean() * misc.ticker_interval_to_minutes(ticker_interval),
|
||||||
len(result[result.profit_BTC > 0]),
|
len(result[result.profit_BTC > 0]),
|
||||||
len(result[result.profit_BTC < 0])
|
len(result[result.profit_BTC < 0])
|
||||||
])
|
])
|
||||||
@ -59,7 +60,7 @@ def generate_text_table(
|
|||||||
len(results.index),
|
len(results.index),
|
||||||
results.profit_percent.mean() * 100.0,
|
results.profit_percent.mean() * 100.0,
|
||||||
results.profit_BTC.sum(),
|
results.profit_BTC.sum(),
|
||||||
results.duration.mean() * ticker_interval,
|
results.duration.mean() * misc.ticker_interval_to_minutes(ticker_interval),
|
||||||
len(results[results.profit_BTC > 0]),
|
len(results[results.profit_BTC > 0]),
|
||||||
len(results[results.profit_BTC < 0])
|
len(results[results.profit_BTC < 0])
|
||||||
])
|
])
|
||||||
@ -113,7 +114,7 @@ def backtest(args) -> DataFrame:
|
|||||||
records = []
|
records = []
|
||||||
trades = []
|
trades = []
|
||||||
trade_count_lock: dict = {}
|
trade_count_lock: dict = {}
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance()
|
||||||
for pair, pair_data in processed.items():
|
for pair, pair_data in processed.items():
|
||||||
pair_data['buy'], pair_data['sell'] = 0, 0
|
pair_data['buy'], pair_data['sell'] = 0, 0
|
||||||
ticker = populate_sell_trend(populate_buy_trend(pair_data))
|
ticker = populate_sell_trend(populate_buy_trend(pair_data))
|
||||||
@ -164,7 +165,7 @@ def start(args):
|
|||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
)
|
)
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance()
|
||||||
|
|
||||||
logger.info('Using config: %s ...', args.config)
|
logger.info('Using config: %s ...', args.config)
|
||||||
config = misc.load_config(args.config)
|
config = misc.load_config(args.config)
|
||||||
|
@ -17,6 +17,7 @@ import talib.abstract as ta
|
|||||||
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
|
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
|
||||||
from hyperopt.mongoexp import MongoTrials
|
from hyperopt.mongoexp import MongoTrials
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
import ccxt
|
||||||
|
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
# Monkey patch config
|
# Monkey patch config
|
||||||
@ -448,7 +449,7 @@ def start(args):
|
|||||||
|
|
||||||
TOTAL_TRIES = args.epochs
|
TOTAL_TRIES = args.epochs
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance()
|
||||||
|
|
||||||
# Initialize logger
|
# Initialize logger
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
|
@ -94,6 +94,7 @@ def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[
|
|||||||
:param command_handler: Telegram CommandHandler
|
:param command_handler: Telegram CommandHandler
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
update = kwargs.get('update') or args[1]
|
update = kwargs.get('update') or args[1]
|
||||||
|
|
||||||
@ -108,6 +109,7 @@ def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[
|
|||||||
return command_handler(*args, **kwargs)
|
return command_handler(*args, **kwargs)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
logger.exception('Exception occurred within Telegram module')
|
logger.exception('Exception occurred within Telegram module')
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@ -138,7 +140,7 @@ def _status(bot: Bot, update: Update) -> None:
|
|||||||
for trade in trades:
|
for trade in trades:
|
||||||
order = None
|
order = None
|
||||||
if trade.open_order_id:
|
if trade.open_order_id:
|
||||||
order = exchange.get_order(trade.open_order_id)
|
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
current_rate = exchange.get_ticker(trade.pair, False)['bid']
|
current_rate = exchange.get_ticker(trade.pair, False)['bid']
|
||||||
current_profit = trade.calc_profit_percent(current_rate)
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
@ -238,9 +240,9 @@ def _daily(bot: Bot, update: Update) -> None:
|
|||||||
profitday = today - timedelta(days=day)
|
profitday = today - timedelta(days=day)
|
||||||
trades = Trade.query \
|
trades = Trade.query \
|
||||||
.filter(Trade.is_open.is_(False)) \
|
.filter(Trade.is_open.is_(False)) \
|
||||||
.filter(Trade.close_date >= profitday)\
|
.filter(Trade.close_date >= profitday) \
|
||||||
.filter(Trade.close_date < (profitday + timedelta(days=1)))\
|
.filter(Trade.close_date < (profitday + timedelta(days=1))) \
|
||||||
.order_by(Trade.close_date)\
|
.order_by(Trade.close_date) \
|
||||||
.all()
|
.all()
|
||||||
curdayprofit = sum(trade.calc_profit() for trade in trades)
|
curdayprofit = sum(trade.calc_profit() for trade in trades)
|
||||||
profit_days[profitday] = {
|
profit_days[profitday] = {
|
||||||
@ -385,42 +387,42 @@ def _balance(bot: Bot, update: Update) -> None:
|
|||||||
Returns current account balance per crypto
|
Returns current account balance per crypto
|
||||||
"""
|
"""
|
||||||
output = ''
|
output = ''
|
||||||
balances = [
|
balances = {
|
||||||
c for c in exchange.get_balances()
|
k: c for k, c in exchange.get_balances().items()
|
||||||
if c['Balance'] or c['Available'] or c['Pending']
|
if c['total'] or c['free'] or c['used']
|
||||||
]
|
}
|
||||||
if not balances:
|
if not balances:
|
||||||
send_msg('`All balances are zero.`')
|
send_msg('`All balances are zero.`')
|
||||||
return
|
return
|
||||||
|
|
||||||
total = 0.0
|
total = 0.0
|
||||||
for currency in balances:
|
for coin, balance in balances.items():
|
||||||
coin = currency['Currency']
|
|
||||||
if coin == 'BTC':
|
if coin == 'BTC':
|
||||||
currency["Rate"] = 1.0
|
balance["rate"] = 1.0
|
||||||
else:
|
else:
|
||||||
if coin == 'USDT':
|
if coin == 'USDT':
|
||||||
currency["Rate"] = 1.0 / exchange.get_ticker('USDT_BTC', False)['bid']
|
balance["rate"] = 1.0 / exchange.get_ticker('BTC/USDT', False)['bid']
|
||||||
else:
|
else:
|
||||||
currency["Rate"] = exchange.get_ticker('BTC_' + coin, False)['bid']
|
balance["rate"] = exchange.get_ticker(coin + '/BTC', False)['bid']
|
||||||
currency['BTC'] = currency["Rate"] * currency["Balance"]
|
balance['BTCequiv'] = balance["rate"] * balance["total"]
|
||||||
total = total + currency['BTC']
|
balance['coin'] = coin
|
||||||
output += """*Currency*: {Currency}
|
total = total + balance['BTCequiv']
|
||||||
*Available*: {Available}
|
output += """*Currency*: {coin}
|
||||||
*Balance*: {Balance}
|
*Available*: {free}
|
||||||
*Pending*: {Pending}
|
*Balance*: {total}
|
||||||
*Est. BTC*: {BTC: .8f}
|
*Pending*: {used}
|
||||||
|
*Est. BTC*: {BTCequiv: .8f}
|
||||||
|
|
||||||
""".format(**currency)
|
""".format(**balance)
|
||||||
|
|
||||||
symbol = _CONF['fiat_display_currency']
|
symbol = _CONF['fiat_display_currency']
|
||||||
value = _FIAT_CONVERT.convert_amount(
|
value = _FIAT_CONVERT.convert_amount(
|
||||||
total, 'BTC', symbol
|
total, 'BTC', symbol
|
||||||
)
|
)
|
||||||
output += """*Estimated Value*:
|
output += """*Estimated Value*:
|
||||||
*BTC*: {0: .8f}
|
*BTC*: {0: .8f}
|
||||||
*{1}*: {2: .2f}
|
*{1}*: {2: .2f}
|
||||||
""".format(total, symbol, value)
|
""".format(total, symbol, value)
|
||||||
send_msg(output)
|
send_msg(output)
|
||||||
|
|
||||||
|
|
||||||
@ -597,11 +599,11 @@ def shorten_date(_date):
|
|||||||
def _exec_forcesell(trade: Trade) -> None:
|
def _exec_forcesell(trade: Trade) -> None:
|
||||||
# Check if there is there is an open order
|
# Check if there is there is an open order
|
||||||
if trade.open_order_id:
|
if trade.open_order_id:
|
||||||
order = exchange.get_order(trade.open_order_id)
|
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||||
|
|
||||||
# Cancel open LIMIT_BUY orders and close trade
|
# Cancel open LIMIT_BUY orders and close trade
|
||||||
if order and not order['closed'] and order['type'] == 'LIMIT_BUY':
|
if order and not order['closed'] and order['type'] == 'LIMIT_BUY':
|
||||||
exchange.cancel_order(trade.open_order_id)
|
exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
trade.close(order.get('rate') or trade.open_rate)
|
trade.close(order.get('rate') or trade.open_rate)
|
||||||
# TODO: sell amount which has been bought already
|
# TODO: sell amount which has been bought already
|
||||||
return
|
return
|
||||||
|
@ -30,7 +30,7 @@ def default_conf():
|
|||||||
"stake_currency": "BTC",
|
"stake_currency": "BTC",
|
||||||
"stake_amount": 0.001,
|
"stake_amount": 0.001,
|
||||||
"fiat_display_currency": "USD",
|
"fiat_display_currency": "USD",
|
||||||
"ticker_interval": 5,
|
"ticker_interval": "5m",
|
||||||
"dry_run": True,
|
"dry_run": True,
|
||||||
"minimal_roi": {
|
"minimal_roi": {
|
||||||
"40": 0.0,
|
"40": 0.0,
|
||||||
@ -44,16 +44,16 @@ def default_conf():
|
|||||||
"ask_last_balance": 0.0
|
"ask_last_balance": 0.0
|
||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "bittrex",
|
"name": "binance",
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"key": "key",
|
"key": "key",
|
||||||
"secret": "secret",
|
"secret": "secret",
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"BTC_ETH",
|
"ETH/BTC",
|
||||||
"BTC_TKN",
|
"TKN/BTC",
|
||||||
"BTC_TRST",
|
"TRST/BTC",
|
||||||
"BTC_SWT",
|
"SWT/BTC",
|
||||||
"BTC_BCC"
|
"BCC/BTC"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"telegram": {
|
"telegram": {
|
||||||
@ -150,7 +150,7 @@ def limit_buy_order_old():
|
|||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_buy_old',
|
'id': 'mocked_limit_buy_old',
|
||||||
'type': 'LIMIT_BUY',
|
'type': 'LIMIT_BUY',
|
||||||
'pair': 'BTC_ETH',
|
'pair': 'ETH/BTC',
|
||||||
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||||
'rate': 0.00001099,
|
'rate': 0.00001099,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
@ -163,7 +163,7 @@ def limit_sell_order_old():
|
|||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_sell_old',
|
'id': 'mocked_limit_sell_old',
|
||||||
'type': 'LIMIT_SELL',
|
'type': 'LIMIT_SELL',
|
||||||
'pair': 'BTC_ETH',
|
'pair': 'ETH/BTC',
|
||||||
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||||
'rate': 0.00001099,
|
'rate': 0.00001099,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
@ -176,7 +176,7 @@ def limit_buy_order_old_partial():
|
|||||||
return {
|
return {
|
||||||
'id': 'mocked_limit_buy_old_partial',
|
'id': 'mocked_limit_buy_old_partial',
|
||||||
'type': 'LIMIT_BUY',
|
'type': 'LIMIT_BUY',
|
||||||
'pair': 'BTC_ETH',
|
'pair': 'ETH/BTC',
|
||||||
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
'opened': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||||
'rate': 0.00001099,
|
'rate': 0.00001099,
|
||||||
'amount': 90.99181073,
|
'amount': 90.99181073,
|
||||||
@ -198,6 +198,36 @@ def limit_sell_order():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ticker_history_api():
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
1511686200000, # unix timestamp ms
|
||||||
|
8.794e-05, # open
|
||||||
|
8.948e-05, # high
|
||||||
|
8.794e-05, # low
|
||||||
|
8.88e-05, # close
|
||||||
|
0.0877869, # volume (in quote currency)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1511686500000,
|
||||||
|
8.88e-05,
|
||||||
|
8.942e-05,
|
||||||
|
8.88e-05,
|
||||||
|
8.893e-05,
|
||||||
|
0.05874751,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1511686800,
|
||||||
|
8.891e-05,
|
||||||
|
8.893e-05,
|
||||||
|
8.875e-05,
|
||||||
|
8.877e-05,
|
||||||
|
0.7039405
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ticker_history():
|
def ticker_history():
|
||||||
return [
|
return [
|
||||||
@ -263,7 +293,7 @@ def ticker_history_without_bv():
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def result():
|
def result():
|
||||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file:
|
||||||
return parse_ticker_dataframe(json.load(data_file))
|
return parse_ticker_dataframe(json.load(data_file))
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
|
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
|
||||||
# pragma pylint: disable=protected-access
|
# pragma pylint: disable=protected-access
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
from random import randint
|
from random import randint
|
||||||
import logging
|
import logging
|
||||||
from requests.exceptions import RequestException
|
import ccxt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException, DependencyException, NetworkException
|
||||||
from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get_balances, \
|
from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get_balances, \
|
||||||
get_ticker, get_ticker_history, cancel_order, get_name, get_fee
|
get_ticker, get_ticker_history, cancel_order, get_name, get_fee
|
||||||
import freqtrade.exchange as exchange
|
import freqtrade.exchange as exchange
|
||||||
@ -42,9 +42,12 @@ def test_init_exception(default_conf):
|
|||||||
|
|
||||||
def test_validate_pairs(default_conf, mocker):
|
def test_validate_pairs(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(return_value=[
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
'BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT', 'BTC_BCC',
|
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
||||||
])
|
})
|
||||||
|
id_mock = PropertyMock(return_value='test_exchange')
|
||||||
|
type(api_mock).id = id_mock
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
@ -52,7 +55,7 @@ def test_validate_pairs(default_conf, mocker):
|
|||||||
|
|
||||||
def test_validate_pairs_not_available(default_conf, mocker):
|
def test_validate_pairs_not_available(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(return_value=[])
|
api_mock.load_markets = MagicMock(return_value={})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
with pytest.raises(OperationalException, match=r'not available'):
|
with pytest.raises(OperationalException, match=r'not available'):
|
||||||
@ -61,8 +64,9 @@ def test_validate_pairs_not_available(default_conf, mocker):
|
|||||||
|
|
||||||
def test_validate_pairs_not_compatible(default_conf, mocker):
|
def test_validate_pairs_not_compatible(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT'])
|
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
||||||
|
})
|
||||||
default_conf['stake_currency'] = 'ETH'
|
default_conf['stake_currency'] = 'ETH'
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
@ -72,10 +76,9 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
|
|||||||
|
|
||||||
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_markets = MagicMock(side_effect=RequestException())
|
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# with pytest.raises(RequestException, match=r'Unable to validate pairs'):
|
|
||||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
assert ('freqtrade.exchange',
|
assert ('freqtrade.exchange',
|
||||||
logging.WARNING,
|
logging.WARNING,
|
||||||
@ -87,38 +90,100 @@ def test_buy_dry_run(default_conf, mocker):
|
|||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
|
order = buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'dry_run_buy_' in order['id']
|
||||||
|
|
||||||
|
|
||||||
def test_buy_prod(default_conf, mocker):
|
def test_buy_prod(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.buy = MagicMock(
|
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||||
return_value='dry_run_buy_{}'.format(randint(0, 10**6)))
|
api_mock.create_limit_buy_order = MagicMock(return_value={
|
||||||
|
'id': order_id,
|
||||||
|
'info': {
|
||||||
|
'foo': 'bar'
|
||||||
|
}
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_buy_' in buy(pair='BTC_ETH', rate=200, amount=1)
|
order = buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'info' in order
|
||||||
|
assert order['id'] == order_id
|
||||||
|
|
||||||
|
# test exception handling
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
|
||||||
def test_sell_dry_run(default_conf, mocker):
|
def test_sell_dry_run(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
|
order = sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'dry_run_sell_' in order['id']
|
||||||
|
|
||||||
|
|
||||||
def test_sell_prod(default_conf, mocker):
|
def test_sell_prod(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.sell = MagicMock(
|
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
|
||||||
return_value='dry_run_sell_{}'.format(randint(0, 10**6)))
|
api_mock.create_limit_sell_order = MagicMock(return_value={
|
||||||
|
'id': order_id,
|
||||||
|
'info': {
|
||||||
|
'foo': 'bar'
|
||||||
|
}
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert 'dry_run_sell_' in sell(pair='BTC_ETH', rate=200, amount=1)
|
order = sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'info' in order
|
||||||
|
assert order['id'] == order_id
|
||||||
|
|
||||||
|
# test exception handling
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
|
|
||||||
def test_get_balance_dry_run(default_conf, mocker):
|
def test_get_balance_dry_run(default_conf, mocker):
|
||||||
@ -130,7 +195,7 @@ def test_get_balance_dry_run(default_conf, mocker):
|
|||||||
|
|
||||||
def test_get_balance_prod(default_conf, mocker):
|
def test_get_balance_prod(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_balance = MagicMock(return_value=123.4)
|
api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
@ -138,36 +203,51 @@ def test_get_balance_prod(default_conf, mocker):
|
|||||||
|
|
||||||
assert get_balance(currency='BTC') == 123.4
|
assert get_balance(currency='BTC') == 123.4
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
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
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert get_balances() == []
|
assert get_balances() == {}
|
||||||
|
|
||||||
|
|
||||||
def test_get_balances_prod(default_conf, mocker):
|
def test_get_balances_prod(default_conf, mocker):
|
||||||
balance_item = {
|
balance_item = {
|
||||||
'Currency': '1ST',
|
'free': 10.0,
|
||||||
'Balance': 10.0,
|
'total': 10.0,
|
||||||
'Available': 10.0,
|
'used': 0.0
|
||||||
'Pending': 0.0,
|
|
||||||
'CryptoAddress': None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_balances = MagicMock(
|
api_mock.fetch_balance = MagicMock(return_value={
|
||||||
return_value=[balance_item, balance_item, balance_item])
|
'1ST': balance_item,
|
||||||
|
'2ST': balance_item,
|
||||||
|
'3ST': balance_item
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert len(get_balances()) == 3
|
assert len(get_balances()) == 3
|
||||||
assert get_balances()[0]['Currency'] == '1ST'
|
assert get_balances()['1ST']['free'] == 10.0
|
||||||
assert get_balances()[0]['Balance'] == 10.0
|
assert get_balances()['1ST']['total'] == 10.0
|
||||||
assert get_balances()[0]['Available'] == 10.0
|
assert get_balances()['1ST']['used'] == 0.0
|
||||||
assert get_balances()[0]['Pending'] == 0.0
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_balances()
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_balances()
|
||||||
|
|
||||||
|
|
||||||
# This test is somewhat redundant with
|
# This test is somewhat redundant with
|
||||||
@ -175,57 +255,117 @@ def test_get_balances_prod(default_conf, mocker):
|
|||||||
def test_get_ticker(default_conf, mocker):
|
def test_get_ticker(default_conf, mocker):
|
||||||
maybe_init_api(default_conf, mocker)
|
maybe_init_api(default_conf, mocker)
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
|
tick = {
|
||||||
api_mock.get_ticker = MagicMock(return_value=tick)
|
'symbol': 'ETH/BTC',
|
||||||
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
|
'bid': 0.00001098,
|
||||||
|
'ask': 0.00001099,
|
||||||
|
'last': 0.0001,
|
||||||
|
}
|
||||||
|
api_mock.fetch_ticker = MagicMock(return_value=tick)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# retrieve original ticker
|
# retrieve original ticker
|
||||||
ticker = get_ticker(pair='BTC_ETH')
|
ticker = get_ticker(pair='ETH/BTC')
|
||||||
assert ticker['bid'] == 0.00001098
|
assert ticker['bid'] == 0.00001098
|
||||||
assert ticker['ask'] == 0.00001099
|
assert ticker['ask'] == 0.00001099
|
||||||
|
|
||||||
# change the ticker
|
# change the ticker
|
||||||
tick = {"success": True, 'result': {"Bid": 0.5, "Ask": 1, "Last": 42}}
|
tick = {
|
||||||
api_mock.get_ticker = MagicMock(return_value=tick)
|
'symbol': 'ETH/BTC',
|
||||||
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
|
'bid': 0.5,
|
||||||
|
'ask': 1,
|
||||||
|
'last': 42,
|
||||||
|
}
|
||||||
|
api_mock.fetch_ticker = MagicMock(return_value=tick)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# if not caching the result we should get the same ticker
|
# if not caching the result we should get the same ticker
|
||||||
# if not fetching a new result we should get the cached ticker
|
# if not fetching a new result we should get the cached ticker
|
||||||
ticker = get_ticker(pair='BTC_ETH', refresh=False)
|
ticker = get_ticker(pair='ETH/BTC', refresh=False)
|
||||||
assert ticker['bid'] == 0.00001098
|
assert ticker['bid'] == 0.00001098
|
||||||
assert ticker['ask'] == 0.00001099
|
assert ticker['ask'] == 0.00001099
|
||||||
|
|
||||||
# force ticker refresh
|
# force ticker refresh
|
||||||
ticker = get_ticker(pair='BTC_ETH', refresh=True)
|
ticker = get_ticker(pair='ETH/BTC', refresh=True)
|
||||||
assert ticker['bid'] == 0.5
|
assert ticker['bid'] == 0.5
|
||||||
assert ticker['ask'] == 1
|
assert ticker['ask'] == 1
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_ticker(pair='ETH/BTC', refresh=True)
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
get_ticker(pair='ETH/BTC', refresh=True)
|
||||||
|
|
||||||
|
|
||||||
def test_get_ticker_history(default_conf, mocker):
|
def test_get_ticker_history(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
tick = 123
|
tick = [
|
||||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
[
|
||||||
|
1511686200000, # unix timestamp ms
|
||||||
|
1, # open
|
||||||
|
2, # high
|
||||||
|
3, # low
|
||||||
|
4, # close
|
||||||
|
5, # volume (in quote currency)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
has = PropertyMock(return_value={'fetchOHLCV': True})
|
||||||
|
type(api_mock).has = has
|
||||||
|
api_mock.fetch_ohlcv = MagicMock(return_value=tick)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# retrieve original ticker
|
# retrieve original ticker
|
||||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
|
||||||
assert ticks == 123
|
assert ticks[0]['O'] == 1
|
||||||
|
assert ticks[0]['H'] == 2
|
||||||
|
assert ticks[0]['L'] == 3
|
||||||
|
assert ticks[0]['C'] == 4
|
||||||
|
assert ticks[0]['V'] == 5
|
||||||
|
|
||||||
# change the ticker
|
# change the ticker
|
||||||
tick = 999
|
new_tick = [
|
||||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
[
|
||||||
|
1511686200000, # unix timestamp ms
|
||||||
|
6, # open
|
||||||
|
7, # high
|
||||||
|
8, # low
|
||||||
|
9, # close
|
||||||
|
10, # volume (in quote currency)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
api_mock.get_ticker_history = MagicMock(return_value=new_tick)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
|
||||||
# ensure caching will still return the original ticker
|
# ensure caching will still return the original ticker
|
||||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
|
||||||
assert ticks == 123
|
assert ticks[0]['O'] == 1
|
||||||
|
assert ticks[0]['H'] == 2
|
||||||
|
assert ticks[0]['L'] == 3
|
||||||
|
assert ticks[0]['C'] == 4
|
||||||
|
assert ticks[0]['V'] == 5
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
# new symbol to get around cache
|
||||||
|
get_ticker_history('ABCD/BTC', default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
# new symbol to get around cache
|
||||||
|
get_ticker_history('EFGH/BTC', default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_cancel_order_dry_run(default_conf, mocker):
|
def test_cancel_order_dry_run(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
|
|
||||||
assert cancel_order(order_id='123') is None
|
assert cancel_order(order_id='123', pair='TKN/BTC') is None
|
||||||
|
|
||||||
|
|
||||||
# Ensure that if not dry_run, we should call API
|
# Ensure that if not dry_run, we should call API
|
||||||
@ -235,7 +375,22 @@ def test_cancel_order(default_conf, mocker):
|
|||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.cancel_order = MagicMock(return_value=123)
|
api_mock.cancel_order = MagicMock(return_value=123)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
assert cancel_order(order_id='_') == 123
|
assert cancel_order(order_id='_', pair='TKN/BTC') == 123
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_get_order(default_conf, mocker):
|
def test_get_order(default_conf, mocker):
|
||||||
@ -244,44 +399,63 @@ def test_get_order(default_conf, mocker):
|
|||||||
order = MagicMock()
|
order = MagicMock()
|
||||||
order.myid = 123
|
order.myid = 123
|
||||||
exchange._DRY_RUN_OPEN_ORDERS['X'] = order
|
exchange._DRY_RUN_OPEN_ORDERS['X'] = order
|
||||||
print(exchange.get_order('X'))
|
print(exchange.get_order('X', 'TKN/BTC'))
|
||||||
assert exchange.get_order('X').myid == 123
|
assert exchange.get_order('X', 'TKN/BTC').myid == 123
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.get_order = MagicMock(return_value=456)
|
api_mock.fetch_order = MagicMock(return_value=456)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
assert exchange.get_order('X') == 456
|
assert exchange.get_order('X', 'TKN/BTC') == 456
|
||||||
|
|
||||||
|
with pytest.raises(NetworkException):
|
||||||
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException):
|
||||||
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException):
|
||||||
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_get_name(default_conf, mocker):
|
def test_get_name(default_conf, mocker):
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||||
side_effect=lambda s: True)
|
side_effect=lambda s: True)
|
||||||
default_conf['exchange']['name'] = 'bittrex'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
init(default_conf)
|
init(default_conf)
|
||||||
|
|
||||||
assert get_name() == 'Bittrex'
|
assert get_name() == 'Binance'
|
||||||
|
|
||||||
|
|
||||||
def test_get_fee(default_conf, mocker):
|
def test_get_fee(default_conf, mocker):
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
|
||||||
side_effect=lambda s: True)
|
|
||||||
init(default_conf)
|
|
||||||
|
|
||||||
assert get_fee() == 0.0025
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_misc(mocker):
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
|
api_mock.calculate_fee = MagicMock(return_value={
|
||||||
|
'type': 'taker',
|
||||||
|
'currency': 'BTC',
|
||||||
|
'rate': 0.025,
|
||||||
|
'cost': 0.05
|
||||||
|
})
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
exchange.get_markets()
|
assert get_fee() == 0.025
|
||||||
assert api_mock.get_markets.call_count == 1
|
|
||||||
exchange.get_market_summaries()
|
# TODO: disable until caching implemented
|
||||||
assert api_mock.get_market_summaries.call_count == 1
|
# def test_exchange_misc(mocker):
|
||||||
api_mock.name = 123
|
# api_mock = MagicMock()
|
||||||
assert exchange.get_name() == 123
|
# mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
api_mock.fee = 456
|
# exchange.get_markets()
|
||||||
assert exchange.get_fee() == 456
|
# assert api_mock.get_markets.call_count == 1
|
||||||
exchange.get_wallet_health()
|
# exchange.get_market_summaries()
|
||||||
assert api_mock.get_wallet_health.call_count == 1
|
# assert api_mock.get_market_summaries.call_count == 1
|
||||||
|
# api_mock.name = 123
|
||||||
|
# assert exchange.get_name() == 123
|
||||||
|
# api_mock.fee = 456
|
||||||
|
# assert exchange.get_fee() == 456
|
||||||
|
# exchange.get_wallet_health()
|
||||||
|
# assert api_mock.get_wallet_health.call_count == 1
|
||||||
|
@ -1,347 +0,0 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103, protected-access, unused-argument
|
|
||||||
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
import pytest
|
|
||||||
from requests.exceptions import ContentDecodingError
|
|
||||||
from freqtrade.exchange.bittrex import Bittrex
|
|
||||||
import freqtrade.exchange.bittrex as btx
|
|
||||||
|
|
||||||
|
|
||||||
# Eat this flake8
|
|
||||||
# +------------------+
|
|
||||||
# | bittrex.Bittrex |
|
|
||||||
# +------------------+
|
|
||||||
# |
|
|
||||||
# (mock Fake_bittrex)
|
|
||||||
# |
|
|
||||||
# +-----------------------------+
|
|
||||||
# | freqtrade.exchange.Bittrex |
|
|
||||||
# +-----------------------------+
|
|
||||||
# Call into Bittrex will flow up to the
|
|
||||||
# external package bittrex.Bittrex.
|
|
||||||
# By inserting a mock, we redirect those
|
|
||||||
# calls.
|
|
||||||
# The faked bittrex API is called just 'fb'
|
|
||||||
# The freqtrade.exchange.Bittrex is a
|
|
||||||
# wrapper, and is called 'wb'
|
|
||||||
|
|
||||||
|
|
||||||
def _stub_config():
|
|
||||||
return {'key': '',
|
|
||||||
'secret': ''}
|
|
||||||
|
|
||||||
|
|
||||||
class FakeBittrex():
|
|
||||||
def __init__(self, success=True):
|
|
||||||
self.success = True # Believe in yourself
|
|
||||||
self.result = None
|
|
||||||
self.get_ticker_call_count = 0
|
|
||||||
# This is really ugly, doing side-effect during instance creation
|
|
||||||
# But we're allowed to in testing-code
|
|
||||||
btx._API = MagicMock()
|
|
||||||
btx._API.buy_limit = self.fake_buysell_limit
|
|
||||||
btx._API.sell_limit = self.fake_buysell_limit
|
|
||||||
btx._API.get_balance = self.fake_get_balance
|
|
||||||
btx._API.get_balances = self.fake_get_balances
|
|
||||||
btx._API.get_ticker = self.fake_get_ticker
|
|
||||||
btx._API.get_order = self.fake_get_order
|
|
||||||
btx._API.cancel = self.fake_cancel_order
|
|
||||||
btx._API.get_markets = self.fake_get_markets
|
|
||||||
btx._API.get_market_summaries = self.fake_get_market_summaries
|
|
||||||
btx._API_V2 = MagicMock()
|
|
||||||
btx._API_V2.get_candles = self.fake_get_candles
|
|
||||||
btx._API_V2.get_wallet_health = self.fake_get_wallet_health
|
|
||||||
|
|
||||||
def fake_buysell_limit(self, pair, amount, limit):
|
|
||||||
return {'success': self.success,
|
|
||||||
'result': {'uuid': '1234'},
|
|
||||||
'message': 'barter'}
|
|
||||||
|
|
||||||
def fake_get_balance(self, cur):
|
|
||||||
return {'success': self.success,
|
|
||||||
'result': {'Balance': 1234},
|
|
||||||
'message': 'unbalanced'}
|
|
||||||
|
|
||||||
def fake_get_balances(self):
|
|
||||||
return {'success': self.success,
|
|
||||||
'result': [{'BTC_ETH': 1234}],
|
|
||||||
'message': 'no balances'}
|
|
||||||
|
|
||||||
def fake_get_ticker(self, pair):
|
|
||||||
self.get_ticker_call_count += 1
|
|
||||||
return self.result or {'success': self.success,
|
|
||||||
'result': {'Bid': 1, 'Ask': 1, 'Last': 1},
|
|
||||||
'message': 'NO_API_RESPONSE'}
|
|
||||||
|
|
||||||
def fake_get_candles(self, pair, interval):
|
|
||||||
return self.result or {'success': self.success,
|
|
||||||
'result': [{'C': 0, 'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}],
|
|
||||||
'message': 'candles lit'}
|
|
||||||
|
|
||||||
def fake_get_order(self, uuid):
|
|
||||||
return {'success': self.success,
|
|
||||||
'result': {'OrderUuid': 'ABC123',
|
|
||||||
'Type': 'Type',
|
|
||||||
'Exchange': 'BTC_ETH',
|
|
||||||
'Opened': True,
|
|
||||||
'PricePerUnit': 1,
|
|
||||||
'Quantity': 1,
|
|
||||||
'QuantityRemaining': 1,
|
|
||||||
'Closed': True},
|
|
||||||
'message': 'lost'}
|
|
||||||
|
|
||||||
def fake_cancel_order(self, uuid):
|
|
||||||
return self.result or {'success': self.success,
|
|
||||||
'message': 'no such order'}
|
|
||||||
|
|
||||||
def fake_get_markets(self):
|
|
||||||
return self.result or {'success': self.success,
|
|
||||||
'message': 'market gone',
|
|
||||||
'result': [{'MarketName': '-_'}]}
|
|
||||||
|
|
||||||
def fake_get_market_summaries(self):
|
|
||||||
return self.result or {'success': self.success,
|
|
||||||
'message': 'no summary',
|
|
||||||
'result': ['sum']}
|
|
||||||
|
|
||||||
def fake_get_wallet_health(self):
|
|
||||||
return self.result or {'success': self.success,
|
|
||||||
'message': 'bad health',
|
|
||||||
'result': [{'Health': {'Currency': 'BTC_ETH',
|
|
||||||
'IsActive': True,
|
|
||||||
'LastChecked': 0},
|
|
||||||
'Currency': {'Notice': True}}]}
|
|
||||||
|
|
||||||
|
|
||||||
# The freqtrade.exchange.bittrex is called wrap_bittrex
|
|
||||||
# to not confuse naming with bittrex.bittrex
|
|
||||||
def make_wrap_bittrex():
|
|
||||||
conf = _stub_config()
|
|
||||||
wb = btx.Bittrex(conf)
|
|
||||||
return wb
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_class():
|
|
||||||
conf = _stub_config()
|
|
||||||
b = Bittrex(conf)
|
|
||||||
assert isinstance(b, Bittrex)
|
|
||||||
slots = dir(b)
|
|
||||||
for name in ['fee', 'buy', 'sell', 'get_balance', 'get_balances',
|
|
||||||
'get_ticker', 'get_ticker_history', 'get_order',
|
|
||||||
'cancel_order', 'get_pair_detail_url', 'get_markets',
|
|
||||||
'get_market_summaries', 'get_wallet_health']:
|
|
||||||
assert name in slots
|
|
||||||
# FIX: ensure that the slot is also a method in the class
|
|
||||||
# getattr(b, name) => bound method Bittrex.buy
|
|
||||||
# type(getattr(b, name)) => class 'method'
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_fee():
|
|
||||||
fee = Bittrex.fee.__get__(Bittrex)
|
|
||||||
assert fee >= 0 and fee < 0.1 # Fee is 0-10 %
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_buy_good():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
uuid = wb.buy('BTC_ETH', 1, 1)
|
|
||||||
assert uuid == fb.fake_buysell_limit(1, 2, 3)['result']['uuid']
|
|
||||||
|
|
||||||
fb.success = False
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'barter.*'):
|
|
||||||
wb.buy('BAD', 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_sell_good():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
uuid = wb.sell('BTC_ETH', 1, 1)
|
|
||||||
assert uuid == fb.fake_buysell_limit(1, 2, 3)['result']['uuid']
|
|
||||||
|
|
||||||
fb.success = False
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'barter.*'):
|
|
||||||
uuid = wb.sell('BAD', 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_get_balance():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
bal = wb.get_balance('BTC_ETH')
|
|
||||||
assert bal == fb.fake_get_balance(1)['result']['Balance']
|
|
||||||
|
|
||||||
fb.success = False
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'unbalanced'):
|
|
||||||
wb.get_balance('BTC_ETH')
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_get_balances():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
bals = wb.get_balances()
|
|
||||||
assert bals == fb.fake_get_balances()['result']
|
|
||||||
|
|
||||||
fb.success = False
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'no balances'):
|
|
||||||
wb.get_balances()
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_get_ticker():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
|
|
||||||
# Poll ticker, which updates the cache
|
|
||||||
tick = wb.get_ticker('BTC_ETH')
|
|
||||||
for x in ['bid', 'ask', 'last']:
|
|
||||||
assert x in tick
|
|
||||||
# Ensure the side-effect was made (update the ticker cache)
|
|
||||||
assert 'BTC_ETH' in wb.cached_ticker.keys()
|
|
||||||
|
|
||||||
# taint the cache, so we can recognize the cache wall utilized
|
|
||||||
wb.cached_ticker['BTC_ETH']['bid'] = 1234
|
|
||||||
# Poll again, getting the cached result
|
|
||||||
fb.get_ticker_call_count = 0
|
|
||||||
tick = wb.get_ticker('BTC_ETH', False)
|
|
||||||
# Ensure the result was from the cache, and that we didn't call exchange
|
|
||||||
assert wb.cached_ticker['BTC_ETH']['bid'] == 1234
|
|
||||||
assert fb.get_ticker_call_count == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_get_ticker_bad():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
fb.result = {'success': True, 'result': {'Bid': 1, 'Ask': 0}} # incomplete result
|
|
||||||
|
|
||||||
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
|
|
||||||
wb.get_ticker('BTC_ETH')
|
|
||||||
fb.result = {'success': False, 'message': 'gone bad'}
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
|
|
||||||
wb.get_ticker('BTC_ETH')
|
|
||||||
|
|
||||||
fb.result = {'success': True, 'result': {}} # incomplete result
|
|
||||||
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
|
|
||||||
wb.get_ticker('BTC_ETH')
|
|
||||||
fb.result = {'success': False, 'message': 'gone bad'}
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
|
|
||||||
wb.get_ticker('BTC_ETH')
|
|
||||||
|
|
||||||
fb.result = {'success': True,
|
|
||||||
'result': {'Bid': 1, 'Ask': 0, 'Last': None}} # incomplete result
|
|
||||||
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
|
|
||||||
wb.get_ticker('BTC_ETH')
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_get_ticker_history_intervals():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
FakeBittrex()
|
|
||||||
for tick_interval in [1, 5, 30, 60, 1440]:
|
|
||||||
assert ([{'C': 0, 'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}] ==
|
|
||||||
wb.get_ticker_history('BTC_ETH', tick_interval))
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_get_ticker_history():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
assert wb.get_ticker_history('BTC_ETH', 5)
|
|
||||||
with pytest.raises(ValueError, match=r'.*Cannot parse tick_interval.*'):
|
|
||||||
wb.get_ticker_history('BTC_ETH', 2)
|
|
||||||
|
|
||||||
fb.success = False
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'candles lit.*'):
|
|
||||||
wb.get_ticker_history('BTC_ETH', 5)
|
|
||||||
|
|
||||||
fb.success = True
|
|
||||||
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex.*'):
|
|
||||||
fb.result = {'bad': 0}
|
|
||||||
wb.get_ticker_history('BTC_ETH', 5)
|
|
||||||
|
|
||||||
with pytest.raises(ContentDecodingError, match=r'.*Required property C not present.*'):
|
|
||||||
fb.result = {'success': True,
|
|
||||||
'result': [{'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}], # close is missing
|
|
||||||
'message': 'candles lit'}
|
|
||||||
wb.get_ticker_history('BTC_ETH', 5)
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_get_order():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
order = wb.get_order('someUUID')
|
|
||||||
assert order['id'] == 'ABC123'
|
|
||||||
fb.success = False
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'lost'):
|
|
||||||
wb.get_order('someUUID')
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_bittrex_cancel_order():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
wb.cancel_order('someUUID')
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'no such order'):
|
|
||||||
fb.success = False
|
|
||||||
wb.cancel_order('someUUID')
|
|
||||||
# Note: this can be a bug in exchange.bittrex._validate_response
|
|
||||||
with pytest.raises(KeyError):
|
|
||||||
fb.result = {'success': False} # message is missing!
|
|
||||||
wb.cancel_order('someUUID')
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'foo'):
|
|
||||||
fb.result = {'success': False, 'message': 'foo'}
|
|
||||||
wb.cancel_order('someUUID')
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_get_pair_detail_url():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
assert wb.get_pair_detail_url('BTC_ETH')
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_get_markets():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
x = wb.get_markets()
|
|
||||||
assert x == ['__']
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'market gone'):
|
|
||||||
fb.success = False
|
|
||||||
wb.get_markets()
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_get_market_summaries():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
assert ['sum'] == wb.get_market_summaries()
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'no summary'):
|
|
||||||
fb.success = False
|
|
||||||
wb.get_market_summaries()
|
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_get_wallet_health():
|
|
||||||
wb = make_wrap_bittrex()
|
|
||||||
fb = FakeBittrex()
|
|
||||||
x = wb.get_wallet_health()
|
|
||||||
assert x[0]['Currency'] == 'BTC_ETH'
|
|
||||||
with pytest.raises(btx.OperationalException, match=r'bad health'):
|
|
||||||
fb.success = False
|
|
||||||
wb.get_wallet_health()
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_response_success():
|
|
||||||
response = {
|
|
||||||
'message': '',
|
|
||||||
'result': [],
|
|
||||||
}
|
|
||||||
Bittrex._validate_response(response)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_response_no_api_response():
|
|
||||||
response = {
|
|
||||||
'message': 'NO_API_RESPONSE',
|
|
||||||
'result': None,
|
|
||||||
}
|
|
||||||
with pytest.raises(ContentDecodingError, match=r'.*NO_API_RESPONSE.*'):
|
|
||||||
Bittrex._validate_response(response)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_response_min_trade_requirement_not_met():
|
|
||||||
response = {
|
|
||||||
'message': 'MIN_TRADE_REQUIREMENT_NOT_MET',
|
|
||||||
'result': None,
|
|
||||||
}
|
|
||||||
with pytest.raises(ContentDecodingError, match=r'.*MIN_TRADE_REQUIREMENT_NOT_MET.*'):
|
|
||||||
Bittrex._validate_response(response)
|
|
@ -1,11 +1,11 @@
|
|||||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103
|
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import ccxt
|
||||||
import math
|
import math
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from freqtrade import exchange, optimize
|
from freqtrade import exchange, optimize
|
||||||
from freqtrade.exchange import Bittrex
|
|
||||||
from freqtrade.optimize import preprocess
|
from freqtrade.optimize import preprocess
|
||||||
from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe
|
from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe
|
||||||
import freqtrade.optimize.backtesting as backtesting
|
import freqtrade.optimize.backtesting as backtesting
|
||||||
@ -21,7 +21,7 @@ def trim_dictlist(dict_list, num):
|
|||||||
def test_generate_text_table():
|
def test_generate_text_table():
|
||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'currency': ['BTC_ETH', 'BTC_ETH'],
|
'currency': ['ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2],
|
'profit_percent': [0.1, 0.2],
|
||||||
'profit_BTC': [0.2, 0.4],
|
'profit_BTC': [0.2, 0.4],
|
||||||
'duration': [10, 30],
|
'duration': [10, 30],
|
||||||
@ -29,17 +29,17 @@ def test_generate_text_table():
|
|||||||
'loss': [0, 0]
|
'loss': [0, 0]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
print(generate_text_table({'BTC_ETH': {}}, results, 'BTC', 5))
|
print(generate_text_table({'ETH/BTC': {}}, results, 'BTC', '5m'))
|
||||||
assert generate_text_table({'BTC_ETH': {}}, results, 'BTC', 5) == (
|
assert generate_text_table({'ETH/BTC': {}}, results, 'BTC', '5m') == (
|
||||||
'pair buy count avg profit % total profit BTC avg duration profit loss\n' # noqa
|
'pair buy count avg profit % total profit BTC avg duration profit loss\n' # noqa
|
||||||
'------- ----------- -------------- ------------------ -------------- -------- ------\n' # noqa
|
'------- ----------- -------------- ------------------ -------------- -------- ------\n' # noqa
|
||||||
'BTC_ETH 2 15.00 0.60000000 100.0 2 0\n' # noqa
|
'ETH/BTC 2 15.00 0.60000000 100.0 2 0\n' # noqa
|
||||||
'TOTAL 2 15.00 0.60000000 100.0 2 0') # noqa
|
'TOTAL 2 15.00 0.60000000 100.0 2 0') # noqa
|
||||||
|
|
||||||
|
|
||||||
def test_get_timeframe(default_strategy):
|
def test_get_timeframe(default_strategy):
|
||||||
data = preprocess(optimize.load_data(
|
data = preprocess(optimize.load_data(
|
||||||
None, ticker_interval=1, pairs=['BTC_UNITEST']))
|
None, ticker_interval='1m', pairs=['UNITTEST/BTC']))
|
||||||
min_date, max_date = get_timeframe(data)
|
min_date, max_date = get_timeframe(data)
|
||||||
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
|
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
|
||||||
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
|
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
|
||||||
@ -47,9 +47,11 @@ def test_get_timeframe(default_strategy):
|
|||||||
|
|
||||||
def test_backtest(default_strategy, default_conf, mocker):
|
def test_backtest(default_strategy, default_conf, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
mocker.patch.multiple('freqtrade.optimize.backtesting.exchange',
|
||||||
|
get_fee=MagicMock(return_value=0.0025))
|
||||||
|
exchange._API = ccxt.binance()
|
||||||
|
|
||||||
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
|
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
|
||||||
data = trim_dictlist(data, -200)
|
data = trim_dictlist(data, -200)
|
||||||
results = backtest({'stake_amount': default_conf['stake_amount'],
|
results = backtest({'stake_amount': default_conf['stake_amount'],
|
||||||
'processed': optimize.preprocess(data),
|
'processed': optimize.preprocess(data),
|
||||||
@ -60,10 +62,12 @@ def test_backtest(default_strategy, default_conf, mocker):
|
|||||||
|
|
||||||
def test_backtest_1min_ticker_interval(default_strategy, default_conf, mocker):
|
def test_backtest_1min_ticker_interval(default_strategy, default_conf, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
mocker.patch.multiple('freqtrade.optimize.backtesting.exchange',
|
||||||
|
get_fee=MagicMock(return_value=0.0025))
|
||||||
|
exchange._API = ccxt.binance()
|
||||||
|
|
||||||
# Run a backtesting for an exiting 5min ticker_interval
|
# Run a backtesting for an exiting 5min ticker_interval
|
||||||
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'])
|
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||||
data = trim_dictlist(data, -200)
|
data = trim_dictlist(data, -200)
|
||||||
results = backtest({'stake_amount': default_conf['stake_amount'],
|
results = backtest({'stake_amount': default_conf['stake_amount'],
|
||||||
'processed': optimize.preprocess(data),
|
'processed': optimize.preprocess(data),
|
||||||
@ -74,39 +78,37 @@ def test_backtest_1min_ticker_interval(default_strategy, default_conf, mocker):
|
|||||||
|
|
||||||
def load_data_test(what):
|
def load_data_test(what):
|
||||||
timerange = ((None, 'line'), None, -100)
|
timerange = ((None, 'line'), None, -100)
|
||||||
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'], timerange=timerange)
|
data = optimize.load_data(None, ticker_interval='1m',
|
||||||
pair = data['BTC_UNITEST']
|
pairs=['UNITTEST/BTC'], timerange=timerange)
|
||||||
|
pair = data['UNITTEST/BTC']
|
||||||
datalen = len(pair)
|
datalen = len(pair)
|
||||||
# Depending on the what parameter we now adjust the
|
# Depending on the what parameter we now adjust the
|
||||||
# loaded data looks:
|
# loaded data looks:
|
||||||
# pair :: [{'O': 0.123, 'H': 0.123, 'L': 0.123,
|
# pair :: [{'O': 0.123, 'H': 0.123, 'L': 0.123,
|
||||||
# 'C': 0.123, 'V': 123.123,
|
# 'C': 0.123, 'V': 123.123,
|
||||||
# 'T': '2017-11-04T23:02:00', 'BV': 0.123}]
|
# 'T': '2017-11-04T23:02:00.000000'}]
|
||||||
base = 0.001
|
base = 0.001
|
||||||
if what == 'raise':
|
if what == 'raise':
|
||||||
return {'BTC_UNITEST':
|
return {'UNITTEST/BTC':
|
||||||
[{'T': pair[x]['T'], # Keep old dates
|
[{'T': pair[x]['T'], # Keep old dates
|
||||||
'V': pair[x]['V'], # Keep old volume
|
'V': pair[x]['V'], # Keep old volume
|
||||||
'BV': pair[x]['BV'], # keep too
|
|
||||||
'O': x * base, # But replace O,H,L,C
|
'O': x * base, # But replace O,H,L,C
|
||||||
'H': x * base + 0.0001,
|
'H': x * base + 0.0001,
|
||||||
'L': x * base - 0.0001,
|
'L': x * base - 0.0001,
|
||||||
'C': x * base} for x in range(0, datalen)]}
|
'C': x * base} for x in range(0, datalen)]}
|
||||||
if what == 'lower':
|
if what == 'lower':
|
||||||
return {'BTC_UNITEST':
|
return {'UNITTEST/BTC':
|
||||||
[{'T': pair[x]['T'], # Keep old dates
|
[{'T': pair[x]['T'], # Keep old dates
|
||||||
'V': pair[x]['V'], # Keep old volume
|
'V': pair[x]['V'], # Keep old volume
|
||||||
'BV': pair[x]['BV'], # keep too
|
|
||||||
'O': 1 - x * base, # But replace O,H,L,C
|
'O': 1 - x * base, # But replace O,H,L,C
|
||||||
'H': 1 - x * base + 0.0001,
|
'H': 1 - x * base + 0.0001,
|
||||||
'L': 1 - x * base - 0.0001,
|
'L': 1 - x * base - 0.0001,
|
||||||
'C': 1 - x * base} for x in range(0, datalen)]}
|
'C': 1 - x * base} for x in range(0, datalen)]}
|
||||||
if what == 'sine':
|
if what == 'sine':
|
||||||
hz = 0.1 # frequency
|
hz = 0.1 # frequency
|
||||||
return {'BTC_UNITEST':
|
return {'UNITTEST/BTC':
|
||||||
[{'T': pair[x]['T'], # Keep old dates
|
[{'T': pair[x]['T'], # Keep old dates
|
||||||
'V': pair[x]['V'], # Keep old volume
|
'V': pair[x]['V'], # Keep old volume
|
||||||
'BV': pair[x]['BV'], # keep too
|
|
||||||
# But replace O,H,L,C
|
# But replace O,H,L,C
|
||||||
'O': math.sin(x * hz) / 1000 + base,
|
'O': math.sin(x * hz) / 1000 + base,
|
||||||
'H': math.sin(x * hz) / 1000 + base + 0.0001,
|
'H': math.sin(x * hz) / 1000 + base + 0.0001,
|
||||||
@ -127,26 +129,11 @@ def simple_backtest(config, contour, num_results):
|
|||||||
assert len(results) == num_results
|
assert len(results) == num_results
|
||||||
|
|
||||||
|
|
||||||
# Test backtest on offline data
|
|
||||||
# loaded by freqdata/optimize/__init__.py::load_data()
|
|
||||||
|
|
||||||
|
|
||||||
def test_backtest2(default_conf, mocker, default_strategy):
|
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
|
||||||
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
|
|
||||||
data = trim_dictlist(data, -200)
|
|
||||||
results = backtest({'stake_amount': default_conf['stake_amount'],
|
|
||||||
'processed': optimize.preprocess(data),
|
|
||||||
'max_open_trades': 10,
|
|
||||||
'realistic': True})
|
|
||||||
assert not results.empty
|
|
||||||
|
|
||||||
|
|
||||||
def test_processed(default_conf, mocker, default_strategy):
|
def test_processed(default_conf, mocker, default_strategy):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
dict_of_tickerrows = load_data_test('raise')
|
dict_of_tickerrows = load_data_test('raise')
|
||||||
dataframes = optimize.preprocess(dict_of_tickerrows)
|
dataframes = optimize.preprocess(dict_of_tickerrows)
|
||||||
dataframe = dataframes['BTC_UNITEST']
|
dataframe = dataframes['UNITTEST/BTC']
|
||||||
cols = dataframe.columns
|
cols = dataframe.columns
|
||||||
# assert the dataframe got some of the indicator columns
|
# assert the dataframe got some of the indicator columns
|
||||||
for col in ['close', 'high', 'low', 'open', 'date',
|
for col in ['close', 'high', 'low', 'open', 'date',
|
||||||
@ -156,25 +143,27 @@ def test_processed(default_conf, mocker, default_strategy):
|
|||||||
|
|
||||||
def test_backtest_pricecontours(default_conf, mocker, default_strategy):
|
def test_backtest_pricecontours(default_conf, mocker, default_strategy):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
mocker.patch.multiple('freqtrade.optimize.backtesting.exchange',
|
||||||
|
get_fee=MagicMock(return_value=0.0025))
|
||||||
tests = [['raise', 17], ['lower', 0], ['sine', 17]]
|
tests = [['raise', 17], ['lower', 0], ['sine', 17]]
|
||||||
for [contour, numres] in tests:
|
for [contour, numres] in tests:
|
||||||
simple_backtest(default_conf, contour, numres)
|
simple_backtest(default_conf, contour, numres)
|
||||||
|
|
||||||
|
|
||||||
def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None):
|
def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None):
|
||||||
tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1, timerange=timerange)
|
tickerdata = optimize.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||||
pairdata = {'BTC_UNITEST': tickerdata}
|
pairdata = {'UNITTEST/BTC': tickerdata}
|
||||||
return pairdata
|
return pairdata
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_start(default_conf, mocker, caplog):
|
def test_backtest_start(default_conf, mocker, caplog):
|
||||||
default_conf['exchange']['pair_whitelist'] = ['BTC_UNITEST']
|
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch('freqtrade.misc.load_config', new=lambda s: default_conf)
|
mocker.patch('freqtrade.misc.load_config', new=lambda s: default_conf)
|
||||||
mocker.patch.multiple('freqtrade.optimize',
|
mocker.patch.multiple('freqtrade.optimize',
|
||||||
load_data=mocked_load_data)
|
load_data=mocked_load_data)
|
||||||
args = MagicMock()
|
args = MagicMock()
|
||||||
args.ticker_interval = 1
|
args.ticker_interval = '1m'
|
||||||
args.level = 10
|
args.level = 10
|
||||||
args.live = False
|
args.live = False
|
||||||
args.datadir = None
|
args.datadir = None
|
||||||
|
@ -3,14 +3,15 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import ccxt
|
||||||
import uuid
|
import uuid
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
from freqtrade import exchange, optimize
|
from freqtrade import exchange, optimize
|
||||||
from freqtrade.exchange import Bittrex
|
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \
|
||||||
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs,\
|
|
||||||
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, file_dump_json
|
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, file_dump_json
|
||||||
|
|
||||||
# Change this if modifying BTC_UNITEST testdatafile
|
# Change this if modifying UNITTEST/BTC testdatafile
|
||||||
_BTC_UNITTEST_LENGTH = 13681
|
_BTC_UNITTEST_LENGTH = 13681
|
||||||
|
|
||||||
|
|
||||||
@ -49,15 +50,15 @@ def test_load_data_30min_ticker(default_conf, ticker_history, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance({'key': '', 'secret': ''})
|
||||||
|
|
||||||
file = 'freqtrade/tests/testdata/BTC_UNITTEST-30.json'
|
file = 'freqtrade/tests/testdata/UNITTEST_BTC-30m.json'
|
||||||
_backup_file(file, copy_file=True)
|
_backup_file(file, copy_file=True)
|
||||||
optimize.load_data(None, pairs=['BTC_UNITTEST'], ticker_interval=30)
|
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m')
|
||||||
assert os.path.isfile(file) is True
|
assert os.path.isfile(file) is True
|
||||||
assert ('freqtrade.optimize',
|
assert ('freqtrade.optimize',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
'Download the pair: "BTC_ETH", Interval: 30 min') not in caplog.record_tuples
|
'Downloading the pair: "ETH/BTC", Interval: 30 min') not in caplog.record_tuples
|
||||||
_clean_test_file(file)
|
_clean_test_file(file)
|
||||||
|
|
||||||
|
|
||||||
@ -65,15 +66,15 @@ def test_load_data_5min_ticker(default_conf, ticker_history, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance({'key': '', 'secret': ''})
|
||||||
|
|
||||||
file = 'freqtrade/tests/testdata/BTC_ETH-5.json'
|
file = 'freqtrade/tests/testdata/ETH_BTC-5m.json'
|
||||||
_backup_file(file, copy_file=True)
|
_backup_file(file, copy_file=True)
|
||||||
optimize.load_data(None, pairs=['BTC_ETH'], ticker_interval=5)
|
optimize.load_data(None, pairs=['ETH/BTC'], ticker_interval='5m')
|
||||||
assert os.path.isfile(file) is True
|
assert os.path.isfile(file) is True
|
||||||
assert ('freqtrade.optimize',
|
assert ('freqtrade.optimize',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
'Download the pair: "BTC_ETH", Interval: 5 min') not in caplog.record_tuples
|
'Downloading the pair: "ETH/BTC", Interval: 5 min') not in caplog.record_tuples
|
||||||
_clean_test_file(file)
|
_clean_test_file(file)
|
||||||
|
|
||||||
|
|
||||||
@ -81,15 +82,15 @@ def test_load_data_1min_ticker(default_conf, ticker_history, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance({'key': '', 'secret': ''})
|
||||||
|
|
||||||
file = 'freqtrade/tests/testdata/BTC_ETH-1.json'
|
file = 'freqtrade/tests/testdata/ETH_BTC-1m.json'
|
||||||
_backup_file(file, copy_file=True)
|
_backup_file(file, copy_file=True)
|
||||||
optimize.load_data(None, ticker_interval=1, pairs=['BTC_ETH'])
|
optimize.load_data(None, ticker_interval='1m', pairs=['ETH/BTC'])
|
||||||
assert os.path.isfile(file) is True
|
assert os.path.isfile(file) is True
|
||||||
assert ('freqtrade.optimize',
|
assert ('freqtrade.optimize',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
'Download the pair: "BTC_ETH", Interval: 1 min') not in caplog.record_tuples
|
'Download the pair: "ETH/BTC", Interval: 1 min') not in caplog.record_tuples
|
||||||
_clean_test_file(file)
|
_clean_test_file(file)
|
||||||
|
|
||||||
|
|
||||||
@ -97,15 +98,15 @@ def test_load_data_with_new_pair_1min(default_conf, ticker_history, mocker, capl
|
|||||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance({'key': '', 'secret': ''})
|
||||||
|
|
||||||
file = 'freqtrade/tests/testdata/BTC_MEME-1.json'
|
file = 'freqtrade/tests/testdata/MEME_BTC-1m.json'
|
||||||
_backup_file(file)
|
_backup_file(file)
|
||||||
optimize.load_data(None, ticker_interval=1, pairs=['BTC_MEME'])
|
optimize.load_data(None, ticker_interval='1m', pairs=['MEME/BTC'])
|
||||||
assert os.path.isfile(file) is True
|
assert os.path.isfile(file) is True
|
||||||
assert ('freqtrade.optimize',
|
assert ('freqtrade.optimize',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
'Download the pair: "BTC_MEME", Interval: 1 min') in caplog.record_tuples
|
'Downloading the pair: "MEME/BTC", Interval: 1m') in caplog.record_tuples
|
||||||
_clean_test_file(file)
|
_clean_test_file(file)
|
||||||
|
|
||||||
|
|
||||||
@ -116,12 +117,12 @@ def test_testdata_path():
|
|||||||
def test_download_pairs(default_conf, ticker_history, mocker):
|
def test_download_pairs(default_conf, ticker_history, mocker):
|
||||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance({'key': '', 'secret': ''})
|
||||||
|
|
||||||
file1_1 = 'freqtrade/tests/testdata/BTC_MEME-1.json'
|
file1_1 = 'freqtrade/tests/testdata/MEME_BTC-1m.json'
|
||||||
file1_5 = 'freqtrade/tests/testdata/BTC_MEME-5.json'
|
file1_5 = 'freqtrade/tests/testdata/MEME_BTC-5m.json'
|
||||||
file2_1 = 'freqtrade/tests/testdata/BTC_CFI-1.json'
|
file2_1 = 'freqtrade/tests/testdata/CFI_BTC-1m.json'
|
||||||
file2_5 = 'freqtrade/tests/testdata/BTC_CFI-5.json'
|
file2_5 = 'freqtrade/tests/testdata/CFI_BTC-5m.json'
|
||||||
|
|
||||||
_backup_file(file1_1)
|
_backup_file(file1_1)
|
||||||
_backup_file(file1_5)
|
_backup_file(file1_5)
|
||||||
@ -131,7 +132,7 @@ def test_download_pairs(default_conf, ticker_history, mocker):
|
|||||||
assert os.path.isfile(file1_1) is False
|
assert os.path.isfile(file1_1) is False
|
||||||
assert os.path.isfile(file2_1) is False
|
assert os.path.isfile(file2_1) is False
|
||||||
|
|
||||||
assert download_pairs(None, pairs=['BTC-MEME', 'BTC-CFI'], ticker_interval=1) is True
|
assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='1m') is True
|
||||||
|
|
||||||
assert os.path.isfile(file1_1) is True
|
assert os.path.isfile(file1_1) is True
|
||||||
assert os.path.isfile(file2_1) is True
|
assert os.path.isfile(file2_1) is True
|
||||||
@ -143,7 +144,7 @@ def test_download_pairs(default_conf, ticker_history, mocker):
|
|||||||
assert os.path.isfile(file1_5) is False
|
assert os.path.isfile(file1_5) is False
|
||||||
assert os.path.isfile(file2_5) is False
|
assert os.path.isfile(file2_5) is False
|
||||||
|
|
||||||
assert download_pairs(None, pairs=['BTC-MEME', 'BTC-CFI'], ticker_interval=5) is True
|
assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='5m') is True
|
||||||
|
|
||||||
assert os.path.isfile(file1_5) is True
|
assert os.path.isfile(file1_5) is True
|
||||||
assert os.path.isfile(file2_5) is True
|
assert os.path.isfile(file2_5) is True
|
||||||
@ -158,59 +159,59 @@ def test_download_pairs_exception(default_conf, ticker_history, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata',
|
mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata',
|
||||||
side_effect=BaseException('File Error'))
|
side_effect=BaseException('File Error'))
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance({'key': '', 'secret': ''})
|
||||||
|
|
||||||
file1_1 = 'freqtrade/tests/testdata/BTC_MEME-1.json'
|
file1_1 = 'freqtrade/tests/testdata/MEME_BTC-1m.json'
|
||||||
file1_5 = 'freqtrade/tests/testdata/BTC_MEME-5.json'
|
file1_5 = 'freqtrade/tests/testdata/MEME_BTC-5m.json'
|
||||||
_backup_file(file1_1)
|
_backup_file(file1_1)
|
||||||
_backup_file(file1_5)
|
_backup_file(file1_5)
|
||||||
|
|
||||||
download_pairs(None, pairs=['BTC-MEME'], ticker_interval=1)
|
download_pairs(None, pairs=['MEME/BTC'], ticker_interval='1m')
|
||||||
# clean files freshly downloaded
|
# clean files freshly downloaded
|
||||||
_clean_test_file(file1_1)
|
_clean_test_file(file1_1)
|
||||||
_clean_test_file(file1_5)
|
_clean_test_file(file1_5)
|
||||||
assert ('freqtrade.optimize.__init__',
|
assert ('freqtrade.optimize.__init__',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
'Failed to download the pair: "BTC-MEME", Interval: 1 min') in caplog.record_tuples
|
'Failed to download the pair: "MEME/BTC", Interval: 1m') in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
def test_download_backtesting_testdata(default_conf, ticker_history, mocker):
|
def test_download_backtesting_testdata(default_conf, ticker_history, mocker):
|
||||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.binance({'key': '', 'secret': ''})
|
||||||
|
|
||||||
# Download a 1 min ticker file
|
# Download a 1 min ticker file
|
||||||
file1 = 'freqtrade/tests/testdata/BTC_XEL-1.json'
|
file1 = 'freqtrade/tests/testdata/XEL_BTC-1m.json'
|
||||||
_backup_file(file1)
|
_backup_file(file1)
|
||||||
download_backtesting_testdata(None, pair="BTC-XEL", interval=1)
|
download_backtesting_testdata(None, pair="XEL/BTC", interval='1m')
|
||||||
assert os.path.isfile(file1) is True
|
assert os.path.isfile(file1) is True
|
||||||
_clean_test_file(file1)
|
_clean_test_file(file1)
|
||||||
|
|
||||||
# Download a 5 min ticker file
|
# Download a 5 min ticker file
|
||||||
file2 = 'freqtrade/tests/testdata/BTC_STORJ-5.json'
|
file2 = 'freqtrade/tests/testdata/STORJ_BTC-5m.json'
|
||||||
_backup_file(file2)
|
_backup_file(file2)
|
||||||
|
|
||||||
download_backtesting_testdata(None, pair="BTC-STORJ", interval=5)
|
download_backtesting_testdata(None, pair="STORJ/BTC", interval='5m')
|
||||||
assert os.path.isfile(file2) is True
|
assert os.path.isfile(file2) is True
|
||||||
_clean_test_file(file2)
|
_clean_test_file(file2)
|
||||||
|
|
||||||
|
|
||||||
def test_download_backtesting_testdata2(mocker):
|
def test_download_backtesting_testdata2(mocker):
|
||||||
tick = [{'T': 'bar'}, {'T': 'foo'}]
|
tick = [{'T': 'foo'}, {'T': 'bar'}] # invalid response
|
||||||
mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick)
|
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick)
|
||||||
assert download_backtesting_testdata(None, pair="BTC-UNITEST", interval=1)
|
assert download_backtesting_testdata(None, pair="UNITEST/BTC", interval='1m')
|
||||||
assert download_backtesting_testdata(None, pair="BTC-UNITEST", interval=3)
|
assert download_backtesting_testdata(None, pair="UNITEST/BTC", interval='3m')
|
||||||
|
|
||||||
|
|
||||||
def test_load_tickerdata_file():
|
def test_load_tickerdata_file():
|
||||||
# 7 does not exist in either format.
|
# 7m does not exist in either format.
|
||||||
assert not load_tickerdata_file(None, 'BTC_UNITEST', 7)
|
assert not load_tickerdata_file(None, 'UNITTEST/BTC', '7m')
|
||||||
# 1 exists only as a .json
|
# 1m exists only as a .json
|
||||||
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1)
|
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||||
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
||||||
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
|
# 8m.json is empty and will fail if it's loaded. 8m.json.gz is a copy of 1m.json
|
||||||
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 8)
|
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '8m')
|
||||||
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
|
||||||
|
|
||||||
|
|
||||||
@ -218,19 +219,22 @@ def test_init(default_conf, mocker):
|
|||||||
conf = {'exchange': {'pair_whitelist': []}}
|
conf = {'exchange': {'pair_whitelist': []}}
|
||||||
mocker.patch('freqtrade.optimize.hyperopt_optimize_conf', return_value=conf)
|
mocker.patch('freqtrade.optimize.hyperopt_optimize_conf', return_value=conf)
|
||||||
assert {} == optimize.load_data('', pairs=[], refresh_pairs=True,
|
assert {} == optimize.load_data('', pairs=[], refresh_pairs=True,
|
||||||
ticker_interval=int(default_conf['ticker_interval']))
|
ticker_interval=default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_tickerdata_to_dataframe():
|
def test_tickerdata_to_dataframe():
|
||||||
timerange = ((None, 'line'), None, -100)
|
timerange = ((None, 'line'), None, -100)
|
||||||
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
|
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||||
tickerlist = {'BTC_UNITEST': tick}
|
tickerlist = {'UNITTEST/BTC': tick}
|
||||||
|
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init({})
|
||||||
data = optimize.tickerdata_to_dataframe(tickerlist)
|
data = optimize.tickerdata_to_dataframe(tickerlist)
|
||||||
assert len(data['BTC_UNITEST']) == 100
|
assert len(data['UNITTEST/BTC']) == 100
|
||||||
|
|
||||||
|
|
||||||
def test_trim_tickerlist():
|
def test_trim_tickerlist():
|
||||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file:
|
||||||
ticker_list = json.load(data_file)
|
ticker_list = json.load(data_file)
|
||||||
ticker_list_len = len(ticker_list)
|
ticker_list_len = len(ticker_list)
|
||||||
|
|
||||||
@ -275,7 +279,6 @@ def test_trim_tickerlist():
|
|||||||
|
|
||||||
|
|
||||||
def test_file_dump_json():
|
def test_file_dump_json():
|
||||||
|
|
||||||
file = 'freqtrade/tests/testdata/test_{id}.json'.format(id=str(uuid.uuid4()))
|
file = 'freqtrade/tests/testdata/test_{id}.json'.format(id=str(uuid.uuid4()))
|
||||||
data = {'bar': 'foo'}
|
data = {'bar': 'foo'}
|
||||||
|
|
||||||
|
@ -103,12 +103,12 @@ def test_status_handle(default_conf, update, ticker, mocker):
|
|||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
# Trigger status while we have a fulfilled order for the open trade
|
# Trigger status while we have a fulfilled order for the open trade
|
||||||
_status(bot=MagicMock(), update=update)
|
_status(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '[BTC_ETH]' in msg_mock.call_args_list[0][0][0]
|
assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_status_table_handle(default_conf, update, ticker, mocker):
|
def test_status_table_handle(default_conf, update, ticker, mocker):
|
||||||
@ -124,7 +124,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_order_id'))
|
buy=MagicMock(return_value={'id': 'mocked_order_id'}))
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
_status_table(bot=MagicMock(), update=update)
|
_status_table(bot=MagicMock(), update=update)
|
||||||
@ -139,7 +139,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
|
|||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(15.0, int(default_conf['ticker_interval']))
|
create_trade(15.0, default_conf['ticker_interval'])
|
||||||
|
|
||||||
_status_table(bot=MagicMock(), update=update)
|
_status_table(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
|
|||||||
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
|
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
|
||||||
|
|
||||||
assert int(fields[0]) == 1
|
assert int(fields[0]) == 1
|
||||||
assert fields[1] == 'BTC_ETH'
|
assert fields[1] == 'ETH/BTC'
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@ -164,6 +164,7 @@ def test_profit_handle(
|
|||||||
send_msg=msg_mock)
|
send_msg=msg_mock)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
@ -177,7 +178,7 @@ def test_profit_handle(
|
|||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
|
|
||||||
# Simulate fulfilled LIMIT_BUY order for trade
|
# Simulate fulfilled LIMIT_BUY order for trade
|
||||||
@ -206,7 +207,7 @@ def test_profit_handle(
|
|||||||
assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0]
|
assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
assert '*Best Performing:* `BTC_ETH: 6.20%`' in msg_mock.call_args_list[-1][0][0]
|
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
|
def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
|
||||||
@ -219,12 +220,13 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
|
|||||||
send_msg=MagicMock())
|
send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -232,6 +234,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
|
|||||||
# Increase the price and sell it
|
# Increase the price and sell it
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker_sell_up)
|
get_ticker=ticker_sell_up)
|
||||||
|
|
||||||
update.message.text = '/forcesell 1'
|
update.message.text = '/forcesell 1'
|
||||||
@ -239,7 +242,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
|
|||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0]
|
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -256,16 +259,18 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
|
|||||||
send_msg=MagicMock())
|
send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
# Decrease the price and sell it
|
# Decrease the price and sell it
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker_sell_down)
|
get_ticker=ticker_sell_down)
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
@ -276,7 +281,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
|
|||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -294,9 +299,9 @@ def test_exec_forcesell_open_orders(default_conf, ticker, mocker):
|
|||||||
}),
|
}),
|
||||||
cancel_order=cancel_order_mock)
|
cancel_order=cancel_order_mock)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
open_rate=1,
|
open_rate=1,
|
||||||
exchange='BITTREX',
|
exchange='binance',
|
||||||
open_order_id='123456789',
|
open_order_id='123456789',
|
||||||
amount=1,
|
amount=1,
|
||||||
fee=0.0,
|
fee=0.0,
|
||||||
@ -317,13 +322,14 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker):
|
|||||||
send_msg=MagicMock())
|
send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
for _ in range(4):
|
for _ in range(4):
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
rpc_mock.reset_mock()
|
rpc_mock.reset_mock()
|
||||||
|
|
||||||
update.message.text = '/forcesell all'
|
update.message.text = '/forcesell all'
|
||||||
@ -384,11 +390,12 @@ def test_performance_handle(
|
|||||||
send_msg=msg_mock)
|
send_msg=msg_mock)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
|
|
||||||
@ -403,7 +410,7 @@ def test_performance_handle(
|
|||||||
_performance(bot=MagicMock(), update=update)
|
_performance(bot=MagicMock(), update=update)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'Performance' in msg_mock.call_args_list[0][0][0]
|
assert 'Performance' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '<code>BTC_ETH\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
|
assert '<code>ETH/BTC\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
|
def test_daily_handle(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
|
||||||
@ -417,6 +424,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, limit_sell_
|
|||||||
send_msg=msg_mock)
|
send_msg=msg_mock)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
@ -425,7 +433,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, limit_sell_
|
|||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
|
|
||||||
@ -452,8 +460,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, limit_sell_
|
|||||||
# Reset msg_mock
|
# Reset msg_mock
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
# Add two other trades
|
# Add two other trades
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trades = Trade.query.all()
|
trades = Trade.query.all()
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
@ -516,7 +524,7 @@ def test_count_handle(default_conf, update, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_order_id'))
|
buy=MagicMock(return_value={'id': 'mocked_order_id'}))
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
_count(bot=MagicMock(), update=update)
|
_count(bot=MagicMock(), update=update)
|
||||||
@ -526,7 +534,7 @@ def test_count_handle(default_conf, update, ticker, mocker):
|
|||||||
update_state(State.RUNNING)
|
update_state(State.RUNNING)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
_count(bot=MagicMock(), update=update)
|
_count(bot=MagicMock(), update=update)
|
||||||
|
|
||||||
@ -631,35 +639,31 @@ def test_stop_handle_already_stopped(default_conf, update, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_balance_handle(default_conf, update, mocker):
|
def test_balance_handle(default_conf, update, mocker):
|
||||||
|
mock_balance = {
|
||||||
mock_balance = [{
|
'BTC': {
|
||||||
'Currency': 'BTC',
|
'total': 12.0,
|
||||||
'Balance': 10.0,
|
'free': 12.0,
|
||||||
'Available': 12.0,
|
'used': 0.0,
|
||||||
'Pending': 0.0,
|
},
|
||||||
'CryptoAddress': 'XXXX',
|
'ETH': {
|
||||||
}, {
|
'total': 0.0,
|
||||||
'Currency': 'ETH',
|
'free': 0.0,
|
||||||
'Balance': 0.0,
|
'used': 0.0,
|
||||||
'Available': 0.0,
|
},
|
||||||
'Pending': 0.0,
|
'USDT': {
|
||||||
'CryptoAddress': 'XXXX',
|
'total': 10000.0,
|
||||||
}, {
|
'free': 0.0,
|
||||||
'Currency': 'USDT',
|
'used': 0.0,
|
||||||
'Balance': 10000.0,
|
},
|
||||||
'Available': 0.0,
|
'LTC': {
|
||||||
'Pending': 0.0,
|
'total': 10.0,
|
||||||
'CryptoAddress': 'XXXX',
|
'free': 10.0,
|
||||||
}, {
|
'used': 0.0,
|
||||||
'Currency': 'LTC',
|
}
|
||||||
'Balance': 10.0,
|
}
|
||||||
'Available': 10.0,
|
|
||||||
'Pending': 0.0,
|
|
||||||
'CryptoAddress': 'XXXX',
|
|
||||||
}]
|
|
||||||
|
|
||||||
def mock_ticker(symbol, refresh):
|
def mock_ticker(symbol, refresh):
|
||||||
if symbol == 'USDT_BTC':
|
if symbol == 'BTC/USDT':
|
||||||
return {
|
return {
|
||||||
'bid': 10000.00,
|
'bid': 10000.00,
|
||||||
'ask': 10000.00,
|
'ask': 10000.00,
|
||||||
@ -693,7 +697,7 @@ def test_balance_handle(default_conf, update, mocker):
|
|||||||
assert '*Currency*: USDT' in result
|
assert '*Currency*: USDT' in result
|
||||||
assert 'Balance' in result
|
assert 'Balance' in result
|
||||||
assert 'Est. BTC' in result
|
assert 'Est. BTC' in result
|
||||||
assert '*BTC*: 12.00000000' in result
|
assert '*BTC*: 14.00000000' in result
|
||||||
|
|
||||||
|
|
||||||
def test_zero_balance_handle(default_conf, update, mocker):
|
def test_zero_balance_handle(default_conf, update, mocker):
|
||||||
@ -704,7 +708,7 @@ def test_zero_balance_handle(default_conf, update, mocker):
|
|||||||
init=MagicMock(),
|
init=MagicMock(),
|
||||||
send_msg=msg_mock)
|
send_msg=msg_mock)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
get_balances=MagicMock(return_value=[]))
|
get_balances=MagicMock(return_value={}))
|
||||||
_balance(bot=MagicMock(), update=update)
|
_balance(bot=MagicMock(), update=update)
|
||||||
result = msg_mock.call_args_list[0][0][0]
|
result = msg_mock.call_args_list[0][0][0]
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
|
@ -7,7 +7,7 @@ from freqtrade.analyze import parse_ticker_dataframe
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def result():
|
def result():
|
||||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file:
|
||||||
return parse_ticker_dataframe(json.load(data_file))
|
return parse_ticker_dataframe(json.load(data_file))
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from freqtrade.main import refresh_whitelist, gen_pair_whitelist
|
from freqtrade.main import refresh_whitelist, gen_pair_whitelist
|
||||||
|
|
||||||
|
|
||||||
# whitelist, blacklist, filtering, all of that will
|
# whitelist, blacklist, filtering, all of that will
|
||||||
# eventually become some rules to run on a generic ACL engine
|
# eventually become some rules to run on a generic ACL engine
|
||||||
# perhaps try to anticipate that by using some python package
|
# perhaps try to anticipate that by using some python package
|
||||||
@ -12,75 +13,94 @@ def whitelist_conf():
|
|||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'exchange': {
|
'exchange': {
|
||||||
'pair_whitelist': [
|
'pair_whitelist': [
|
||||||
'BTC_ETH',
|
'ETH/BTC',
|
||||||
'BTC_TKN',
|
'TKN/BTC',
|
||||||
'BTC_TRST',
|
'TRST/BTC',
|
||||||
'BTC_SWT',
|
'SWT/BTC',
|
||||||
'BTC_BCC'
|
'BCC/BTC'
|
||||||
],
|
],
|
||||||
'pair_blacklist': [
|
'pair_blacklist': [
|
||||||
'BTC_BLK'
|
'BLK/BTC'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_market_summaries():
|
def get_markets():
|
||||||
return [{
|
return [
|
||||||
'MarketName': 'BTC-TKN',
|
{
|
||||||
'High': 0.00000919,
|
'id': 'ethbtc',
|
||||||
'Low': 0.00000820,
|
'symbol': 'ETH/BTC',
|
||||||
'Volume': 74339.61396015,
|
'base': 'ETH',
|
||||||
'Last': 0.00000820,
|
'quote': 'BTC',
|
||||||
'BaseVolume': 1664,
|
'active': True,
|
||||||
'TimeStamp': '2014-07-09T07:19:30.15',
|
'precision': {
|
||||||
'Bid': 0.00000820,
|
'price': 8,
|
||||||
'Ask': 0.00000831,
|
'amount': 8,
|
||||||
'OpenBuyOrders': 15,
|
'cost': 8,
|
||||||
'OpenSellOrders': 15,
|
},
|
||||||
'PrevDay': 0.00000821,
|
'lot': 0.00000001,
|
||||||
'Created': '2014-03-20T06:00:00',
|
|
||||||
'DisplayMarketName': ''
|
'limits': {
|
||||||
}, {
|
'amount': {
|
||||||
'MarketName': 'BTC-ETH',
|
'min': 0.01,
|
||||||
'High': 0.00000072,
|
'max': 1000,
|
||||||
'Low': 0.00000001,
|
},
|
||||||
'Volume': 166340678.42280999,
|
'price': 500000,
|
||||||
'Last': 0.00000005,
|
'cost': 500000,
|
||||||
'BaseVolume': 42,
|
},
|
||||||
'TimeStamp': '2014-07-09T07:21:40.51',
|
'info': '',
|
||||||
'Bid': 0.00000004,
|
},
|
||||||
'Ask': 0.00000005,
|
{
|
||||||
'OpenBuyOrders': 18,
|
'id': 'tknbtc',
|
||||||
'OpenSellOrders': 18,
|
'symbol': 'TKN/BTC',
|
||||||
'PrevDay': 0.00000002,
|
'base': 'TKN',
|
||||||
'Created': '2014-05-30T07:57:49.637',
|
'quote': 'BTC',
|
||||||
'DisplayMarketName': ''
|
'active': True,
|
||||||
}, {
|
'precision': {
|
||||||
'MarketName': 'BTC-BLK',
|
'price': 8,
|
||||||
'High': 0.00000072,
|
'amount': 8,
|
||||||
'Low': 0.00000001,
|
'cost': 8,
|
||||||
'Volume': 166340678.42280999,
|
},
|
||||||
'Last': 0.00000005,
|
'lot': 0.00000001,
|
||||||
'BaseVolume': 3,
|
|
||||||
'TimeStamp': '2014-07-09T07:21:40.51',
|
'limits': {
|
||||||
'Bid': 0.00000004,
|
'amount': {
|
||||||
'Ask': 0.00000005,
|
'min': 0.01,
|
||||||
'OpenBuyOrders': 18,
|
'max': 1000,
|
||||||
'OpenSellOrders': 18,
|
},
|
||||||
'PrevDay': 0.00000002,
|
'price': 500000,
|
||||||
'Created': '2014-05-30T07:57:49.637',
|
'cost': 500000,
|
||||||
'DisplayMarketName': ''
|
},
|
||||||
}]
|
'info': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'blkbtc',
|
||||||
|
'symbol': 'BLK/BTC',
|
||||||
|
'base': 'BLK',
|
||||||
|
'quote': 'BTC',
|
||||||
|
'active': True,
|
||||||
|
'precision': {
|
||||||
|
'price': 8,
|
||||||
|
'amount': 8,
|
||||||
|
'cost': 8,
|
||||||
|
},
|
||||||
|
'lot': 0.00000001,
|
||||||
|
|
||||||
|
'limits': {
|
||||||
|
'amount': {
|
||||||
|
'min': 0.01,
|
||||||
|
'max': 1000,
|
||||||
|
},
|
||||||
|
'price': 500000,
|
||||||
|
'cost': 500000,
|
||||||
|
},
|
||||||
|
'info': '',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_health():
|
def get_markets_empty():
|
||||||
return [{'Currency': 'ETH', 'IsActive': True},
|
|
||||||
{'Currency': 'TKN', 'IsActive': True},
|
|
||||||
{'Currency': 'BLK', 'IsActive': True}]
|
|
||||||
|
|
||||||
|
|
||||||
def get_health_empty():
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@ -88,11 +108,11 @@ def test_refresh_market_pair_not_in_whitelist(mocker):
|
|||||||
conf = whitelist_conf()
|
conf = whitelist_conf()
|
||||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
get_wallet_health=get_health)
|
get_markets=get_markets)
|
||||||
refreshedwhitelist = refresh_whitelist(
|
refreshedwhitelist = refresh_whitelist(
|
||||||
conf['exchange']['pair_whitelist'] + ['BTC_XXX'])
|
conf['exchange']['pair_whitelist'] + ['XXX/BTC'])
|
||||||
# List ordered by BaseVolume
|
# List ordered by BaseVolume
|
||||||
whitelist = ['BTC_ETH', 'BTC_TKN']
|
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||||
# Ensure all except those in whitelist are removed
|
# Ensure all except those in whitelist are removed
|
||||||
assert whitelist == refreshedwhitelist
|
assert whitelist == refreshedwhitelist
|
||||||
|
|
||||||
@ -101,10 +121,10 @@ def test_refresh_whitelist(mocker):
|
|||||||
conf = whitelist_conf()
|
conf = whitelist_conf()
|
||||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
get_wallet_health=get_health)
|
get_markets=get_markets)
|
||||||
refreshedwhitelist = refresh_whitelist(conf['exchange']['pair_whitelist'])
|
refreshedwhitelist = refresh_whitelist(conf['exchange']['pair_whitelist'])
|
||||||
# List ordered by BaseVolume
|
# List ordered by BaseVolume
|
||||||
whitelist = ['BTC_ETH', 'BTC_TKN']
|
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||||
# Ensure all except those in whitelist are removed
|
# Ensure all except those in whitelist are removed
|
||||||
assert whitelist == refreshedwhitelist
|
assert whitelist == refreshedwhitelist
|
||||||
|
|
||||||
@ -113,11 +133,9 @@ def test_refresh_whitelist_dynamic(mocker):
|
|||||||
conf = whitelist_conf()
|
conf = whitelist_conf()
|
||||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
get_wallet_health=get_health)
|
get_markets=get_markets)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
|
||||||
get_market_summaries=get_market_summaries)
|
|
||||||
# argument: use the whitelist dynamically by exchange-volume
|
# argument: use the whitelist dynamically by exchange-volume
|
||||||
whitelist = ['BTC_TKN', 'BTC_ETH']
|
whitelist = ['TKN/BTC', 'ETH/BTC']
|
||||||
refreshedwhitelist = refresh_whitelist(
|
refreshedwhitelist = refresh_whitelist(
|
||||||
gen_pair_whitelist(conf['stake_currency']))
|
gen_pair_whitelist(conf['stake_currency']))
|
||||||
assert whitelist == refreshedwhitelist
|
assert whitelist == refreshedwhitelist
|
||||||
@ -127,7 +145,7 @@ def test_refresh_whitelist_dynamic_empty(mocker):
|
|||||||
conf = whitelist_conf()
|
conf = whitelist_conf()
|
||||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
get_wallet_health=get_health_empty)
|
get_markets=get_markets_empty)
|
||||||
# argument: use the whitelist dynamically by exchange-volume
|
# argument: use the whitelist dynamically by exchange-volume
|
||||||
whitelist = []
|
whitelist = []
|
||||||
conf['exchange']['pair_whitelist'] = []
|
conf['exchange']['pair_whitelist'] = []
|
||||||
|
@ -44,13 +44,13 @@ def test_returns_latest_buy_signal(mocker):
|
|||||||
'freqtrade.analyze.analyze_ticker',
|
'freqtrade.analyze.analyze_ticker',
|
||||||
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (True, False)
|
assert get_signal('ETH/BTC', '5m') == (True, False)
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.analyze.analyze_ticker',
|
'freqtrade.analyze.analyze_ticker',
|
||||||
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (False, True)
|
assert get_signal('ETH/BTC', '5m') == (False, True)
|
||||||
|
|
||||||
|
|
||||||
def test_returns_latest_sell_signal(mocker):
|
def test_returns_latest_sell_signal(mocker):
|
||||||
@ -59,18 +59,18 @@ def test_returns_latest_sell_signal(mocker):
|
|||||||
'freqtrade.analyze.analyze_ticker',
|
'freqtrade.analyze.analyze_ticker',
|
||||||
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (False, True)
|
assert get_signal('ETH/BTC', '5m') == (False, True)
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.analyze.analyze_ticker',
|
'freqtrade.analyze.analyze_ticker',
|
||||||
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
|
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
|
||||||
)
|
)
|
||||||
assert get_signal('BTC-ETH', 5) == (True, False)
|
assert get_signal('ETH/BTC', '5m') == (True, False)
|
||||||
|
|
||||||
|
|
||||||
def test_get_signal_empty(default_conf, mocker, caplog):
|
def test_get_signal_empty(default_conf, mocker, caplog):
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
||||||
assert (False, False) == get_signal('foo', int(default_conf['ticker_interval']))
|
assert (False, False) == get_signal('foo', default_conf['ticker_interval'])
|
||||||
assert tt.log_has('Empty ticker history for pair foo',
|
assert tt.log_has('Empty ticker history for pair foo',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker',
|
mocker.patch('freqtrade.analyze.analyze_ticker',
|
||||||
side_effect=ValueError('xyz'))
|
side_effect=ValueError('xyz'))
|
||||||
assert (False, False) == get_signal('foo', int(default_conf['ticker_interval']))
|
assert (False, False) == get_signal('foo', default_conf['ticker_interval'])
|
||||||
assert tt.log_has('Unable to analyze ticker for pair foo: xyz',
|
assert tt.log_has('Unable to analyze ticker for pair foo: xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
|||||||
def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
|
def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([]))
|
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([]))
|
||||||
assert (False, False) == get_signal('xyz', int(default_conf['ticker_interval']))
|
assert (False, False) == get_signal('xyz', default_conf['ticker_interval'])
|
||||||
assert tt.log_has('Empty dataframe for pair xyz',
|
assert tt.log_has('Empty dataframe for pair xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
|
|||||||
oldtime = arrow.utcnow() - datetime.timedelta(minutes=11)
|
oldtime = arrow.utcnow() - datetime.timedelta(minutes=11)
|
||||||
ticks = DataFrame([{'buy': 1, 'date': oldtime}])
|
ticks = DataFrame([{'buy': 1, 'date': oldtime}])
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks))
|
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks))
|
||||||
assert (False, False) == get_signal('xyz', int(default_conf['ticker_interval']))
|
assert (False, False) == get_signal('xyz', default_conf['ticker_interval'])
|
||||||
assert tt.log_has('Too old dataframe for pair xyz',
|
assert tt.log_has('Too old dataframe for pair xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ def test_get_signal_handles_exceptions(mocker):
|
|||||||
mocker.patch('freqtrade.analyze.analyze_ticker',
|
mocker.patch('freqtrade.analyze.analyze_ticker',
|
||||||
side_effect=Exception('invalid ticker history '))
|
side_effect=Exception('invalid ticker history '))
|
||||||
|
|
||||||
assert get_signal('BTC-ETH', 5) == (False, False)
|
assert get_signal('ETH/BTC', '5m') == (False, False)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
||||||
|
@ -4,11 +4,11 @@ import pandas
|
|||||||
import freqtrade.optimize
|
import freqtrade.optimize
|
||||||
from freqtrade import analyze
|
from freqtrade import analyze
|
||||||
|
|
||||||
_pairs = ['BTC_ETH']
|
_pairs = ['ETH/BTC']
|
||||||
|
|
||||||
|
|
||||||
def load_dataframe_pair(pairs):
|
def load_dataframe_pair(pairs):
|
||||||
ld = freqtrade.optimize.load_data(None, ticker_interval=5, pairs=pairs)
|
ld = freqtrade.optimize.load_data(None, ticker_interval='5m', pairs=pairs)
|
||||||
assert isinstance(ld, dict)
|
assert isinstance(ld, dict)
|
||||||
assert isinstance(pairs[0], str)
|
assert isinstance(pairs[0], str)
|
||||||
dataframe = ld[pairs[0]]
|
dataframe = ld[pairs[0]]
|
||||||
|
@ -5,13 +5,11 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
import freqtrade.main as main
|
import freqtrade.main as main
|
||||||
import freqtrade.tests.conftest as tt # test tools
|
import freqtrade.tests.conftest as tt # test tools
|
||||||
from freqtrade import DependencyException, OperationalException
|
from freqtrade import DependencyException, OperationalException, NetworkException
|
||||||
from freqtrade.exchange import Exchanges
|
|
||||||
from freqtrade.main import (_process, check_handle_timedout, create_trade,
|
from freqtrade.main import (_process, check_handle_timedout, create_trade,
|
||||||
execute_sell, get_target_bid, handle_trade, init)
|
execute_sell, get_target_bid, handle_trade, init)
|
||||||
from freqtrade.misc import State, get_state
|
from freqtrade.misc import State, get_state
|
||||||
@ -32,7 +30,7 @@ def test_parse_args_backtesting(mocker):
|
|||||||
assert call_args.loglevel == 20
|
assert call_args.loglevel == 20
|
||||||
assert call_args.subparser == 'backtesting'
|
assert call_args.subparser == 'backtesting'
|
||||||
assert call_args.func is not None
|
assert call_args.func is not None
|
||||||
assert call_args.ticker_interval == 5
|
assert call_args.ticker_interval == '5m'
|
||||||
|
|
||||||
|
|
||||||
def test_main_start_hyperopt(mocker):
|
def test_main_start_hyperopt(mocker):
|
||||||
@ -50,9 +48,9 @@ def test_main_start_hyperopt(mocker):
|
|||||||
def test_process_maybe_execute_buy(default_conf, mocker):
|
def test_process_maybe_execute_buy(default_conf, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch('freqtrade.main.create_trade', return_value=True)
|
mocker.patch('freqtrade.main.create_trade', return_value=True)
|
||||||
assert main.process_maybe_execute_buy(int(default_conf['ticker_interval']))
|
assert main.process_maybe_execute_buy(default_conf['ticker_interval'])
|
||||||
mocker.patch('freqtrade.main.create_trade', return_value=False)
|
mocker.patch('freqtrade.main.create_trade', return_value=False)
|
||||||
assert not main.process_maybe_execute_buy(int(default_conf['ticker_interval']))
|
assert not main.process_maybe_execute_buy(default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_process_maybe_execute_sell(default_conf, mocker):
|
def test_process_maybe_execute_sell(default_conf, mocker):
|
||||||
@ -61,17 +59,17 @@ def test_process_maybe_execute_sell(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.get_order', return_value=1)
|
mocker.patch('freqtrade.exchange.get_order', return_value=1)
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
trade.open_order_id = '123'
|
trade.open_order_id = '123'
|
||||||
assert not main.process_maybe_execute_sell(trade, int(default_conf['ticker_interval']))
|
assert not main.process_maybe_execute_sell(trade, default_conf['ticker_interval'])
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
# Assert we call handle_trade() if trade is feasible for execution
|
# Assert we call handle_trade() if trade is feasible for execution
|
||||||
assert main.process_maybe_execute_sell(trade, int(default_conf['ticker_interval']))
|
assert main.process_maybe_execute_sell(trade, default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_process_maybe_execute_buy_exception(default_conf, mocker, caplog):
|
def test_process_maybe_execute_buy_exception(default_conf, mocker, caplog):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch('freqtrade.main.create_trade', MagicMock(side_effect=DependencyException))
|
mocker.patch('freqtrade.main.create_trade', MagicMock(side_effect=DependencyException))
|
||||||
main.process_maybe_execute_buy(int(default_conf['ticker_interval']))
|
main.process_maybe_execute_buy(default_conf['ticker_interval'])
|
||||||
tt.log_has('Unable to create trade:', caplog.record_tuples)
|
tt.log_has('Unable to create trade:', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
@ -82,15 +80,14 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, m
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_wallet_health=health,
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
|
||||||
get_order=MagicMock(return_value=limit_buy_order))
|
get_order=MagicMock(return_value=limit_buy_order))
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
assert not trades
|
assert not trades
|
||||||
|
|
||||||
result = _process(interval=int(default_conf['ticker_interval']))
|
result = _process(interval=default_conf['ticker_interval'])
|
||||||
assert result is True
|
assert result is True
|
||||||
|
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
@ -100,7 +97,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, m
|
|||||||
assert trade.stake_amount == default_conf['stake_amount']
|
assert trade.stake_amount == default_conf['stake_amount']
|
||||||
assert trade.is_open
|
assert trade.is_open
|
||||||
assert trade.open_date is not None
|
assert trade.open_date is not None
|
||||||
assert trade.exchange == Exchanges.BITTREX.name
|
assert trade.exchange == 'binance'
|
||||||
assert trade.open_rate == 0.00001099
|
assert trade.open_rate == 0.00001099
|
||||||
assert trade.amount == 90.99181073703367
|
assert trade.amount == 90.99181073703367
|
||||||
|
|
||||||
@ -113,10 +110,9 @@ def test_process_exchange_failures(default_conf, ticker, health, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_wallet_health=health,
|
buy=MagicMock(side_effect=NetworkException))
|
||||||
buy=MagicMock(side_effect=requests.exceptions.RequestException))
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
result = _process(interval=int(default_conf['ticker_interval']))
|
result = _process(interval=default_conf['ticker_interval'])
|
||||||
assert result is False
|
assert result is False
|
||||||
assert sleep_mock.has_calls()
|
assert sleep_mock.has_calls()
|
||||||
|
|
||||||
@ -129,12 +125,11 @@ def test_process_operational_exception(default_conf, ticker, health, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_wallet_health=health,
|
|
||||||
buy=MagicMock(side_effect=OperationalException))
|
buy=MagicMock(side_effect=OperationalException))
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
assert get_state() == State.RUNNING
|
assert get_state() == State.RUNNING
|
||||||
|
|
||||||
result = _process(interval=int(default_conf['ticker_interval']))
|
result = _process(interval=default_conf['ticker_interval'])
|
||||||
assert result is False
|
assert result is False
|
||||||
assert get_state() == State.STOPPED
|
assert get_state() == State.STOPPED
|
||||||
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
|
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
|
||||||
@ -147,19 +142,18 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, m
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_wallet_health=health,
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
|
||||||
get_order=MagicMock(return_value=limit_buy_order))
|
get_order=MagicMock(return_value=limit_buy_order))
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
assert not trades
|
assert not trades
|
||||||
result = _process(interval=int(default_conf['ticker_interval']))
|
result = _process(interval=default_conf['ticker_interval'])
|
||||||
assert result is True
|
assert result is True
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
assert len(trades) == 1
|
assert len(trades) == 1
|
||||||
|
|
||||||
result = _process(interval=int(default_conf['ticker_interval']))
|
result = _process(interval=default_conf['ticker_interval'])
|
||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
@ -170,19 +164,19 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
# Save state of current whitelist
|
# Save state of current whitelist
|
||||||
whitelist = copy.deepcopy(default_conf['exchange']['pair_whitelist'])
|
whitelist = copy.deepcopy(default_conf['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade is not None
|
assert trade is not None
|
||||||
assert trade.stake_amount == 0.001
|
assert trade.stake_amount == 0.001
|
||||||
assert trade.is_open
|
assert trade.is_open
|
||||||
assert trade.open_date is not None
|
assert trade.open_date is not None
|
||||||
assert trade.exchange == Exchanges.BITTREX.name
|
assert trade.exchange == 'binance'
|
||||||
|
|
||||||
# Simulate fulfilled LIMIT_BUY order for trade
|
# Simulate fulfilled LIMIT_BUY order for trade
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
@ -198,14 +192,14 @@ def test_create_trade_minimal_amount(default_conf, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
|
||||||
buy_mock = mocker.patch(
|
buy_mock = mocker.patch(
|
||||||
'freqtrade.main.exchange.buy', MagicMock(return_value='mocked_limit_buy')
|
'freqtrade.main.exchange.buy', MagicMock(return_value={'id': 'mocked_limit_buy'})
|
||||||
)
|
)
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
min_stake_amount = 0.0005
|
min_stake_amount = 0.0005
|
||||||
create_trade(min_stake_amount, int(default_conf['ticker_interval']))
|
create_trade(min_stake_amount, default_conf['ticker_interval'])
|
||||||
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
|
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
|
||||||
assert rate * amount >= min_stake_amount
|
assert rate * amount >= min_stake_amount
|
||||||
|
|
||||||
@ -217,10 +211,10 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}),
|
||||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5))
|
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5))
|
||||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||||
create_trade(default_conf['stake_amount'], int(default_conf['ticker_interval']))
|
create_trade(default_conf['stake_amount'], default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_create_trade_no_pairs(default_conf, ticker, mocker):
|
def test_create_trade_no_pairs(default_conf, ticker, mocker):
|
||||||
@ -230,13 +224,13 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
|
|
||||||
with pytest.raises(DependencyException, match=r'.*No pair in whitelist.*'):
|
with pytest.raises(DependencyException, match=r'.*No pair in whitelist.*'):
|
||||||
conf = copy.deepcopy(default_conf)
|
conf = copy.deepcopy(default_conf)
|
||||||
conf['exchange']['pair_whitelist'] = []
|
conf['exchange']['pair_whitelist'] = []
|
||||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||||
create_trade(default_conf['stake_amount'], int(default_conf['ticker_interval']))
|
create_trade(default_conf['stake_amount'], default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
|
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
|
||||||
@ -246,14 +240,14 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
|
|
||||||
with pytest.raises(DependencyException, match=r'.*No pair in whitelist.*'):
|
with pytest.raises(DependencyException, match=r'.*No pair in whitelist.*'):
|
||||||
conf = copy.deepcopy(default_conf)
|
conf = copy.deepcopy(default_conf)
|
||||||
conf['exchange']['pair_whitelist'] = ["BTC_ETH"]
|
conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
|
||||||
conf['exchange']['pair_blacklist'] = ["BTC_ETH"]
|
conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
|
||||||
mocker.patch.dict('freqtrade.main._CONF', conf)
|
mocker.patch.dict('freqtrade.main._CONF', conf)
|
||||||
create_trade(default_conf['stake_amount'], int(default_conf['ticker_interval']))
|
create_trade(default_conf['stake_amount'], default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_create_trade_no_signal(default_conf, mocker):
|
def test_create_trade_no_signal(default_conf, mocker):
|
||||||
@ -267,7 +261,7 @@ def test_create_trade_no_signal(default_conf, mocker):
|
|||||||
stake_amount = 10
|
stake_amount = 10
|
||||||
Trade.query = MagicMock()
|
Trade.query = MagicMock()
|
||||||
Trade.query.filter = MagicMock()
|
Trade.query.filter = MagicMock()
|
||||||
assert not create_trade(stake_amount, int(default_conf['ticker_interval']))
|
assert not create_trade(stake_amount, default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
|
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
|
||||||
@ -281,13 +275,14 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
|
|||||||
'ask': 0.00001173,
|
'ask': 0.00001173,
|
||||||
'last': 0.00001172
|
'last': 0.00001172
|
||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
sell=MagicMock(return_value='mocked_limit_sell'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}),
|
||||||
|
sell=MagicMock(return_value={'id': 'mocked_limit_sell'}))
|
||||||
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
_cache_symbols=MagicMock(return_value={'BTC': 1}))
|
_cache_symbols=MagicMock(return_value={'BTC': 1}))
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -296,7 +291,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
|
|||||||
assert trade.is_open is True
|
assert trade.is_open is True
|
||||||
|
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True
|
assert handle_trade(trade, default_conf['ticker_interval']) is True
|
||||||
assert trade.open_order_id == 'mocked_limit_sell'
|
assert trade.open_order_id == 'mocked_limit_sell'
|
||||||
|
|
||||||
# Simulate fulfilled LIMIT_SELL order for trade
|
# Simulate fulfilled LIMIT_SELL order for trade
|
||||||
@ -317,11 +312,11 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
# Buy and Sell triggering, so doing nothing ...
|
# Buy and Sell triggering, so doing nothing ...
|
||||||
trades = Trade.query.all()
|
trades = Trade.query.all()
|
||||||
@ -330,7 +325,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker):
|
|||||||
|
|
||||||
# Buy is triggering, so buying ...
|
# Buy is triggering, so buying ...
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
trades = Trade.query.all()
|
trades = Trade.query.all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
assert nb_trades == 1
|
assert nb_trades == 1
|
||||||
@ -338,7 +333,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker):
|
|||||||
|
|
||||||
# Buy and Sell are not triggering, so doing nothing ...
|
# Buy and Sell are not triggering, so doing nothing ...
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, False))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, False))
|
||||||
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
|
assert handle_trade(trades[0], default_conf['ticker_interval']) is False
|
||||||
trades = Trade.query.all()
|
trades = Trade.query.all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
assert nb_trades == 1
|
assert nb_trades == 1
|
||||||
@ -346,7 +341,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker):
|
|||||||
|
|
||||||
# Buy and Sell are triggering, so doing nothing ...
|
# Buy and Sell are triggering, so doing nothing ...
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, True))
|
||||||
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
|
assert handle_trade(trades[0], default_conf['ticker_interval']) is False
|
||||||
trades = Trade.query.all()
|
trades = Trade.query.all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
assert nb_trades == 1
|
assert nb_trades == 1
|
||||||
@ -355,7 +350,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker):
|
|||||||
# Sell is triggering, guess what : we are Selling!
|
# Sell is triggering, guess what : we are Selling!
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
trades = Trade.query.all()
|
trades = Trade.query.all()
|
||||||
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is True
|
assert handle_trade(trades[0], default_conf['ticker_interval']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
|
def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
|
||||||
@ -367,11 +362,11 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
mocker.patch('freqtrade.main.min_roi_reached', return_value=True)
|
mocker.patch('freqtrade.main.min_roi_reached', return_value=True)
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
@ -382,11 +377,11 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
|
|||||||
# executing
|
# executing
|
||||||
# if ROI is reached we must sell
|
# if ROI is reached we must sell
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, interval=int(default_conf['ticker_interval']))
|
assert handle_trade(trade, interval=default_conf['ticker_interval'])
|
||||||
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
|
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
|
||||||
# if ROI is reached we must sell even if sell-signal is not signalled
|
# if ROI is reached we must sell even if sell-signal is not signalled
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, interval=int(default_conf['ticker_interval']))
|
assert handle_trade(trade, interval=default_conf['ticker_interval'])
|
||||||
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
|
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
@ -399,20 +394,20 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog):
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
|
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, False))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, False))
|
||||||
value_returned = handle_trade(trade, int(default_conf['ticker_interval']))
|
value_returned = handle_trade(trade, default_conf['ticker_interval'])
|
||||||
assert value_returned is False
|
assert value_returned is False
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, int(default_conf['ticker_interval']))
|
assert handle_trade(trade, default_conf['ticker_interval'])
|
||||||
s = 'Executing sell due to sell signal ...'
|
s = 'Executing sell due to sell signal ...'
|
||||||
assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples
|
assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples
|
||||||
|
|
||||||
@ -424,11 +419,11 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
|||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
|
|
||||||
# Create trade and sell it
|
# Create trade and sell it
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -438,7 +433,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
|||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r'.*closed trade.*'):
|
with pytest.raises(ValueError, match=r'.*closed trade.*'):
|
||||||
handle_trade(trade, int(default_conf['ticker_interval']))
|
handle_trade(trade, default_conf['ticker_interval'])
|
||||||
|
|
||||||
|
|
||||||
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker):
|
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker):
|
||||||
@ -454,9 +449,9 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
|
|||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
trade_buy = Trade(
|
trade_buy = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
open_rate=0.00001099,
|
open_rate=0.00001099,
|
||||||
exchange='BITTREX',
|
exchange='binance',
|
||||||
open_order_id='123456789',
|
open_order_id='123456789',
|
||||||
amount=90.99181073,
|
amount=90.99181073,
|
||||||
fee=0.0,
|
fee=0.0,
|
||||||
@ -503,9 +498,9 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
|||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
trade_sell = Trade(
|
trade_sell = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
open_rate=0.00001099,
|
open_rate=0.00001099,
|
||||||
exchange='BITTREX',
|
exchange='binance',
|
||||||
open_order_id='123456789',
|
open_order_id='123456789',
|
||||||
amount=90.99181073,
|
amount=90.99181073,
|
||||||
fee=0.0,
|
fee=0.0,
|
||||||
@ -552,9 +547,9 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
|||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
trade_buy = Trade(
|
trade_buy = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
open_rate=0.00001099,
|
open_rate=0.00001099,
|
||||||
exchange='BITTREX',
|
exchange='binance',
|
||||||
open_order_id='123456789',
|
open_order_id='123456789',
|
||||||
amount=90.99181073,
|
amount=90.99181073,
|
||||||
fee=0.0,
|
fee=0.0,
|
||||||
@ -598,12 +593,13 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
|
|||||||
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -611,13 +607,14 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
|
|||||||
# Increase the price and sell it
|
# Increase the price and sell it
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker_sell_up)
|
get_ticker=ticker_sell_up)
|
||||||
|
|
||||||
execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
|
execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
|
||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'Profit' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Profit' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -636,12 +633,13 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
|
|||||||
send_msg=MagicMock())
|
send_msg=MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -655,7 +653,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
|
|||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -669,11 +667,12 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_d
|
|||||||
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -681,6 +680,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_d
|
|||||||
# Decrease the price and sell it
|
# Decrease the price and sell it
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker_sell_down)
|
get_ticker=ticker_sell_down)
|
||||||
mocker.patch('freqtrade.main._CONF', {})
|
mocker.patch('freqtrade.main._CONF', {})
|
||||||
|
|
||||||
@ -688,7 +688,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_d
|
|||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
@ -700,11 +700,12 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
|
|||||||
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker)
|
get_ticker=ticker)
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -712,6 +713,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
|
|||||||
# Increase the price and sell it
|
# Increase the price and sell it
|
||||||
mocker.patch.multiple('freqtrade.main.exchange',
|
mocker.patch.multiple('freqtrade.main.exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
get_ticker=ticker_sell_up)
|
get_ticker=ticker_sell_up)
|
||||||
mocker.patch('freqtrade.main._CONF', {})
|
mocker.patch('freqtrade.main._CONF', {})
|
||||||
|
|
||||||
@ -719,7 +721,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
|
|||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
|
assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
|
||||||
assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0]
|
assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -743,15 +745,16 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
|
|||||||
'ask': 0.00002173,
|
'ask': 0.00002173,
|
||||||
'last': 0.00002172
|
'last': 0.00002172
|
||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True
|
assert handle_trade(trade, default_conf['ticker_interval']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
|
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
|
||||||
@ -771,15 +774,16 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
|
|||||||
'ask': 0.00002173,
|
'ask': 0.00002173,
|
||||||
'last': 0.00002172
|
'last': 0.00002172
|
||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True
|
assert handle_trade(trade, default_conf['ticker_interval']) is True
|
||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
|
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
|
||||||
@ -799,15 +803,16 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
|
|||||||
'ask': 0.00000173,
|
'ask': 0.00000173,
|
||||||
'last': 0.00000172
|
'last': 0.00000172
|
||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, int(default_conf['ticker_interval'])) is False
|
assert handle_trade(trade, default_conf['ticker_interval']) is False
|
||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
|
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
|
||||||
@ -827,12 +832,13 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
|
|||||||
'ask': 0.00000173,
|
'ask': 0.00000173,
|
||||||
'last': 0.00000172
|
'last': 0.00000172
|
||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
get_fee=MagicMock(return_value=0.0025),
|
||||||
|
buy=MagicMock(return_value={'id': 'mocked_limit_buy'}))
|
||||||
|
|
||||||
init(default_conf, create_engine('sqlite://'))
|
init(default_conf, create_engine('sqlite://'))
|
||||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
create_trade(0.001, default_conf['ticker_interval'])
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.update(limit_buy_order)
|
trade.update(limit_buy_order)
|
||||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, True))
|
||||||
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True
|
assert handle_trade(trade, default_conf['ticker_interval']) is True
|
||||||
|
@ -113,7 +113,7 @@ def test_parse_args_backtesting_custom():
|
|||||||
'-c', 'test_conf.json',
|
'-c', 'test_conf.json',
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--live',
|
'--live',
|
||||||
'--ticker-interval', '1',
|
'--ticker-interval', '1m',
|
||||||
'--refresh-pairs-cached']
|
'--refresh-pairs-cached']
|
||||||
call_args = parse_args(args, '')
|
call_args = parse_args(args, '')
|
||||||
assert call_args.config == 'test_conf.json'
|
assert call_args.config == 'test_conf.json'
|
||||||
@ -121,7 +121,7 @@ def test_parse_args_backtesting_custom():
|
|||||||
assert call_args.loglevel == 20
|
assert call_args.loglevel == 20
|
||||||
assert call_args.subparser == 'backtesting'
|
assert call_args.subparser == 'backtesting'
|
||||||
assert call_args.func is not None
|
assert call_args.func is not None
|
||||||
assert call_args.ticker_interval == 1
|
assert call_args.ticker_interval == '1m'
|
||||||
assert call_args.refresh_pairs is True
|
assert call_args.refresh_pairs is True
|
||||||
|
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ def test_load_config(default_conf, mocker):
|
|||||||
|
|
||||||
def test_load_config_invalid_pair(default_conf, mocker):
|
def test_load_config_invalid_pair(default_conf, mocker):
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['exchange']['pair_whitelist'].append('BTC-ETH')
|
conf['exchange']['pair_whitelist'].append('ETH_BTC') # Should have format ETH/BTC
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.misc.open',
|
'freqtrade.misc.open',
|
||||||
mocker.mock_open(
|
mocker.mock_open(
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from freqtrade.exchange import Exchanges
|
|
||||||
from freqtrade.persistence import Trade, init, clean_dry_run_db
|
from freqtrade.persistence import Trade, init, clean_dry_run_db
|
||||||
|
|
||||||
|
|
||||||
@ -117,10 +116,10 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
assert trade.open_order_id is None
|
assert trade.open_order_id is None
|
||||||
assert trade.open_rate is None
|
assert trade.open_rate is None
|
||||||
@ -144,10 +143,10 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
|||||||
|
|
||||||
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
|
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
|
|
||||||
trade.open_order_id = 'something'
|
trade.open_order_id = 'something'
|
||||||
@ -166,10 +165,10 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
|
|||||||
|
|
||||||
def test_calc_close_trade_price_exception(limit_buy_order):
|
def test_calc_close_trade_price_exception(limit_buy_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
|
|
||||||
trade.open_order_id = 'something'
|
trade.open_order_id = 'something'
|
||||||
@ -179,10 +178,10 @@ def test_calc_close_trade_price_exception(limit_buy_order):
|
|||||||
|
|
||||||
def test_update_open_order(limit_buy_order):
|
def test_update_open_order(limit_buy_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=1.00,
|
stake_amount=1.00,
|
||||||
fee=0.1,
|
fee=0.1,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert trade.open_order_id is None
|
assert trade.open_order_id is None
|
||||||
@ -201,10 +200,10 @@ def test_update_open_order(limit_buy_order):
|
|||||||
|
|
||||||
def test_update_invalid_order(limit_buy_order):
|
def test_update_invalid_order(limit_buy_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=1.00,
|
stake_amount=1.00,
|
||||||
fee=0.1,
|
fee=0.1,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
limit_buy_order['type'] = 'invalid'
|
limit_buy_order['type'] = 'invalid'
|
||||||
with pytest.raises(ValueError, match=r'Unknown order type'):
|
with pytest.raises(ValueError, match=r'Unknown order type'):
|
||||||
@ -213,10 +212,10 @@ def test_update_invalid_order(limit_buy_order):
|
|||||||
|
|
||||||
def test_calc_open_trade_price(limit_buy_order):
|
def test_calc_open_trade_price(limit_buy_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'open_trade'
|
trade.open_order_id = 'open_trade'
|
||||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||||
@ -230,10 +229,10 @@ def test_calc_open_trade_price(limit_buy_order):
|
|||||||
|
|
||||||
def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
|
def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'close_trade'
|
trade.open_order_id = 'close_trade'
|
||||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||||
@ -251,10 +250,10 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
|
|||||||
|
|
||||||
def test_calc_profit(limit_buy_order, limit_sell_order):
|
def test_calc_profit(limit_buy_order, limit_sell_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'profit_percent'
|
trade.open_order_id = 'profit_percent'
|
||||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||||
@ -285,10 +284,10 @@ def test_calc_profit(limit_buy_order, limit_sell_order):
|
|||||||
|
|
||||||
def test_calc_profit_percent(limit_buy_order, limit_sell_order):
|
def test_calc_profit_percent(limit_buy_order, limit_sell_order):
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
exchange=Exchanges.BITTREX,
|
exchange='binance',
|
||||||
)
|
)
|
||||||
trade.open_order_id = 'profit_percent'
|
trade.open_order_id = 'profit_percent'
|
||||||
trade.update(limit_buy_order) # Buy @ 0.00001099
|
trade.update(limit_buy_order) # Buy @ 0.00001099
|
||||||
@ -316,35 +315,35 @@ def test_clean_dry_run_db(default_conf):
|
|||||||
|
|
||||||
# Simulate dry_run entries
|
# Simulate dry_run entries
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETH',
|
pair='ETH/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
amount=123.0,
|
amount=123.0,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
open_rate=0.123,
|
open_rate=0.123,
|
||||||
exchange='BITTREX',
|
exchange='binance',
|
||||||
open_order_id='dry_run_buy_12345'
|
open_order_id='dry_run_buy_12345'
|
||||||
)
|
)
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
|
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETC',
|
pair='ETC/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
amount=123.0,
|
amount=123.0,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
open_rate=0.123,
|
open_rate=0.123,
|
||||||
exchange='BITTREX',
|
exchange='binance',
|
||||||
open_order_id='dry_run_sell_12345'
|
open_order_id='dry_run_sell_12345'
|
||||||
)
|
)
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
|
|
||||||
# Simulate prod entry
|
# Simulate prod entry
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='BTC_ETC',
|
pair='ETC/BTC',
|
||||||
stake_amount=0.001,
|
stake_amount=0.001,
|
||||||
amount=123.0,
|
amount=123.0,
|
||||||
fee=0.0025,
|
fee=0.0025,
|
||||||
open_rate=0.123,
|
open_rate=0.123,
|
||||||
exchange='BITTREX',
|
exchange='binance',
|
||||||
open_order_id='prod_buy_12345'
|
open_order_id='prod_buy_12345'
|
||||||
)
|
)
|
||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
|
1
freqtrade/tests/testdata/ADA_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/ADA_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ADA_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/ADA_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ADA-1.json
vendored
1
freqtrade/tests/testdata/BTC_ADA-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ADA-5.json
vendored
1
freqtrade/tests/testdata/BTC_ADA-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_DASH-1.json
vendored
1
freqtrade/tests/testdata/BTC_DASH-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_DASH-5.json
vendored
1
freqtrade/tests/testdata/BTC_DASH-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETC-1.json
vendored
1
freqtrade/tests/testdata/BTC_ETC-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETC-5.json
vendored
1
freqtrade/tests/testdata/BTC_ETC-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETH-1.json
vendored
1
freqtrade/tests/testdata/BTC_ETH-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ETH-5.json
vendored
1
freqtrade/tests/testdata/BTC_ETH-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_LTC-1.json
vendored
1
freqtrade/tests/testdata/BTC_LTC-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_LTC-5.json
vendored
1
freqtrade/tests/testdata/BTC_LTC-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_NXT-1.json
vendored
1
freqtrade/tests/testdata/BTC_NXT-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_NXT-5.json
vendored
1
freqtrade/tests/testdata/BTC_NXT-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_POWR-1.json
vendored
1
freqtrade/tests/testdata/BTC_POWR-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_POWR-5.json
vendored
1
freqtrade/tests/testdata/BTC_POWR-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_UNITEST-1.json
vendored
1
freqtrade/tests/testdata/BTC_UNITEST-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_UNITEST-30.json
vendored
1
freqtrade/tests/testdata/BTC_UNITEST-30.json
vendored
File diff suppressed because one or more lines are too long
3
freqtrade/tests/testdata/BTC_UNITEST-8.json
vendored
3
freqtrade/tests/testdata/BTC_UNITEST-8.json
vendored
@ -1,3 +0,0 @@
|
|||||||
[
|
|
||||||
{"O": 0.00162008, "H": 0.00162008, "L": 0.00162008, "C": 0.00162008, "V": 108.14853839, "T": "2017-11-04T23:02:00", "BV": 0.17520927}
|
|
||||||
]
|
|
BIN
freqtrade/tests/testdata/BTC_UNITEST-8.json.gz
vendored
BIN
freqtrade/tests/testdata/BTC_UNITEST-8.json.gz
vendored
Binary file not shown.
1
freqtrade/tests/testdata/BTC_XLM-1.json
vendored
1
freqtrade/tests/testdata/BTC_XLM-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_XLM-5.json
vendored
1
freqtrade/tests/testdata/BTC_XLM-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_XMR-1.json
vendored
1
freqtrade/tests/testdata/BTC_XMR-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_XMR-5.json
vendored
1
freqtrade/tests/testdata/BTC_XMR-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ZEC-1.json
vendored
1
freqtrade/tests/testdata/BTC_ZEC-1.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/BTC_ZEC-5.json
vendored
1
freqtrade/tests/testdata/BTC_ZEC-5.json
vendored
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/CFI_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/CFI_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/CFI_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/CFI_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/DASH_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/DASH_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/DASH_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/DASH_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ETH_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/ETH_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/ETH_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/ETH_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/LTC_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/LTC_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/LTC_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/LTC_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/MEME_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/MEME_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/MEME_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/MEME_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/POWR_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/POWR_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/POWR_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/POWR_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/UNITTEST_BTC-1m.json
vendored
Normal file
1
freqtrade/tests/testdata/UNITTEST_BTC-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/UNITTEST_BTC-30m.json
vendored
Normal file
1
freqtrade/tests/testdata/UNITTEST_BTC-30m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/UNITTEST_BTC-5m.json
vendored
Normal file
1
freqtrade/tests/testdata/UNITTEST_BTC-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
freqtrade/tests/testdata/UNITTEST_BTC-8m.json
vendored
Normal file
1
freqtrade/tests/testdata/UNITTEST_BTC-8m.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"O": 0.00162008, "H": 0.00162008, "L": 0.00162008, "C": 0.00162008, "V": 108.14853839, "T": "2017-11-04T23:02:00.000000"}]
|
BIN
freqtrade/tests/testdata/UNITTEST_BTC-8m.json.gz
vendored
Normal file
BIN
freqtrade/tests/testdata/UNITTEST_BTC-8m.json.gz
vendored
Normal file
Binary file not shown.
@ -3,21 +3,21 @@
|
|||||||
"""This script generate json data from bittrex"""
|
"""This script generate json data from bittrex"""
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import ccxt
|
||||||
|
|
||||||
from freqtrade import exchange
|
from freqtrade import exchange
|
||||||
from freqtrade.exchange import Bittrex
|
|
||||||
from freqtrade import misc
|
from freqtrade import misc
|
||||||
|
|
||||||
parser = misc.common_args_parser('download utility')
|
parser = misc.common_args_parser('download utility')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--pair',
|
'-p', '--pair',
|
||||||
help='JSON file containing pairs to download',
|
help='JSON file containing pairs to download',
|
||||||
dest='pair',
|
dest='pair',
|
||||||
default=None
|
default=None
|
||||||
)
|
)
|
||||||
args = parser.parse_args(sys.argv[1:])
|
args = parser.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
TICKER_INTERVALS = [1, 5] # ticker interval in minutes (currently implemented: 1 and 5)
|
TICKER_INTERVALS = ['1m', '5m'] # ticker interval in minutes (currently implemented: 1 and 5)
|
||||||
PAIRS = []
|
PAIRS = []
|
||||||
|
|
||||||
if args.pair:
|
if args.pair:
|
||||||
@ -28,11 +28,13 @@ PAIRS = list(set(PAIRS))
|
|||||||
print('About to download pairs:', PAIRS)
|
print('About to download pairs:', PAIRS)
|
||||||
|
|
||||||
# Init Bittrex exchange
|
# Init Bittrex exchange
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = ccxt.bittrex()
|
||||||
|
|
||||||
for pair in PAIRS:
|
for pair in PAIRS:
|
||||||
for tick_interval in TICKER_INTERVALS:
|
for tick_interval in TICKER_INTERVALS:
|
||||||
print('downloading pair %s, interval %s' % (pair, tick_interval))
|
print('downloading pair %s, interval %s' % (pair, tick_interval))
|
||||||
data = exchange.get_ticker_history(pair, tick_interval)
|
data = exchange.get_ticker_history(pair, tick_interval)
|
||||||
filename = '{}-{}.json'.format(pair, tick_interval)
|
pair_print = pair.replace('/', '_')
|
||||||
|
filename = '{}-{}.json'.format(pair_print, tick_interval)
|
||||||
|
data = sorted(data, key=lambda d: d['T'])
|
||||||
misc.file_dump_json(filename, data)
|
misc.file_dump_json(filename, data)
|
||||||
|
30
freqtrade/tests/testdata/pairs.json
vendored
30
freqtrade/tests/testdata/pairs.json
vendored
@ -1,26 +1,10 @@
|
|||||||
[
|
[
|
||||||
"BTC_ADA",
|
"ADA/BTC",
|
||||||
"BTC_BAT",
|
"DASH/BTC",
|
||||||
"BTC_DASH",
|
"ETH/BTC",
|
||||||
"BTC_ETC",
|
"POWR/BTC",
|
||||||
"BTC_ETH",
|
"LTC/BTC",
|
||||||
"BTC_GBYTE",
|
"MEME/BTC",
|
||||||
"BTC_LSK",
|
"CFI/BTC"
|
||||||
"BTC_LTC",
|
|
||||||
"BTC_NEO",
|
|
||||||
"BTC_NXT",
|
|
||||||
"BTC_POWR",
|
|
||||||
"BTC_STORJ",
|
|
||||||
"BTC_QTUM",
|
|
||||||
"BTC_WAVES",
|
|
||||||
"BTC_VTC",
|
|
||||||
"BTC_XLM",
|
|
||||||
"BTC_XMR",
|
|
||||||
"BTC_XVG",
|
|
||||||
"BTC_XRP",
|
|
||||||
"BTC_ZEC",
|
|
||||||
"USDT_BTC",
|
|
||||||
"USDT_LTC",
|
|
||||||
"USDT_ETH"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ hyperopt==0.1
|
|||||||
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
|
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
|
||||||
networkx==1.11
|
networkx==1.11
|
||||||
tabulate==0.8.2
|
tabulate==0.8.2
|
||||||
pymarketcap==3.3.153
|
pymarketcap==3.3.158
|
||||||
|
|
||||||
# Required for plotting data
|
# Required for plotting data
|
||||||
#plotly==2.3.0
|
#plotly==2.3.0
|
||||||
|
@ -26,16 +26,15 @@ def hyperopt_optimize_conf() -> dict:
|
|||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"BTC_ETH",
|
"ETH/BTC",
|
||||||
"BTC_LTC",
|
"LTC/BTC",
|
||||||
"BTC_ETC",
|
"ETC/BTC",
|
||||||
"BTC_DASH",
|
"DASH/BTC",
|
||||||
"BTC_ZEC",
|
"ZEC/BTC",
|
||||||
"BTC_XLM",
|
"XLM/BTC",
|
||||||
"BTC_NXT",
|
"POWR/BTC",
|
||||||
"BTC_POWR",
|
"ADA/BTC",
|
||||||
"BTC_ADA",
|
"XMR/BTC"
|
||||||
"BTC_XMR"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user