commit
efd69b2cd5
@ -72,7 +72,7 @@ class Arguments(object):
|
|||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
'--version',
|
'--version',
|
||||||
action='version',
|
action='version',
|
||||||
version='%(prog)s {}'.format(__version__),
|
version=f'%(prog)s {__version__}'
|
||||||
)
|
)
|
||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
'-c', '--config',
|
'-c', '--config',
|
||||||
|
@ -236,9 +236,8 @@ class Configuration(object):
|
|||||||
exchange = config.get('exchange', {}).get('name').lower()
|
exchange = config.get('exchange', {}).get('name').lower()
|
||||||
if exchange not in ccxt.exchanges:
|
if exchange not in ccxt.exchanges:
|
||||||
|
|
||||||
exception_msg = 'Exchange "{}" not supported.\n' \
|
exception_msg = f'Exchange "{exchange}" not supported.\n' \
|
||||||
'The following exchanges are supported: {}'\
|
f'The following exchanges are supported: {", ".join(ccxt.exchanges)}'
|
||||||
.format(exchange, ', '.join(ccxt.exchanges))
|
|
||||||
|
|
||||||
logger.critical(exception_msg)
|
logger.critical(exception_msg)
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
|
@ -59,7 +59,7 @@ def init_ccxt(exchange_config: dict) -> ccxt.Exchange:
|
|||||||
name = exchange_config['name']
|
name = exchange_config['name']
|
||||||
|
|
||||||
if name not in ccxt.exchanges:
|
if name not in ccxt.exchanges:
|
||||||
raise OperationalException('Exchange {} is not supported'.format(name))
|
raise OperationalException(f'Exchange {name} is not supported')
|
||||||
try:
|
try:
|
||||||
api = getattr(ccxt, name.lower())({
|
api = getattr(ccxt, name.lower())({
|
||||||
'apiKey': exchange_config.get('key'),
|
'apiKey': exchange_config.get('key'),
|
||||||
@ -69,7 +69,7 @@ def init_ccxt(exchange_config: dict) -> ccxt.Exchange:
|
|||||||
'enableRateLimit': True,
|
'enableRateLimit': True,
|
||||||
})
|
})
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
raise OperationalException('Exchange {} is not supported'.format(name))
|
raise OperationalException(f'Exchange {name} is not supported')
|
||||||
|
|
||||||
return api
|
return api
|
||||||
|
|
||||||
@ -118,11 +118,10 @@ def validate_pairs(pairs: List[str]) -> None:
|
|||||||
# 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):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur)
|
f'Pair {pair} not compatible with stake_currency: {stake_cur}')
|
||||||
)
|
|
||||||
if pair not in markets:
|
if pair not in markets:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Pair {} is not available at {}'.format(pair, get_name()))
|
f'Pair {pair} is not available at {get_name()}')
|
||||||
|
|
||||||
|
|
||||||
def exchange_has(endpoint: str) -> bool:
|
def exchange_has(endpoint: str) -> bool:
|
||||||
@ -138,7 +137,7 @@ def exchange_has(endpoint: str) -> bool:
|
|||||||
def buy(pair: str, rate: float, amount: float) -> Dict:
|
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 = f'dry_run_buy_{randint(0, 10**6)}'
|
||||||
_DRY_RUN_OPEN_ORDERS[order_id] = {
|
_DRY_RUN_OPEN_ORDERS[order_id] = {
|
||||||
'pair': pair,
|
'pair': pair,
|
||||||
'price': rate,
|
'price': rate,
|
||||||
@ -156,20 +155,17 @@ def buy(pair: str, rate: float, amount: float) -> Dict:
|
|||||||
return _API.create_limit_buy_order(pair, amount, rate)
|
return _API.create_limit_buy_order(pair, amount, rate)
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Insufficient funds to create limit buy order on market {}.'
|
f'Insufficient funds to create limit buy order on market {pair}.'
|
||||||
'Tried to buy amount {} at rate {} (total {}).'
|
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
|
||||||
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
f'Message: {e}')
|
||||||
)
|
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Could not create limit buy order on market {}.'
|
f'Could not create limit buy order on market {pair}.'
|
||||||
'Tried to buy amount {} at rate {} (total {}).'
|
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
|
||||||
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
f'Message: {e}')
|
||||||
)
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not place buy order due to {}. Message: {}'.format(
|
f'Could not place buy order due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -177,7 +173,7 @@ def buy(pair: str, rate: float, amount: float) -> Dict:
|
|||||||
def sell(pair: str, rate: float, amount: float) -> Dict:
|
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 = f'dry_run_sell_{randint(0, 10**6)}'
|
||||||
_DRY_RUN_OPEN_ORDERS[order_id] = {
|
_DRY_RUN_OPEN_ORDERS[order_id] = {
|
||||||
'pair': pair,
|
'pair': pair,
|
||||||
'price': rate,
|
'price': rate,
|
||||||
@ -194,20 +190,17 @@ def sell(pair: str, rate: float, amount: float) -> Dict:
|
|||||||
return _API.create_limit_sell_order(pair, amount, rate)
|
return _API.create_limit_sell_order(pair, amount, rate)
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Insufficient funds to create limit sell order on market {}.'
|
f'Insufficient funds to create limit sell order on market {pair}.'
|
||||||
'Tried to sell amount {} at rate {} (total {}).'
|
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
|
||||||
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
f'Message: {e}')
|
||||||
)
|
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Could not create limit sell order on market {}.'
|
f'Could not create limit sell order on market {pair}.'
|
||||||
'Tried to sell amount {} at rate {} (total {}).'
|
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
|
||||||
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
f'Message: {e}')
|
||||||
)
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not place sell order due to {}. Message: {}'.format(
|
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -222,8 +215,7 @@ def get_balance(currency: str) -> float:
|
|||||||
balance = balances.get(currency)
|
balance = balances.get(currency)
|
||||||
if balance is None:
|
if balance is None:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not get {} balance due to malformed exchange response: {}'.format(
|
f'Could not get {currency} balance due to malformed exchange response: {balances}')
|
||||||
currency, balances))
|
|
||||||
return balance['free']
|
return balance['free']
|
||||||
|
|
||||||
|
|
||||||
@ -243,8 +235,7 @@ def get_balances() -> dict:
|
|||||||
return balances
|
return balances
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not get balance due to {}. Message: {}'.format(
|
f'Could not get balance due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -255,13 +246,11 @@ def get_tickers() -> Dict:
|
|||||||
return _API.fetch_tickers()
|
return _API.fetch_tickers()
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Exchange {} does not support fetching tickers in batch.'
|
f'Exchange {_API.name} does not support fetching tickers in batch.'
|
||||||
'Message: {}'.format(_API.name, e)
|
f'Message: {e}')
|
||||||
)
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not load tickers due to {}. Message: {}'.format(
|
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -282,8 +271,7 @@ def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
|||||||
return data
|
return data
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not load ticker history due to {}. Message: {}'.format(
|
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
else:
|
else:
|
||||||
@ -327,15 +315,13 @@ def get_ticker_history(pair: str, tick_interval: str, since_ms: Optional[int] =
|
|||||||
return data
|
return data
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Exchange {} does not support fetching historical candlestick data.'
|
f'Exchange {_API.name} does not support fetching historical candlestick data.'
|
||||||
'Message: {}'.format(_API.name, e)
|
f'Message: {e}')
|
||||||
)
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not load ticker history due to {}. Message: {}'.format(
|
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
|
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
|
||||||
|
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
@ -347,12 +333,10 @@ def cancel_order(order_id: str, pair: str) -> None:
|
|||||||
return _API.cancel_order(order_id, pair)
|
return _API.cancel_order(order_id, pair)
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Could not cancel order. Message: {}'.format(e)
|
f'Could not cancel order. Message: {e}')
|
||||||
)
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not cancel order due to {}. Message: {}'.format(
|
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -369,12 +353,10 @@ def get_order(order_id: str, pair: str) -> Dict:
|
|||||||
return _API.fetch_order(order_id, pair)
|
return _API.fetch_order(order_id, pair)
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Could not get order. Message: {}'.format(e)
|
f'Could not get order. Message: {e}')
|
||||||
)
|
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not get order due to {}. Message: {}'.format(
|
f'Could not get order due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -393,8 +375,7 @@ def get_trades_for_order(order_id: str, pair: str, since: datetime) -> List:
|
|||||||
|
|
||||||
except ccxt.NetworkError as e:
|
except ccxt.NetworkError as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not get trades due to networking error. Message: {}'.format(e)
|
f'Could not get trades due to networking error. Message: {e}')
|
||||||
)
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -416,8 +397,7 @@ def get_markets() -> List[dict]:
|
|||||||
return _API.fetch_markets()
|
return _API.fetch_markets()
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not load markets due to {}. Message: {}'.format(
|
f'Could not load markets due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -442,8 +422,7 @@ def get_fee(symbol='ETH/BTC', type='', side='', amount=1,
|
|||||||
price=price, takerOrMaker=taker_or_maker)['rate']
|
price=price, takerOrMaker=taker_or_maker)['rate']
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
'Could not get fee info due to {}. Message: {}'.format(
|
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
|
||||||
e.__class__.__name__, e))
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ class CryptoToFiatConverter(object):
|
|||||||
|
|
||||||
# Check if the fiat convertion you want is supported
|
# Check if the fiat convertion you want is supported
|
||||||
if not self._is_supported_fiat(fiat=fiat_symbol):
|
if not self._is_supported_fiat(fiat=fiat_symbol):
|
||||||
raise ValueError('The fiat {} is not supported.'.format(fiat_symbol))
|
raise ValueError(f'The fiat {fiat_symbol} is not supported.')
|
||||||
|
|
||||||
# Get the pair that interest us and return the price in fiat
|
# Get the pair that interest us and return the price in fiat
|
||||||
for pair in self._pairs:
|
for pair in self._pairs:
|
||||||
@ -182,7 +182,7 @@ class CryptoToFiatConverter(object):
|
|||||||
"""
|
"""
|
||||||
# Check if the fiat convertion you want is supported
|
# Check if the fiat convertion you want is supported
|
||||||
if not self._is_supported_fiat(fiat=fiat_symbol):
|
if not self._is_supported_fiat(fiat=fiat_symbol):
|
||||||
raise ValueError('The fiat {} is not supported.'.format(fiat_symbol))
|
raise ValueError(f'The fiat {fiat_symbol} is not supported.')
|
||||||
|
|
||||||
# No need to convert if both crypto and fiat are the same
|
# No need to convert if both crypto and fiat are the same
|
||||||
if crypto_symbol == fiat_symbol:
|
if crypto_symbol == fiat_symbol:
|
||||||
|
@ -97,7 +97,7 @@ class FreqtradeBot(object):
|
|||||||
# Log state transition
|
# Log state transition
|
||||||
state = self.state
|
state = self.state
|
||||||
if state != old_state:
|
if state != old_state:
|
||||||
self.rpc.send_msg('*Status:* `{}`'.format(state.name.lower()))
|
self.rpc.send_msg(f'*Status:* `{state.name.lower()}`')
|
||||||
logger.info('Changing state to: %s', state.name)
|
logger.info('Changing state to: %s', state.name)
|
||||||
|
|
||||||
if state == State.STOPPED:
|
if state == State.STOPPED:
|
||||||
@ -171,12 +171,10 @@ class FreqtradeBot(object):
|
|||||||
logger.warning('%s, retrying in 30 seconds...', error)
|
logger.warning('%s, retrying in 30 seconds...', error)
|
||||||
time.sleep(constants.RETRY_TIMEOUT)
|
time.sleep(constants.RETRY_TIMEOUT)
|
||||||
except OperationalException:
|
except OperationalException:
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
hint = 'Issue `/start` if you think it is safe to restart.'
|
||||||
self.rpc.send_msg(
|
self.rpc.send_msg(
|
||||||
'*Status:* OperationalException:\n```\n{traceback}```{hint}'
|
f'*Status:* OperationalException:\n```\n{tb}```{hint}'
|
||||||
.format(
|
|
||||||
traceback=traceback.format_exc(),
|
|
||||||
hint='Issue `/start` if you think it is safe to restart.'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
logger.exception('OperationalException. Stopping trader ...')
|
logger.exception('OperationalException. Stopping trader ...')
|
||||||
self.state = State.STOPPED
|
self.state = State.STOPPED
|
||||||
@ -260,6 +258,9 @@ class FreqtradeBot(object):
|
|||||||
"""
|
"""
|
||||||
stake_amount = self.config['stake_amount']
|
stake_amount = self.config['stake_amount']
|
||||||
interval = self.analyze.get_ticker_interval()
|
interval = self.analyze.get_ticker_interval()
|
||||||
|
stake_currency = self.config['stake_currency']
|
||||||
|
fiat_currency = self.config['fiat_display_currency']
|
||||||
|
exc_name = exchange.get_name()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'Checking buy signals to create a new trade with stake_amount: %f ...',
|
'Checking buy signals to create a new trade with stake_amount: %f ...',
|
||||||
@ -267,10 +268,9 @@ class FreqtradeBot(object):
|
|||||||
)
|
)
|
||||||
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
|
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
|
||||||
# Check if stake_amount is fulfilled
|
# Check if stake_amount is fulfilled
|
||||||
if exchange.get_balance(self.config['stake_currency']) < stake_amount:
|
if exchange.get_balance(stake_currency) < stake_amount:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'stake amount is not fulfilled (currency={})'.format(self.config['stake_currency'])
|
f'stake amount is not fulfilled (currency={stake_currency})')
|
||||||
)
|
|
||||||
|
|
||||||
# Remove currently opened and latest pairs from whitelist
|
# Remove currently opened and latest pairs from whitelist
|
||||||
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
||||||
@ -289,7 +289,8 @@ class FreqtradeBot(object):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
pair_s = pair.replace('_', '/')
|
||||||
|
pair_url = exchange.get_pair_detail_url(pair)
|
||||||
# Calculate amount
|
# Calculate amount
|
||||||
buy_limit = self.get_target_bid(exchange.get_ticker(pair))
|
buy_limit = self.get_target_bid(exchange.get_ticker(pair))
|
||||||
amount = stake_amount / buy_limit
|
amount = stake_amount / buy_limit
|
||||||
@ -298,23 +299,15 @@ class FreqtradeBot(object):
|
|||||||
|
|
||||||
stake_amount_fiat = self.fiat_converter.convert_amount(
|
stake_amount_fiat = self.fiat_converter.convert_amount(
|
||||||
stake_amount,
|
stake_amount,
|
||||||
self.config['stake_currency'],
|
stake_currency,
|
||||||
self.config['fiat_display_currency']
|
fiat_currency
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create trade entity and return
|
# Create trade entity and return
|
||||||
self.rpc.send_msg(
|
self.rpc.send_msg(
|
||||||
'*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` '
|
f"""*{exc_name}:* Buying [{pair_s}]({pair_url}) \
|
||||||
.format(
|
with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
||||||
exchange.get_name(),
|
{stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`"""
|
||||||
pair.replace('_', '/'),
|
|
||||||
exchange.get_pair_detail_url(pair),
|
|
||||||
buy_limit,
|
|
||||||
stake_amount,
|
|
||||||
self.config['stake_currency'],
|
|
||||||
stake_amount_fiat,
|
|
||||||
self.config['fiat_display_currency']
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
||||||
fee = exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
fee = exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
||||||
@ -415,12 +408,12 @@ class FreqtradeBot(object):
|
|||||||
fee_abs += exectrade['fee']['cost']
|
fee_abs += exectrade['fee']['cost']
|
||||||
|
|
||||||
if amount != order_amount:
|
if amount != order_amount:
|
||||||
logger.warning("amount {} does not match amount {}".format(amount, trade.amount))
|
logger.warning(f"amount {amount} does not match amount {trade.amount}")
|
||||||
raise OperationalException("Half bought? Amounts don't match")
|
raise OperationalException("Half bought? Amounts don't match")
|
||||||
real_amount = amount - fee_abs
|
real_amount = amount - fee_abs
|
||||||
if fee_abs != 0:
|
if fee_abs != 0:
|
||||||
logger.info("Applying fee on amount for {} (from {} to {}) from Trades".format(
|
logger.info(f"""Applying fee on amount for {trade} \
|
||||||
trade, order['amount'], real_amount))
|
(from {order_amount} to {real_amount}) from Trades""")
|
||||||
return real_amount
|
return real_amount
|
||||||
|
|
||||||
def handle_trade(self, trade: Trade) -> bool:
|
def handle_trade(self, trade: Trade) -> bool:
|
||||||
@ -429,7 +422,7 @@ class FreqtradeBot(object):
|
|||||||
:return: True if trade has been sold, False otherwise
|
:return: True if trade has been sold, False otherwise
|
||||||
"""
|
"""
|
||||||
if not trade.is_open:
|
if not trade.is_open:
|
||||||
raise ValueError('attempt to handle closed trade: {}'.format(trade))
|
raise ValueError(f'attempt to handle closed trade: {trade}')
|
||||||
|
|
||||||
logger.debug('Handling %s ...', trade)
|
logger.debug('Handling %s ...', trade)
|
||||||
current_rate = exchange.get_ticker(trade.pair)['bid']
|
current_rate = exchange.get_ticker(trade.pair)['bid']
|
||||||
@ -480,6 +473,7 @@ class FreqtradeBot(object):
|
|||||||
"""Buy timeout - cancel order
|
"""Buy timeout - cancel order
|
||||||
:return: True if order was fully cancelled
|
:return: True if order was fully cancelled
|
||||||
"""
|
"""
|
||||||
|
pair_s = trade.pair.replace('_', '/')
|
||||||
exchange.cancel_order(trade.open_order_id, trade.pair)
|
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
|
||||||
@ -488,8 +482,7 @@ class FreqtradeBot(object):
|
|||||||
# check_handle_timedout will flush afterwards
|
# check_handle_timedout will flush afterwards
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
logger.info('Buy order timeout for %s.', trade)
|
logger.info('Buy order timeout for %s.', trade)
|
||||||
self.rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
|
self.rpc.send_msg(f'*Timeout:* Unfilled buy order for {pair_s} cancelled')
|
||||||
trade.pair.replace('_', '/')))
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# if trade is partially complete, edit the stake details for the trade
|
# if trade is partially complete, edit the stake details for the trade
|
||||||
@ -498,8 +491,7 @@ class FreqtradeBot(object):
|
|||||||
trade.stake_amount = trade.amount * trade.open_rate
|
trade.stake_amount = trade.amount * trade.open_rate
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
logger.info('Partial buy order timeout for %s.', trade)
|
logger.info('Partial buy order timeout for %s.', trade)
|
||||||
self.rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format(
|
self.rpc.send_msg(f'*Timeout:* Remaining buy order for {pair_s} cancelled')
|
||||||
trade.pair.replace('_', '/')))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# FIX: 20180110, should cancel_order() be cond. or unconditionally called?
|
# FIX: 20180110, should cancel_order() be cond. or unconditionally called?
|
||||||
@ -508,6 +500,7 @@ class FreqtradeBot(object):
|
|||||||
Sell timeout - cancel order and update trade
|
Sell timeout - cancel order and update trade
|
||||||
:return: True if order was fully cancelled
|
:return: True if order was fully cancelled
|
||||||
"""
|
"""
|
||||||
|
pair_s = trade.pair.replace('_', '/')
|
||||||
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, trade.pair)
|
exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
@ -516,8 +509,7 @@ class FreqtradeBot(object):
|
|||||||
trade.close_date = None
|
trade.close_date = None
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
self.rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
|
self.rpc.send_msg(f'*Timeout:* Unfilled sell order for {pair_s} cancelled')
|
||||||
trade.pair.replace('_', '/')))
|
|
||||||
logger.info('Sell order timeout for %s.', trade)
|
logger.info('Sell order timeout for %s.', trade)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -531,6 +523,8 @@ class FreqtradeBot(object):
|
|||||||
:param limit: limit rate for the sell order
|
:param limit: limit rate for the sell order
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
exc = trade.exchange
|
||||||
|
pair = trade.pair
|
||||||
# Execute sell and update trade record
|
# Execute sell and update trade record
|
||||||
order_id = exchange.sell(str(trade.pair), limit, trade.amount)['id']
|
order_id = exchange.sell(str(trade.pair), limit, trade.amount)['id']
|
||||||
trade.open_order_id = order_id
|
trade.open_order_id = order_id
|
||||||
@ -540,43 +534,31 @@ class FreqtradeBot(object):
|
|||||||
profit_trade = trade.calc_profit(rate=limit)
|
profit_trade = trade.calc_profit(rate=limit)
|
||||||
current_rate = exchange.get_ticker(trade.pair)['bid']
|
current_rate = exchange.get_ticker(trade.pair)['bid']
|
||||||
profit = trade.calc_profit_percent(limit)
|
profit = trade.calc_profit_percent(limit)
|
||||||
|
pair_url = exchange.get_pair_detail_url(trade.pair)
|
||||||
|
gain = "profit" if fmt_exp_profit > 0 else "loss"
|
||||||
|
|
||||||
message = "*{exchange}:* Selling\n" \
|
message = f"*{exc}:* Selling\n" \
|
||||||
"*Current Pair:* [{pair}]({pair_url})\n" \
|
f"*Current Pair:* [{pair}]({pair_url})\n" \
|
||||||
"*Limit:* `{limit}`\n" \
|
f"*Limit:* `{limit}`\n" \
|
||||||
"*Amount:* `{amount}`\n" \
|
f"*Amount:* `{round(trade.amount, 8)}`\n" \
|
||||||
"*Open Rate:* `{open_rate:.8f}`\n" \
|
f"*Open Rate:* `{trade.open_rate:.8f}`\n" \
|
||||||
"*Current Rate:* `{current_rate:.8f}`\n" \
|
f"*Current Rate:* `{current_rate:.8f}`\n" \
|
||||||
"*Profit:* `{profit:.2f}%`" \
|
f"*Profit:* `{round(profit * 100, 2):.2f}%`" \
|
||||||
"".format(
|
""
|
||||||
exchange=trade.exchange,
|
|
||||||
pair=trade.pair,
|
|
||||||
pair_url=exchange.get_pair_detail_url(trade.pair),
|
|
||||||
limit=limit,
|
|
||||||
open_rate=trade.open_rate,
|
|
||||||
current_rate=current_rate,
|
|
||||||
amount=round(trade.amount, 8),
|
|
||||||
profit=round(profit * 100, 2),
|
|
||||||
)
|
|
||||||
|
|
||||||
# For regular case, when the configuration exists
|
# For regular case, when the configuration exists
|
||||||
if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
|
if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
|
||||||
|
stake = self.config['stake_currency']
|
||||||
|
fiat = self.config['fiat_display_currency']
|
||||||
fiat_converter = CryptoToFiatConverter()
|
fiat_converter = CryptoToFiatConverter()
|
||||||
profit_fiat = fiat_converter.convert_amount(
|
profit_fiat = fiat_converter.convert_amount(
|
||||||
profit_trade,
|
profit_trade,
|
||||||
self.config['stake_currency'],
|
stake,
|
||||||
self.config['fiat_display_currency']
|
fiat
|
||||||
)
|
|
||||||
message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f} {coin}`' \
|
|
||||||
'` / {profit_fiat:.3f} {fiat})`' \
|
|
||||||
''.format(
|
|
||||||
gain="profit" if fmt_exp_profit > 0 else "loss",
|
|
||||||
profit_percent=fmt_exp_profit,
|
|
||||||
profit_coin=profit_trade,
|
|
||||||
coin=self.config['stake_currency'],
|
|
||||||
profit_fiat=profit_fiat,
|
|
||||||
fiat=self.config['fiat_display_currency'],
|
|
||||||
)
|
)
|
||||||
|
message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f} {stake}`' \
|
||||||
|
f'` / {profit_fiat:.3f} {fiat})`'\
|
||||||
|
''
|
||||||
# Because telegram._forcesell does not have the configuration
|
# Because telegram._forcesell does not have the configuration
|
||||||
# Ignore the FIAT value and does not show the stake_currency as well
|
# Ignore the FIAT value and does not show the stake_currency as well
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user