Merge pull request #122 from gcarq/feature/fix-signal-handling
fix signal handling
This commit is contained in:
commit
765a762ccf
@ -1,4 +1,16 @@
|
|||||||
""" FreqTrade bot """
|
""" FreqTrade bot """
|
||||||
__version__ = '0.14.3'
|
__version__ = '0.14.3'
|
||||||
|
|
||||||
from . import main
|
|
||||||
|
class DependencyException(BaseException):
|
||||||
|
"""
|
||||||
|
Indicates that a assumed dependency is not met.
|
||||||
|
This could happen when there is currently not enough money on the account.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class OperationalException(BaseException):
|
||||||
|
"""
|
||||||
|
Requires manual intervention.
|
||||||
|
This happens when an exchange returns an unexpected error during runtime.
|
||||||
|
"""
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Functions to analyze ticker data with indicators and produce buy and sell signals
|
Functions to analyze ticker data with indicators and produce buy and sell signals
|
||||||
"""
|
"""
|
||||||
from enum import Enum
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import talib.abstract as ta
|
import talib.abstract as ta
|
||||||
|
@ -9,6 +9,7 @@ import arrow
|
|||||||
import requests
|
import requests
|
||||||
from cachetools import cached, TTLCache
|
from cachetools import cached, TTLCache
|
||||||
|
|
||||||
|
from freqtrade import OperationalException
|
||||||
from freqtrade.exchange.bittrex import Bittrex
|
from freqtrade.exchange.bittrex import Bittrex
|
||||||
from freqtrade.exchange.interface import Exchange
|
from freqtrade.exchange.interface import Exchange
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ def init(config: dict) -> None:
|
|||||||
try:
|
try:
|
||||||
exchange_class = Exchanges[name.upper()].value
|
exchange_class = Exchanges[name.upper()].value
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise RuntimeError('Exchange {} is not supported'.format(name))
|
raise OperationalException('Exchange {} is not supported'.format(name))
|
||||||
|
|
||||||
_API = exchange_class(exchange_config)
|
_API = exchange_class(exchange_config)
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ def init(config: dict) -> None:
|
|||||||
def validate_pairs(pairs: List[str]) -> None:
|
def validate_pairs(pairs: List[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Checks if all given pairs are tradable on the current exchange.
|
Checks if all given pairs are tradable on the current exchange.
|
||||||
Raises RuntimeError if one pair is not available.
|
Raises OperationalException if one pair is not available.
|
||||||
:param pairs: list of pairs
|
:param pairs: list of pairs
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
@ -75,11 +76,12 @@ def validate_pairs(pairs: List[str]) -> None:
|
|||||||
stake_cur = _CONF['stake_currency']
|
stake_cur = _CONF['stake_currency']
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
if not pair.startswith(stake_cur):
|
if not pair.startswith(stake_cur):
|
||||||
raise RuntimeError(
|
raise OperationalException(
|
||||||
'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur)
|
'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur)
|
||||||
)
|
)
|
||||||
if pair not in markets:
|
if pair not in markets:
|
||||||
raise RuntimeError('Pair {} is not available at {}'.format(pair, _API.name.lower()))
|
raise OperationalException(
|
||||||
|
'Pair {} is not available at {}'.format(pair, _API.name.lower()))
|
||||||
|
|
||||||
|
|
||||||
def buy(pair: str, rate: float, amount: float) -> str:
|
def buy(pair: str, rate: float, amount: float) -> str:
|
||||||
|
@ -4,6 +4,7 @@ from typing import List, Dict
|
|||||||
from bittrex.bittrex import Bittrex as _Bittrex, API_V2_0, API_V1_1
|
from bittrex.bittrex import Bittrex as _Bittrex, API_V2_0, API_V1_1
|
||||||
from requests.exceptions import ContentDecodingError
|
from requests.exceptions import ContentDecodingError
|
||||||
|
|
||||||
|
from freqtrade import OperationalException
|
||||||
from freqtrade.exchange.interface import Exchange
|
from freqtrade.exchange.interface import Exchange
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -46,7 +47,7 @@ class Bittrex(Exchange):
|
|||||||
def buy(self, pair: str, rate: float, amount: float) -> str:
|
def buy(self, pair: str, rate: float, amount: float) -> str:
|
||||||
data = _API.buy_limit(pair.replace('_', '-'), amount, rate)
|
data = _API.buy_limit(pair.replace('_', '-'), amount, rate)
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message} params=({pair}, {rate}, {amount})'.format(
|
raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format(
|
||||||
message=data['message'],
|
message=data['message'],
|
||||||
pair=pair,
|
pair=pair,
|
||||||
rate=rate,
|
rate=rate,
|
||||||
@ -56,7 +57,7 @@ class Bittrex(Exchange):
|
|||||||
def sell(self, pair: str, rate: float, amount: float) -> str:
|
def sell(self, pair: str, rate: float, amount: float) -> str:
|
||||||
data = _API.sell_limit(pair.replace('_', '-'), amount, rate)
|
data = _API.sell_limit(pair.replace('_', '-'), amount, rate)
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message} params=({pair}, {rate}, {amount})'.format(
|
raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format(
|
||||||
message=data['message'],
|
message=data['message'],
|
||||||
pair=pair,
|
pair=pair,
|
||||||
rate=rate,
|
rate=rate,
|
||||||
@ -66,7 +67,7 @@ class Bittrex(Exchange):
|
|||||||
def get_balance(self, currency: str) -> float:
|
def get_balance(self, currency: str) -> float:
|
||||||
data = _API.get_balance(currency)
|
data = _API.get_balance(currency)
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message} params=({currency})'.format(
|
raise OperationalException('{message} params=({currency})'.format(
|
||||||
message=data['message'],
|
message=data['message'],
|
||||||
currency=currency))
|
currency=currency))
|
||||||
return float(data['result']['Balance'] or 0.0)
|
return float(data['result']['Balance'] or 0.0)
|
||||||
@ -74,13 +75,13 @@ class Bittrex(Exchange):
|
|||||||
def get_balances(self):
|
def get_balances(self):
|
||||||
data = _API.get_balances()
|
data = _API.get_balances()
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message}'.format(message=data['message']))
|
raise OperationalException('{message}'.format(message=data['message']))
|
||||||
return data['result']
|
return data['result']
|
||||||
|
|
||||||
def get_ticker(self, pair: str) -> dict:
|
def get_ticker(self, pair: str) -> dict:
|
||||||
data = _API.get_ticker(pair.replace('_', '-'))
|
data = _API.get_ticker(pair.replace('_', '-'))
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message} params=({pair})'.format(
|
raise OperationalException('{message} params=({pair})'.format(
|
||||||
message=data['message'],
|
message=data['message'],
|
||||||
pair=pair))
|
pair=pair))
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ class Bittrex(Exchange):
|
|||||||
pair=pair))
|
pair=pair))
|
||||||
|
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message} params=({pair})'.format(
|
raise OperationalException('{message} params=({pair})'.format(
|
||||||
message=data['message'],
|
message=data['message'],
|
||||||
pair=pair))
|
pair=pair))
|
||||||
|
|
||||||
@ -130,7 +131,7 @@ class Bittrex(Exchange):
|
|||||||
def get_order(self, order_id: str) -> Dict:
|
def get_order(self, order_id: str) -> Dict:
|
||||||
data = _API.get_order(order_id)
|
data = _API.get_order(order_id)
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message} params=({order_id})'.format(
|
raise OperationalException('{message} params=({order_id})'.format(
|
||||||
message=data['message'],
|
message=data['message'],
|
||||||
order_id=order_id))
|
order_id=order_id))
|
||||||
data = data['result']
|
data = data['result']
|
||||||
@ -148,7 +149,7 @@ class Bittrex(Exchange):
|
|||||||
def cancel_order(self, order_id: str) -> None:
|
def cancel_order(self, order_id: str) -> None:
|
||||||
data = _API.cancel(order_id)
|
data = _API.cancel(order_id)
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message} params=({order_id})'.format(
|
raise OperationalException('{message} params=({order_id})'.format(
|
||||||
message=data['message'],
|
message=data['message'],
|
||||||
order_id=order_id))
|
order_id=order_id))
|
||||||
|
|
||||||
@ -158,19 +159,19 @@ class Bittrex(Exchange):
|
|||||||
def get_markets(self) -> List[str]:
|
def get_markets(self) -> List[str]:
|
||||||
data = _API.get_markets()
|
data = _API.get_markets()
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message}'.format(message=data['message']))
|
raise OperationalException('{message}'.format(message=data['message']))
|
||||||
return [m['MarketName'].replace('-', '_') for m in data['result']]
|
return [m['MarketName'].replace('-', '_') for m in data['result']]
|
||||||
|
|
||||||
def get_market_summaries(self) -> List[Dict]:
|
def get_market_summaries(self) -> List[Dict]:
|
||||||
data = _API.get_market_summaries()
|
data = _API.get_market_summaries()
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message}'.format(message=data['message']))
|
raise OperationalException('{message}'.format(message=data['message']))
|
||||||
return data['result']
|
return data['result']
|
||||||
|
|
||||||
def get_wallet_health(self) -> List[Dict]:
|
def get_wallet_health(self) -> List[Dict]:
|
||||||
data = _API_V2.get_wallet_health()
|
data = _API_V2.get_wallet_health()
|
||||||
if not data['success']:
|
if not data['success']:
|
||||||
raise RuntimeError('{message}'.format(message=data['message']))
|
raise OperationalException('{message}'.format(message=data['message']))
|
||||||
return [{
|
return [{
|
||||||
'Currency': entry['Health']['Currency'],
|
'Currency': entry['Health']['Currency'],
|
||||||
'IsActive': entry['Health']['IsActive'],
|
'IsActive': entry['Health']['IsActive'],
|
||||||
|
@ -6,16 +6,16 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from signal import signal, SIGINT, SIGABRT, SIGTERM
|
|
||||||
from typing import Dict, Optional, List
|
from typing import Dict, Optional, List
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from cachetools import cached, TTLCache
|
from cachetools import cached, TTLCache
|
||||||
|
|
||||||
from freqtrade import __version__, exchange, persistence, rpc
|
from freqtrade import __version__, exchange, persistence, rpc, DependencyException, \
|
||||||
|
OperationalException
|
||||||
from freqtrade.analyze import get_signal, SignalType
|
from freqtrade.analyze import get_signal, SignalType
|
||||||
from freqtrade.misc import State, get_state, update_state, parse_args, throttle, \
|
from freqtrade.misc import State, get_state, update_state, parse_args, throttle, \
|
||||||
load_config, FreqtradeException
|
load_config
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
logger = logging.getLogger('freqtrade')
|
logger = logging.getLogger('freqtrade')
|
||||||
@ -76,7 +76,7 @@ def _process(dynamic_whitelist: Optional[bool] = False) -> bool:
|
|||||||
'Checked all whitelisted currencies. '
|
'Checked all whitelisted currencies. '
|
||||||
'Found no suitable entry positions for buying. Will keep looking ...'
|
'Found no suitable entry positions for buying. Will keep looking ...'
|
||||||
)
|
)
|
||||||
except FreqtradeException as e:
|
except DependencyException as e:
|
||||||
logger.warning('Unable to create trade: %s', e)
|
logger.warning('Unable to create trade: %s', e)
|
||||||
|
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
@ -97,12 +97,12 @@ def _process(dynamic_whitelist: Optional[bool] = False) -> bool:
|
|||||||
error
|
error
|
||||||
)
|
)
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
except RuntimeError:
|
except OperationalException:
|
||||||
rpc.send_msg('*Status:* Got RuntimeError:\n```\n{traceback}```{hint}'.format(
|
rpc.send_msg('*Status:* Got OperationalException:\n```\n{traceback}```{hint}'.format(
|
||||||
traceback=traceback.format_exc(),
|
traceback=traceback.format_exc(),
|
||||||
hint='Issue `/start` if you think it is safe to restart.'
|
hint='Issue `/start` if you think it is safe to restart.'
|
||||||
))
|
))
|
||||||
logger.exception('Got RuntimeError. Stopping trader ...')
|
logger.exception('Got OperationalException. Stopping trader ...')
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
return state_changed
|
return state_changed
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ def create_trade(stake_amount: float) -> Optional[Trade]:
|
|||||||
whitelist = copy.deepcopy(_CONF['exchange']['pair_whitelist'])
|
whitelist = copy.deepcopy(_CONF['exchange']['pair_whitelist'])
|
||||||
# Check if stake_amount is fulfilled
|
# Check if stake_amount is fulfilled
|
||||||
if exchange.get_balance(_CONF['stake_currency']) < stake_amount:
|
if exchange.get_balance(_CONF['stake_currency']) < stake_amount:
|
||||||
raise FreqtradeException(
|
raise DependencyException(
|
||||||
'stake amount is not fulfilled (currency={})'.format(_CONF['stake_currency'])
|
'stake amount is not fulfilled (currency={})'.format(_CONF['stake_currency'])
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ def create_trade(stake_amount: float) -> Optional[Trade]:
|
|||||||
whitelist.remove(trade.pair)
|
whitelist.remove(trade.pair)
|
||||||
logger.debug('Ignoring %s in pair whitelist', trade.pair)
|
logger.debug('Ignoring %s in pair whitelist', trade.pair)
|
||||||
if not whitelist:
|
if not whitelist:
|
||||||
raise FreqtradeException('No pair in whitelist')
|
raise DependencyException('No pair in whitelist')
|
||||||
|
|
||||||
# Pick pair based on StochRSI buy signals
|
# Pick pair based on StochRSI buy signals
|
||||||
for _pair in whitelist:
|
for _pair in whitelist:
|
||||||
@ -248,10 +248,6 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
|
|||||||
else:
|
else:
|
||||||
update_state(State.STOPPED)
|
update_state(State.STOPPED)
|
||||||
|
|
||||||
# Register signal handlers
|
|
||||||
for sig in (SIGINT, SIGTERM, SIGABRT):
|
|
||||||
signal(sig, cleanup)
|
|
||||||
|
|
||||||
|
|
||||||
@cached(TTLCache(maxsize=1, ttl=1800))
|
@cached(TTLCache(maxsize=1, ttl=1800))
|
||||||
def gen_pair_whitelist(base_currency: str, topn: int = 20, key: str = 'BaseVolume') -> List[str]:
|
def gen_pair_whitelist(base_currency: str, topn: int = 20, key: str = 'BaseVolume') -> List[str]:
|
||||||
@ -270,7 +266,7 @@ def gen_pair_whitelist(base_currency: str, topn: int = 20, key: str = 'BaseVolum
|
|||||||
return [s['MarketName'].replace('-', '_') for s in summaries[:topn]]
|
return [s['MarketName'].replace('-', '_') for s in summaries[:topn]]
|
||||||
|
|
||||||
|
|
||||||
def cleanup(*args, **kwargs) -> None:
|
def cleanup() -> None:
|
||||||
"""
|
"""
|
||||||
Cleanup the application state und finish all pending tasks
|
Cleanup the application state und finish all pending tasks
|
||||||
:return: None
|
:return: None
|
||||||
@ -283,7 +279,7 @@ def cleanup(*args, **kwargs) -> None:
|
|||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
"""
|
"""
|
||||||
Loads and validates the config and handles the main loop
|
Loads and validates the config and handles the main loop
|
||||||
:return: None
|
:return: None
|
||||||
@ -311,24 +307,32 @@ def main():
|
|||||||
# Initialize all modules and start main loop
|
# Initialize all modules and start main loop
|
||||||
if args.dynamic_whitelist:
|
if args.dynamic_whitelist:
|
||||||
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
|
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
|
||||||
init(_CONF)
|
|
||||||
old_state = None
|
|
||||||
while True:
|
|
||||||
new_state = get_state()
|
|
||||||
# Log state transition
|
|
||||||
if new_state != old_state:
|
|
||||||
rpc.send_msg('*Status:* `{}`'.format(new_state.name.lower()))
|
|
||||||
logger.info('Changing state to: %s', new_state.name)
|
|
||||||
|
|
||||||
if new_state == State.STOPPED:
|
try:
|
||||||
time.sleep(1)
|
init(_CONF)
|
||||||
elif new_state == State.RUNNING:
|
old_state = None
|
||||||
throttle(
|
while True:
|
||||||
_process,
|
new_state = get_state()
|
||||||
min_secs=_CONF['internals'].get('process_throttle_secs', 10),
|
# Log state transition
|
||||||
dynamic_whitelist=args.dynamic_whitelist,
|
if new_state != old_state:
|
||||||
)
|
rpc.send_msg('*Status:* `{}`'.format(new_state.name.lower()))
|
||||||
old_state = new_state
|
logger.info('Changing state to: %s', new_state.name)
|
||||||
|
|
||||||
|
if new_state == State.STOPPED:
|
||||||
|
time.sleep(1)
|
||||||
|
elif new_state == State.RUNNING:
|
||||||
|
throttle(
|
||||||
|
_process,
|
||||||
|
min_secs=_CONF['internals'].get('process_throttle_secs', 10),
|
||||||
|
dynamic_whitelist=args.dynamic_whitelist,
|
||||||
|
)
|
||||||
|
old_state = new_state
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info('Got SIGINT, aborting ...')
|
||||||
|
except BaseException:
|
||||||
|
logger.exception('Got fatal exception!')
|
||||||
|
finally:
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -15,10 +15,6 @@ from freqtrade import __version__
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FreqtradeException(BaseException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class State(enum.Enum):
|
class State(enum.Enum):
|
||||||
RUNNING = 0
|
RUNNING = 0
|
||||||
STOPPED = 1
|
STOPPED = 1
|
||||||
|
@ -2,11 +2,11 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Callable, Any
|
from typing import Callable, Any
|
||||||
from pandas import DataFrame
|
|
||||||
from tabulate import tabulate
|
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
from pandas import DataFrame
|
||||||
from sqlalchemy import and_, func, text
|
from sqlalchemy import and_, func, text
|
||||||
|
from tabulate import tabulate
|
||||||
from telegram import ParseMode, Bot, Update
|
from telegram import ParseMode, Bot, Update
|
||||||
from telegram.error import NetworkError, TelegramError
|
from telegram.error import NetworkError, TelegramError
|
||||||
from telegram.ext import CommandHandler, Updater
|
from telegram.ext import CommandHandler, Updater
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# pragma pylint: disable=missing-docstring,W0621
|
# pragma pylint: disable=missing-docstring,W0621
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
@ -3,6 +3,7 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from freqtrade import OperationalException
|
||||||
from freqtrade.exchange import validate_pairs
|
from freqtrade.exchange import validate_pairs
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ def test_validate_pairs_not_available(default_conf, mocker):
|
|||||||
api_mock.get_markets = MagicMock(return_value=[])
|
api_mock.get_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(RuntimeError, match=r'not available'):
|
with pytest.raises(OperationalException, match=r'not available'):
|
||||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
|
|
||||||
@ -31,5 +32,5 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
|
|||||||
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)
|
||||||
with pytest.raises(RuntimeError, match=r'not compatible'):
|
with pytest.raises(OperationalException, match=r'not compatible'):
|
||||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||||
|
@ -6,11 +6,12 @@ import pytest
|
|||||||
import requests
|
import requests
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
from freqtrade.exchange import Exchanges
|
from freqtrade import DependencyException, OperationalException
|
||||||
from freqtrade.analyze import SignalType
|
from freqtrade.analyze import SignalType
|
||||||
|
from freqtrade.exchange import Exchanges
|
||||||
from freqtrade.main import create_trade, handle_trade, init, \
|
from freqtrade.main import create_trade, handle_trade, init, \
|
||||||
get_target_bid, _process
|
get_target_bid, _process
|
||||||
from freqtrade.misc import get_state, State, FreqtradeException
|
from freqtrade.misc import get_state, State
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ def test_process_exchange_failures(default_conf, ticker, health, mocker):
|
|||||||
assert sleep_mock.has_calls()
|
assert sleep_mock.has_calls()
|
||||||
|
|
||||||
|
|
||||||
def test_process_runtime_error(default_conf, ticker, health, mocker):
|
def test_process_operational_exception(default_conf, ticker, health, mocker):
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=msg_mock)
|
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=msg_mock)
|
||||||
@ -68,14 +69,14 @@ def test_process_runtime_error(default_conf, ticker, health, mocker):
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_wallet_health=health,
|
get_wallet_health=health,
|
||||||
buy=MagicMock(side_effect=RuntimeError))
|
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()
|
result = _process()
|
||||||
assert result is False
|
assert result is False
|
||||||
assert get_state() == State.STOPPED
|
assert get_state() == State.STOPPED
|
||||||
assert 'RuntimeError' in msg_mock.call_args_list[-1][0][0]
|
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker):
|
def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker):
|
||||||
@ -141,7 +142,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker):
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
buy=MagicMock(return_value='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(FreqtradeException, match=r'.*stake amount.*'):
|
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||||
create_trade(default_conf['stake_amount'])
|
create_trade(default_conf['stake_amount'])
|
||||||
|
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker):
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value='mocked_limit_buy'))
|
buy=MagicMock(return_value='mocked_limit_buy'))
|
||||||
|
|
||||||
with pytest.raises(FreqtradeException, 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)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103
|
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from freqtrade.rpc import init, cleanup, send_msg
|
from freqtrade.rpc import init, cleanup, send_msg
|
||||||
|
|
||||||
|
8
freqtrade/vendor/qtpylib/indicators.py
vendored
8
freqtrade/vendor/qtpylib/indicators.py
vendored
@ -19,12 +19,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import warnings
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from pandas.core.base import PandasObject
|
from pandas.core.base import PandasObject
|
||||||
|
|
||||||
# =============================================
|
# =============================================
|
||||||
|
Loading…
Reference in New Issue
Block a user