This commit is contained in:
enenn 2018-02-09 21:45:58 +00:00 committed by GitHub
commit 74a3a822ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1053 additions and 1164 deletions

74
Vagrantfile vendored Normal file
View 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
View File

View 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": {

View File

@ -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
``` ```

View File

@ -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.

View 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.
"""

View File

@ -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 ?

View File

@ -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']]

View File

@ -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
},
...
"""

View File

@ -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)
@ -467,13 +469,12 @@ def gen_pair_whitelist(base_currency: str, key: str = 'BaseVolume') -> List[str]
: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:

View File

@ -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',

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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))

View 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

View File

@ -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)

View File

@ -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

View File

@ -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'}

View File

@ -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

View File

@ -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))

View 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'] = []

View File

@ -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):

View File

@ -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]]

View File

@ -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

View File

@ -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(

View File

@ -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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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}
]

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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"}]

Binary file not shown.

View File

@ -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)

View File

@ -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"
] ]

View File

@ -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

View File

@ -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"
] ]
} }
} }