Wrote buy/short adjustments in freqtradebot and exchange
This commit is contained in:
parent
3671a8aa13
commit
051bdc5fd0
@ -1,9 +1,11 @@
|
|||||||
# from decimal import Decimal
|
# from decimal import Decimal
|
||||||
from freqtrade.exceptions import OperationalException
|
|
||||||
from freqtrade.enums.tradingmode import TradingMode
|
|
||||||
from freqtrade.enums.collateral import Collateral
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
from freqtrade.enums.collateral import Collateral
|
||||||
|
from freqtrade.enums.tradingmode import TradingMode
|
||||||
|
from freqtrade.exceptions import OperationalException
|
||||||
|
|
||||||
|
|
||||||
# from math import ceil
|
# from math import ceil
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from typing import Dict, Optional
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.enums import LiqFormula
|
from freqtrade.enums import InterestMode, LiqFormula
|
||||||
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
|
||||||
OperationalException, TemporaryError)
|
OperationalException, TemporaryError)
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
@ -24,6 +24,7 @@ class Binance(Exchange):
|
|||||||
"trades_pagination_arg": "fromId",
|
"trades_pagination_arg": "fromId",
|
||||||
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
|
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
|
||||||
}
|
}
|
||||||
|
interest_mode: InterestMode = InterestMode.HOURSPERDAY
|
||||||
|
|
||||||
maintenance_margin_formula = LiqFormula.BINANCE
|
maintenance_margin_formula = LiqFormula.BINANCE
|
||||||
|
|
||||||
@ -193,3 +194,8 @@ class Binance(Exchange):
|
|||||||
|
|
||||||
def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
|
def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
|
||||||
return stake_amount / leverage
|
return stake_amount / leverage
|
||||||
|
|
||||||
|
def get_isolated_liq(self, pair: str, open_rate: float,
|
||||||
|
amount: float, leverage: float, is_short: bool) -> float:
|
||||||
|
# TODO-mg: implement
|
||||||
|
return 0.0
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from freqtrade.exchange import Exchange
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -44,3 +44,8 @@ class Bittrex(Exchange):
|
|||||||
is_short: Optional[bool]
|
is_short: Optional[bool]
|
||||||
):
|
):
|
||||||
raise OperationalException("Bittrex does not support leveraged trading")
|
raise OperationalException("Bittrex does not support leveraged trading")
|
||||||
|
|
||||||
|
def get_isolated_liq(self, pair: str, open_rate: float,
|
||||||
|
amount: float, leverage: float, is_short: bool) -> float:
|
||||||
|
# TODO-mg: implement
|
||||||
|
raise OperationalException("Bittrex does not support margin trading")
|
||||||
|
@ -21,7 +21,7 @@ from pandas import DataFrame
|
|||||||
|
|
||||||
from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
|
from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
|
||||||
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
||||||
from freqtrade.enums import LiqFormula
|
from freqtrade.enums import InterestMode, LiqFormula
|
||||||
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, OperationalException, PricingError,
|
InvalidOrderException, OperationalException, PricingError,
|
||||||
RetryableOrderError, TemporaryError)
|
RetryableOrderError, TemporaryError)
|
||||||
@ -70,6 +70,7 @@ class Exchange:
|
|||||||
}
|
}
|
||||||
_ft_has: Dict = {}
|
_ft_has: Dict = {}
|
||||||
liq_formula: LiqFormula
|
liq_formula: LiqFormula
|
||||||
|
interest_mode: InterestMode = InterestMode.NONE
|
||||||
|
|
||||||
def __init__(self, config: Dict[str, Any], validate: bool = True) -> None:
|
def __init__(self, config: Dict[str, Any], validate: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
@ -715,7 +716,7 @@ class Exchange:
|
|||||||
# Order handling
|
# Order handling
|
||||||
|
|
||||||
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
|
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
|
||||||
rate: float, time_in_force: str = 'gtc') -> Dict:
|
rate: float, time_in_force: str = "gtc") -> Dict:
|
||||||
|
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate)
|
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate)
|
||||||
@ -1533,7 +1534,7 @@ class Exchange:
|
|||||||
:returns List of trade data
|
:returns List of trade data
|
||||||
"""
|
"""
|
||||||
if not self.exchange_has("fetchTrades"):
|
if not self.exchange_has("fetchTrades"):
|
||||||
raise OperationalException("This exchange does not suport downloading Trades.")
|
raise OperationalException("This exchange does not support downloading Trades.")
|
||||||
|
|
||||||
return asyncio.get_event_loop().run_until_complete(
|
return asyncio.get_event_loop().run_until_complete(
|
||||||
self._async_get_trade_history(pair=pair, since=since,
|
self._async_get_trade_history(pair=pair, since=since,
|
||||||
@ -1542,6 +1543,16 @@ class Exchange:
|
|||||||
def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]):
|
def transfer(self, asset: str, amount: float, frm: str, to: str, pair: Optional[str]):
|
||||||
self._api.transfer(asset, amount, frm, to)
|
self._api.transfer(asset, amount, frm, to)
|
||||||
|
|
||||||
|
def get_isolated_liq(self, pair: str, open_rate: float,
|
||||||
|
amount: float, leverage: float, is_short: bool) -> float:
|
||||||
|
raise OperationalException(
|
||||||
|
f"Isolated margin is not available on {self.name} using freqtrade"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_interest_rate(self, pair: str, open_rate: float, is_short: bool) -> float:
|
||||||
|
# TODO-mg: implement
|
||||||
|
return 0.0005
|
||||||
|
|
||||||
|
|
||||||
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:
|
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:
|
||||||
return exchange_name in ccxt_exchanges(ccxt_module)
|
return exchange_name in ccxt_exchanges(ccxt_module)
|
||||||
|
@ -155,3 +155,9 @@ class Ftx(Exchange):
|
|||||||
if order['type'] == 'stop':
|
if order['type'] == 'stop':
|
||||||
return safe_value_fallback2(order, order, 'id_stop', 'id')
|
return safe_value_fallback2(order, order, 'id_stop', 'id')
|
||||||
return order['id']
|
return order['id']
|
||||||
|
|
||||||
|
def get_isolated_liq(self, pair: str, open_rate: float,
|
||||||
|
amount: float, leverage: float, is_short: bool) -> float:
|
||||||
|
# TODO-mg: implement
|
||||||
|
raise OperationalException(
|
||||||
|
"Isolated margin trading not yet implemented on FTX, would you like to implement it?")
|
||||||
|
@ -4,7 +4,7 @@ from typing import Any, Dict, Optional
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.enums import LiqFormula
|
from freqtrade.enums import InterestMode, LiqFormula
|
||||||
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
|
||||||
OperationalException, TemporaryError)
|
OperationalException, TemporaryError)
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
@ -23,6 +23,7 @@ class Kraken(Exchange):
|
|||||||
"trades_pagination": "id",
|
"trades_pagination": "id",
|
||||||
"trades_pagination_arg": "since",
|
"trades_pagination_arg": "since",
|
||||||
}
|
}
|
||||||
|
interest_mode: InterestMode = InterestMode.HOURSPER4
|
||||||
|
|
||||||
maintenance_margin_formula = LiqFormula.KRAKEN
|
maintenance_margin_formula = LiqFormula.KRAKEN
|
||||||
|
|
||||||
@ -147,3 +148,8 @@ class Kraken(Exchange):
|
|||||||
is_short: Optional[bool]
|
is_short: Optional[bool]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def get_isolated_liq(self, pair: str, open_rate: float,
|
||||||
|
amount: float, leverage: float, is_short: bool) -> float:
|
||||||
|
# TODO-mg: implement
|
||||||
|
raise OperationalException("Kraken only supports cross margin trading")
|
||||||
|
@ -16,7 +16,7 @@ from freqtrade.configuration import validate_config_consistency
|
|||||||
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.enums import RPCMessageType, SellType, State
|
from freqtrade.enums import InterestMode, RPCMessageType, SellType, State
|
||||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, PricingError)
|
InvalidOrderException, PricingError)
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||||
@ -442,8 +442,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.")
|
logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
long_lev = 3 if True else 1.0 # Replace with self.strategy.get_leverage
|
long_lev = 1.0 if True else 1.0 # Replace with self.strategy.get_leverage
|
||||||
short_lev = 3 if True else 1.0 # Replace with self.strategy.get_leverage
|
short_lev = 1.0 if True else 1.0 # Replace with self.strategy.get_leverage
|
||||||
# running get_signal on historical data fetched
|
# running get_signal on historical data fetched
|
||||||
(buy, sell, buy_tag) = self.strategy.get_signal(
|
(buy, sell, buy_tag) = self.strategy.get_signal(
|
||||||
pair,
|
pair,
|
||||||
@ -528,7 +528,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
stake_amount: float,
|
stake_amount: float,
|
||||||
price: Optional[float] = None,
|
price: Optional[float] = None,
|
||||||
forcebuy: bool = False,
|
forcebuy: bool = False,
|
||||||
leverage: Optional[float] = 1.0,
|
leverage: float = 1.0,
|
||||||
is_short: bool = False,
|
is_short: bool = False,
|
||||||
buy_tag: Optional[str] = None
|
buy_tag: Optional[str] = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@ -540,16 +540,17 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:return: True if a buy order is created, false if it fails.
|
:return: True if a buy order is created, false if it fails.
|
||||||
"""
|
"""
|
||||||
time_in_force = self.strategy.order_time_in_force['buy'] # TODO-mg Change to enter
|
time_in_force = self.strategy.order_time_in_force['buy'] # TODO-mg Change to enter
|
||||||
trade_type = 'short' if is_short else 'buy'
|
side = 'sell' if is_short else 'buy'
|
||||||
|
name = 'Short' if is_short else 'Buy'
|
||||||
|
|
||||||
if price:
|
if price:
|
||||||
enter_limit_requested = price
|
enter_limit_requested = price
|
||||||
else:
|
else:
|
||||||
# Calculate price
|
# Calculate price
|
||||||
enter_limit_requested = self.exchange.get_rate(pair, refresh=True, side=trade_type)
|
enter_limit_requested = self.exchange.get_rate(pair, refresh=True, side=side)
|
||||||
|
|
||||||
if not enter_limit_requested:
|
if not enter_limit_requested:
|
||||||
raise PricingError('Could not determine buy price.')
|
raise PricingError(f'Could not determine {side} price.')
|
||||||
|
|
||||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(
|
min_stake_amount = self.exchange.get_min_pair_stake_amount(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
@ -570,61 +571,82 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if not stake_amount:
|
if not stake_amount:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log_type = f"{trade_type.capitalize()} signal found"
|
log_type = f"{name} signal found"
|
||||||
logger.info(f"{log_type}: about create a new trade for {pair} with stake_amount: "
|
logger.info(f"{log_type}: about create a new trade for {pair} with stake_amount: "
|
||||||
f"{stake_amount} ...")
|
f"{stake_amount} ...")
|
||||||
|
|
||||||
amount = stake_amount / enter_limit_requested
|
amount = (stake_amount / enter_limit_requested) * leverage
|
||||||
order_type = self.strategy.order_types[trade_type]
|
order_type = self.strategy.order_types[side] # TODO-mg: Don't knoww what to do here
|
||||||
if forcebuy:
|
if forcebuy:
|
||||||
# Forcebuy can define a different ordertype
|
# Forcebuy can define a different ordertype
|
||||||
# TODO-mg get a forceshort
|
# TODO-mg get a forceshort? What is this
|
||||||
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
||||||
|
|
||||||
|
# TODO-mg:Will this work for shorting?
|
||||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
||||||
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
||||||
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)):
|
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)):
|
||||||
logger.info(f"User requested abortion of buying {pair}")
|
logger.info(f"User requested abortion of {name.lower()}ing {pair}")
|
||||||
return False
|
return False
|
||||||
amount = self.exchange.amount_to_precision(pair, amount)
|
amount = self.exchange.amount_to_precision(pair, amount)
|
||||||
order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy",
|
order = self.exchange.create_order(pair=pair, ordertype=order_type, side=side,
|
||||||
amount=amount, rate=enter_limit_requested,
|
amount=amount, rate=enter_limit_requested,
|
||||||
time_in_force=time_in_force)
|
time_in_force=time_in_force)
|
||||||
order_obj = Order.parse_from_ccxt_object(order, pair, 'buy')
|
order_obj = Order.parse_from_ccxt_object(order, pair, side)
|
||||||
order_id = order['id']
|
order_id = order['id']
|
||||||
order_status = order.get('status', None)
|
order_status = order.get('status', None)
|
||||||
|
|
||||||
# we assume the order is executed at the price requested
|
# we assume the order is executed at the price requested
|
||||||
buy_limit_filled_price = enter_limit_requested
|
enter_limit_filled_price = enter_limit_requested
|
||||||
amount_requested = amount
|
amount_requested = amount
|
||||||
|
|
||||||
if order_status == 'expired' or order_status == 'rejected':
|
if order_status == 'expired' or order_status == 'rejected':
|
||||||
order_tif = self.strategy.order_time_in_force['buy']
|
order_tif = self.strategy.order_time_in_force['buy'] # TODO-mg: Update to enter
|
||||||
|
|
||||||
# return false if the order is not filled
|
# return false if the order is not filled
|
||||||
if float(order['filled']) == 0:
|
if float(order['filled']) == 0:
|
||||||
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
|
logger.warning('%s %s order with time in force %s for %s is %s by %s.'
|
||||||
' zero amount is fulfilled.',
|
' zero amount is fulfilled.',
|
||||||
order_tif, order_type, pair, order_status, self.exchange.name)
|
name, order_tif, order_type, pair, order_status, self.exchange.name)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# the order is partially fulfilled
|
# the order is partially fulfilled
|
||||||
# in case of IOC orders we can check immediately
|
# in case of IOC orders we can check immediately
|
||||||
# if the order is fulfilled fully or partially
|
# if the order is fulfilled fully or partially
|
||||||
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
|
|
||||||
|
logger.warning('%s %s order with time in force %s for %s is %s by %s.'
|
||||||
' %s amount fulfilled out of %s (%s remaining which is canceled).',
|
' %s amount fulfilled out of %s (%s remaining which is canceled).',
|
||||||
order_tif, order_type, pair, order_status, self.exchange.name,
|
name, order_tif, order_type, pair, order_status, self.exchange.name,
|
||||||
order['filled'], order['amount'], order['remaining']
|
order['filled'], order['amount'], order['remaining']
|
||||||
)
|
)
|
||||||
stake_amount = order['cost']
|
stake_amount = order['cost']
|
||||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||||
|
|
||||||
# in case of FOK the order may be filled immediately and fully
|
# in case of FOK the order may be filled immediately and fully
|
||||||
elif order_status == 'closed':
|
elif order_status == 'closed':
|
||||||
stake_amount = order['cost']
|
stake_amount = order['cost']
|
||||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||||
|
|
||||||
|
interest_rate = 0
|
||||||
|
isolated_liq = None
|
||||||
|
|
||||||
|
if leverage > 1.0: # TODO-mg: and margin == isolated:
|
||||||
|
isolated_liq = self.exchange.get_isolated_liq(
|
||||||
|
pair=pair,
|
||||||
|
open_rate=enter_limit_filled_price,
|
||||||
|
amount=amount,
|
||||||
|
leverage=leverage,
|
||||||
|
is_short=is_short
|
||||||
|
)
|
||||||
|
|
||||||
|
if leverage > 1.0:
|
||||||
|
interest_rate = self.exchange.get_interest_rate(
|
||||||
|
pair=pair,
|
||||||
|
open_rate=enter_limit_filled_price,
|
||||||
|
is_short=is_short
|
||||||
|
)
|
||||||
|
|
||||||
# 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 = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
||||||
@ -636,14 +658,19 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
amount_requested=amount_requested,
|
amount_requested=amount_requested,
|
||||||
fee_open=fee,
|
fee_open=fee,
|
||||||
fee_close=fee,
|
fee_close=fee,
|
||||||
open_rate=buy_limit_filled_price,
|
open_rate=enter_limit_filled_price,
|
||||||
open_rate_requested=enter_limit_requested,
|
open_rate_requested=enter_limit_requested,
|
||||||
open_date=datetime.utcnow(),
|
open_date=datetime.utcnow(),
|
||||||
exchange=self.exchange.id,
|
exchange=self.exchange.id,
|
||||||
open_order_id=order_id,
|
open_order_id=order_id,
|
||||||
strategy=self.strategy.get_strategy_name(),
|
strategy=self.strategy.get_strategy_name(),
|
||||||
buy_tag=buy_tag,
|
buy_tag=buy_tag,
|
||||||
timeframe=timeframe_to_minutes(self.config['timeframe'])
|
timeframe=timeframe_to_minutes(self.config['timeframe']),
|
||||||
|
leverage=leverage,
|
||||||
|
is_short=is_short,
|
||||||
|
interest_rate=interest_rate,
|
||||||
|
interest_mode=self.exchange.interest_mode,
|
||||||
|
isolated_liq=isolated_liq
|
||||||
)
|
)
|
||||||
trade.orders.append(order_obj)
|
trade.orders.append(order_obj)
|
||||||
|
|
||||||
|
@ -2177,7 +2177,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
|
|||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match="This exchange does not suport downloading Trades."):
|
match="This exchange does not support downloading Trades."):
|
||||||
exchange.get_historic_trades(pair, since=trades_history[0][0],
|
exchange.get_historic_trades(pair, since=trades_history[0][0],
|
||||||
until=trades_history[-1][0])
|
until=trades_history[-1][0])
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user