mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
commit
a79ff1c6c9
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -81,6 +81,7 @@ target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
*.ipynb
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
|
71
config_kraken.json.example
Normal file
71
config_kraken.json.example
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"max_open_trades": 5,
|
||||||
|
"stake_currency": "EUR",
|
||||||
|
"stake_amount": 10,
|
||||||
|
"fiat_display_currency": "EUR",
|
||||||
|
"ticker_interval" : "5m",
|
||||||
|
"dry_run": true,
|
||||||
|
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
|
||||||
|
"trailing_stop": false,
|
||||||
|
"unfilledtimeout": {
|
||||||
|
"buy": 10,
|
||||||
|
"sell": 30
|
||||||
|
},
|
||||||
|
"bid_strategy": {
|
||||||
|
"ask_last_balance": 0.0,
|
||||||
|
"use_order_book": false,
|
||||||
|
"order_book_top": 1,
|
||||||
|
"check_depth_of_market": {
|
||||||
|
"enabled": false,
|
||||||
|
"bids_to_ask_delta": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ask_strategy":{
|
||||||
|
"use_order_book": false,
|
||||||
|
"order_book_min": 1,
|
||||||
|
"order_book_max": 9
|
||||||
|
},
|
||||||
|
"exchange": {
|
||||||
|
"name": "kraken",
|
||||||
|
"key": "",
|
||||||
|
"secret": "",
|
||||||
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
|
"ccxt_async_config": {
|
||||||
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 1000
|
||||||
|
},
|
||||||
|
"pair_whitelist": [
|
||||||
|
"ETH/EUR",
|
||||||
|
"BTC/EUR",
|
||||||
|
"BCH/EUR"
|
||||||
|
],
|
||||||
|
"pair_blacklist": [
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"edge": {
|
||||||
|
"enabled": false,
|
||||||
|
"process_throttle_secs": 3600,
|
||||||
|
"calculate_since_number_of_days": 7,
|
||||||
|
"capital_available_percentage": 0.5,
|
||||||
|
"allowed_risk": 0.01,
|
||||||
|
"stoploss_range_min": -0.01,
|
||||||
|
"stoploss_range_max": -0.1,
|
||||||
|
"stoploss_range_step": -0.01,
|
||||||
|
"minimum_winrate": 0.60,
|
||||||
|
"minimum_expectancy": 0.20,
|
||||||
|
"min_trade_number": 10,
|
||||||
|
"max_trade_duration_minute": 1440,
|
||||||
|
"remove_pumps": false
|
||||||
|
},
|
||||||
|
"telegram": {
|
||||||
|
"enabled": false,
|
||||||
|
"token": "",
|
||||||
|
"chat_id": ""
|
||||||
|
},
|
||||||
|
"initial_state": "running",
|
||||||
|
"forcebuy_enable": false,
|
||||||
|
"internals": {
|
||||||
|
"process_throttle_secs": 5
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
""" FreqTrade bot """
|
""" FreqTrade bot """
|
||||||
__version__ = '0.18.1-dev'
|
__version__ = '0.18.2-dev'
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(BaseException):
|
class DependencyException(BaseException):
|
||||||
|
|
|
@ -67,6 +67,7 @@ def retrier(f):
|
||||||
class Exchange(object):
|
class Exchange(object):
|
||||||
|
|
||||||
_conf: Dict = {}
|
_conf: Dict = {}
|
||||||
|
_params: Dict = {}
|
||||||
|
|
||||||
def __init__(self, config: dict) -> None:
|
def __init__(self, config: dict) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -303,11 +304,12 @@ class Exchange(object):
|
||||||
amount = self.symbol_amount_prec(pair, amount)
|
amount = self.symbol_amount_prec(pair, amount)
|
||||||
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
|
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
|
||||||
|
|
||||||
if time_in_force == 'gtc':
|
params = self._params.copy()
|
||||||
return self._api.create_order(pair, ordertype, 'buy', amount, rate)
|
if time_in_force != 'gtc':
|
||||||
else:
|
params.update({'timeInForce': time_in_force})
|
||||||
return self._api.create_order(pair, ordertype, 'buy',
|
|
||||||
amount, rate, {'timeInForce': time_in_force})
|
return self._api.create_order(pair, ordertype, 'buy',
|
||||||
|
amount, rate, params)
|
||||||
|
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
|
@ -346,11 +348,12 @@ class Exchange(object):
|
||||||
amount = self.symbol_amount_prec(pair, amount)
|
amount = self.symbol_amount_prec(pair, amount)
|
||||||
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
|
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
|
||||||
|
|
||||||
if time_in_force == 'gtc':
|
params = self._params.copy()
|
||||||
return self._api.create_order(pair, ordertype, 'sell', amount, rate)
|
if time_in_force != 'gtc':
|
||||||
else:
|
params.update({'timeInForce': time_in_force})
|
||||||
return self._api.create_order(pair, ordertype, 'sell',
|
|
||||||
amount, rate, {'timeInForce': time_in_force})
|
return self._api.create_order(pair, ordertype, 'sell',
|
||||||
|
amount, rate, params)
|
||||||
|
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
|
@ -402,8 +405,12 @@ class Exchange(object):
|
||||||
return self._dry_run_open_orders[order_id]
|
return self._dry_run_open_orders[order_id]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
params = self._params.copy()
|
||||||
|
params.update({'stopPrice': stop_price})
|
||||||
|
|
||||||
order = self._api.create_order(pair, 'stop_loss_limit', 'sell',
|
order = self._api.create_order(pair, 'stop_loss_limit', 'sell',
|
||||||
amount, rate, {'stopPrice': stop_price})
|
amount, rate, params)
|
||||||
logger.info('stoploss limit order added for %s. '
|
logger.info('stoploss limit order added for %s. '
|
||||||
'stop price: %s. limit: %s' % (pair, stop_price, rate))
|
'stop price: %s. limit: %s' % (pair, stop_price, rate))
|
||||||
return order
|
return order
|
||||||
|
@ -541,8 +548,8 @@ class Exchange(object):
|
||||||
|
|
||||||
# Gather coroutines to run
|
# Gather coroutines to run
|
||||||
for pair, ticker_interval in set(pair_list):
|
for pair, ticker_interval in set(pair_list):
|
||||||
if not ((pair, ticker_interval) in self._klines) \
|
if (not ((pair, ticker_interval) in self._klines)
|
||||||
or self._now_is_time_to_refresh(pair, ticker_interval):
|
or self._now_is_time_to_refresh(pair, ticker_interval)):
|
||||||
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
|
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
|
||||||
else:
|
else:
|
||||||
logger.debug("Using cached ohlcv data for %s, %s ...", pair, ticker_interval)
|
logger.debug("Using cached ohlcv data for %s, %s ...", pair, ticker_interval)
|
||||||
|
|
12
freqtrade/exchange/kraken.py
Normal file
12
freqtrade/exchange/kraken.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
""" Kraken exchange subclass """
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Kraken(Exchange):
|
||||||
|
|
||||||
|
_params: Dict = {"trading_agreement": "agree"}
|
|
@ -17,10 +17,9 @@ from freqtrade import (DependencyException, OperationalException,
|
||||||
from freqtrade.data.converter import order_book_to_dataframe
|
from freqtrade.data.converter import order_book_to_dataframe
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
from freqtrade.exchange import Exchange
|
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc import RPCManager, RPCMessageType
|
from freqtrade.rpc import RPCManager, RPCMessageType
|
||||||
from freqtrade.resolvers import StrategyResolver, PairListResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from freqtrade.strategy.interface import SellType, IStrategy
|
from freqtrade.strategy.interface import SellType, IStrategy
|
||||||
from freqtrade.wallets import Wallets
|
from freqtrade.wallets import Wallets
|
||||||
|
@ -55,7 +54,10 @@ class FreqtradeBot(object):
|
||||||
self.strategy: IStrategy = StrategyResolver(self.config).strategy
|
self.strategy: IStrategy = StrategyResolver(self.config).strategy
|
||||||
|
|
||||||
self.rpc: RPCManager = RPCManager(self)
|
self.rpc: RPCManager = RPCManager(self)
|
||||||
self.exchange = Exchange(self.config)
|
|
||||||
|
exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title()
|
||||||
|
self.exchange = ExchangeResolver(exchange_name, self.config).exchange
|
||||||
|
|
||||||
self.wallets = Wallets(self.exchange)
|
self.wallets = Wallets(self.exchange)
|
||||||
self.dataprovider = DataProvider(self.config, self.exchange)
|
self.dataprovider = DataProvider(self.config, self.exchange)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from freqtrade.resolvers.iresolver import IResolver # noqa: F401
|
from freqtrade.resolvers.iresolver import IResolver # noqa: F401
|
||||||
|
from freqtrade.resolvers.exchange_resolver import ExchangeResolver # noqa: F401
|
||||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
|
||||||
from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401
|
from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401
|
||||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401
|
from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401
|
||||||
|
|
55
freqtrade/resolvers/exchange_resolver.py
Normal file
55
freqtrade/resolvers/exchange_resolver.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
"""
|
||||||
|
This module loads custom exchanges
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
from freqtrade.resolvers import IResolver
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExchangeResolver(IResolver):
|
||||||
|
"""
|
||||||
|
This class contains all the logic to load a custom exchange class
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ['exchange']
|
||||||
|
|
||||||
|
def __init__(self, exchange_name: str, config: dict) -> None:
|
||||||
|
"""
|
||||||
|
Load the custom class from config parameter
|
||||||
|
:param config: configuration dictionary or None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.exchange = self._load_exchange(exchange_name, kwargs={'config': config})
|
||||||
|
except ImportError:
|
||||||
|
logger.info(
|
||||||
|
f"No {exchange_name} specific subclass found. Using the generic class instead.")
|
||||||
|
self.exchange = Exchange(config)
|
||||||
|
|
||||||
|
def _load_exchange(
|
||||||
|
self, exchange_name: str, kwargs: dict) -> Exchange:
|
||||||
|
"""
|
||||||
|
Search and loads the specified exchange.
|
||||||
|
:param exchange_name: name of the module to import
|
||||||
|
:param extra_dir: additional directory to search for the given exchange
|
||||||
|
:return: Exchange instance or None
|
||||||
|
"""
|
||||||
|
abs_path = Path(__file__).parent.parent.joinpath('exchange').resolve()
|
||||||
|
|
||||||
|
try:
|
||||||
|
exchange = self._search_object(directory=abs_path, object_type=Exchange,
|
||||||
|
object_name=exchange_name,
|
||||||
|
kwargs=kwargs)
|
||||||
|
if exchange:
|
||||||
|
logger.info("Using resolved exchange %s from '%s'", exchange_name, abs_path)
|
||||||
|
return exchange
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.warning('Path "%s" does not exist', abs_path.relative_to(Path.cwd()))
|
||||||
|
|
||||||
|
raise ImportError(
|
||||||
|
"Impossible to load Exchange '{}'. This class does not exist"
|
||||||
|
" or contains Python code errors".format(exchange_name)
|
||||||
|
)
|
|
@ -63,7 +63,7 @@ class HyperOptResolver(IResolver):
|
||||||
hyperopt = self._search_object(directory=_path, object_type=IHyperOpt,
|
hyperopt = self._search_object(directory=_path, object_type=IHyperOpt,
|
||||||
object_name=hyperopt_name)
|
object_name=hyperopt_name)
|
||||||
if hyperopt:
|
if hyperopt:
|
||||||
logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, _path)
|
logger.info("Using resolved hyperopt %s from '%s'", hyperopt_name, _path)
|
||||||
return hyperopt
|
return hyperopt
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
|
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
|
||||||
|
|
|
@ -47,7 +47,7 @@ class IResolver(object):
|
||||||
:param directory: relative or absolute directory path
|
:param directory: relative or absolute directory path
|
||||||
:return: object instance
|
:return: object instance
|
||||||
"""
|
"""
|
||||||
logger.debug('Searching for %s %s in \'%s\'', object_type.__name__, object_name, directory)
|
logger.debug("Searching for %s %s in '%s'", object_type.__name__, object_name, directory)
|
||||||
for entry in directory.iterdir():
|
for entry in directory.iterdir():
|
||||||
# Only consider python files
|
# Only consider python files
|
||||||
if not str(entry).endswith('.py'):
|
if not str(entry).endswith('.py'):
|
||||||
|
|
|
@ -48,7 +48,7 @@ class PairListResolver(IResolver):
|
||||||
object_name=pairlist_name,
|
object_name=pairlist_name,
|
||||||
kwargs=kwargs)
|
kwargs=kwargs)
|
||||||
if pairlist:
|
if pairlist:
|
||||||
logger.info('Using resolved pairlist %s from \'%s\'', pairlist_name, _path)
|
logger.info("Using resolved pairlist %s from '%s'", pairlist_name, _path)
|
||||||
return pairlist
|
return pairlist
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
|
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
|
||||||
|
|
|
@ -149,7 +149,7 @@ class StrategyResolver(IResolver):
|
||||||
strategy = self._search_object(directory=_path, object_type=IStrategy,
|
strategy = self._search_object(directory=_path, object_type=IStrategy,
|
||||||
object_name=strategy_name, kwargs={'config': config})
|
object_name=strategy_name, kwargs={'config': config})
|
||||||
if strategy:
|
if strategy:
|
||||||
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, _path)
|
logger.info("Using resolved strategy %s from '%s'", strategy_name, _path)
|
||||||
strategy._populate_fun_len = len(
|
strategy._populate_fun_len = len(
|
||||||
getfullargspec(strategy.populate_indicators).args)
|
getfullargspec(strategy.populate_indicators).args)
|
||||||
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
|
||||||
|
|
|
@ -16,6 +16,7 @@ from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.edge import Edge, PairInfo
|
from freqtrade.edge import Edge, PairInfo
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
|
||||||
logging.getLogger('').setLevel(logging.INFO)
|
logging.getLogger('').setLevel(logging.INFO)
|
||||||
|
|
||||||
|
@ -49,7 +50,11 @@ def patch_exchange(mocker, api_mock=None, id='bittrex') -> None:
|
||||||
|
|
||||||
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange:
|
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange:
|
||||||
patch_exchange(mocker, api_mock, id)
|
patch_exchange(mocker, api_mock, id)
|
||||||
exchange = Exchange(config)
|
config["exchange"]["name"] = id
|
||||||
|
try:
|
||||||
|
exchange = ExchangeResolver(id.title(), config).exchange
|
||||||
|
except ImportError:
|
||||||
|
exchange = Exchange(config)
|
||||||
return exchange
|
return exchange
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade import DependencyException, OperationalException, TemporaryError
|
from freqtrade import DependencyException, OperationalException, TemporaryError
|
||||||
from freqtrade.exchange import API_RETRY_COUNT, Exchange
|
from freqtrade.exchange import API_RETRY_COUNT, Exchange
|
||||||
from freqtrade.tests.conftest import get_patched_exchange, log_has
|
from freqtrade.tests.conftest import get_patched_exchange, log_has, log_has_re
|
||||||
|
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||||
|
|
||||||
|
|
||||||
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
|
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
|
||||||
|
@ -106,6 +107,23 @@ def test_init_exception(default_conf, mocker):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exchange_resolver(default_conf, mocker, caplog):
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=MagicMock()))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||||
|
exchange = ExchangeResolver('Binance', default_conf).exchange
|
||||||
|
assert isinstance(exchange, Exchange)
|
||||||
|
assert log_has_re(r"No .* specific subclass found. Using the generic class instead.",
|
||||||
|
caplog.record_tuples)
|
||||||
|
caplog.clear()
|
||||||
|
|
||||||
|
exchange = ExchangeResolver('Kraken', default_conf).exchange
|
||||||
|
assert isinstance(exchange, Exchange)
|
||||||
|
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
|
||||||
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_symbol_amount_prec(default_conf, mocker):
|
def test_symbol_amount_prec(default_conf, mocker):
|
||||||
'''
|
'''
|
||||||
Test rounds down to 4 Decimal places
|
Test rounds down to 4 Decimal places
|
||||||
|
@ -531,6 +549,67 @@ def test_buy_considers_time_in_force(default_conf, mocker):
|
||||||
assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc'}
|
assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_buy_kraken_trading_agreement(default_conf, mocker):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||||
|
order_type = 'market'
|
||||||
|
time_in_force = 'ioc'
|
||||||
|
api_mock.create_order = MagicMock(return_value={
|
||||||
|
'id': order_id,
|
||||||
|
'info': {
|
||||||
|
'foo': 'bar'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
default_conf['dry_run'] = False
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||||
|
|
||||||
|
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||||
|
amount=1, rate=200, time_in_force=time_in_force)
|
||||||
|
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'info' in order
|
||||||
|
assert order['id'] == order_id
|
||||||
|
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||||
|
assert api_mock.create_order.call_args[0][1] == order_type
|
||||||
|
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||||
|
assert api_mock.create_order.call_args[0][3] == 1
|
||||||
|
assert api_mock.create_order.call_args[0][4] is None
|
||||||
|
assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc',
|
||||||
|
'trading_agreement': 'agree'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_sell_kraken_trading_agreement(default_conf, mocker):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
|
||||||
|
order_type = 'market'
|
||||||
|
api_mock.create_order = MagicMock(return_value={
|
||||||
|
'id': order_id,
|
||||||
|
'info': {
|
||||||
|
'foo': 'bar'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
default_conf['dry_run'] = False
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||||
|
|
||||||
|
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||||
|
|
||||||
|
assert 'id' in order
|
||||||
|
assert 'info' in order
|
||||||
|
assert order['id'] == order_id
|
||||||
|
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||||
|
assert api_mock.create_order.call_args[0][1] == order_type
|
||||||
|
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||||
|
assert api_mock.create_order.call_args[0][3] == 1
|
||||||
|
assert api_mock.create_order.call_args[0][4] is None
|
||||||
|
assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'}
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
|
@ -552,11 +631,12 @@ def test_sell_prod(default_conf, mocker):
|
||||||
})
|
})
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
|
||||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
assert order['id'] == order_id
|
assert order['id'] == order_id
|
||||||
|
|
Loading…
Reference in New Issue
Block a user