Wrote buy/short adjustments in freqtradebot and exchange

This commit is contained in:
Sam Germain 2021-07-25 23:40:38 -06:00
parent 3671a8aa13
commit 051bdc5fd0
8 changed files with 98 additions and 35 deletions

View File

@ -1,9 +1,11 @@
# 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 freqtrade.enums.collateral import Collateral
from freqtrade.enums.tradingmode import TradingMode
from freqtrade.exceptions import OperationalException
# from math import ceil

View File

@ -4,7 +4,7 @@ from typing import Dict, Optional
import ccxt
from freqtrade.enums import LiqFormula
from freqtrade.enums import InterestMode, LiqFormula
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange
@ -24,6 +24,7 @@ class Binance(Exchange):
"trades_pagination_arg": "fromId",
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
}
interest_mode: InterestMode = InterestMode.HOURSPERDAY
maintenance_margin_formula = LiqFormula.BINANCE
@ -193,3 +194,8 @@ class Binance(Exchange):
def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
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

View File

@ -2,8 +2,8 @@
import logging
from typing import Dict, Optional
from freqtrade.exchange import Exchange
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
@ -44,3 +44,8 @@ class Bittrex(Exchange):
is_short: Optional[bool]
):
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")

View File

@ -21,7 +21,7 @@ from pandas import DataFrame
from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
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,
InvalidOrderException, OperationalException, PricingError,
RetryableOrderError, TemporaryError)
@ -70,6 +70,7 @@ class Exchange:
}
_ft_has: Dict = {}
liq_formula: LiqFormula
interest_mode: InterestMode = InterestMode.NONE
def __init__(self, config: Dict[str, Any], validate: bool = True) -> None:
"""
@ -715,7 +716,7 @@ class Exchange:
# Order handling
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']:
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate)
@ -1533,7 +1534,7 @@ class Exchange:
:returns List of trade data
"""
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(
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]):
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:
return exchange_name in ccxt_exchanges(ccxt_module)

View File

@ -155,3 +155,9 @@ class Ftx(Exchange):
if order['type'] == 'stop':
return safe_value_fallback2(order, order, 'id_stop', '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?")

View File

@ -4,7 +4,7 @@ from typing import Any, Dict, Optional
import ccxt
from freqtrade.enums import LiqFormula
from freqtrade.enums import InterestMode, LiqFormula
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange
@ -23,6 +23,7 @@ class Kraken(Exchange):
"trades_pagination": "id",
"trades_pagination_arg": "since",
}
interest_mode: InterestMode = InterestMode.HOURSPER4
maintenance_margin_formula = LiqFormula.KRAKEN
@ -147,3 +148,8 @@ class Kraken(Exchange):
is_short: Optional[bool]
):
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")

View File

@ -16,7 +16,7 @@ from freqtrade.configuration import validate_config_consistency
from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider
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,
InvalidOrderException, PricingError)
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.")
return False
long_lev = 3 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
long_lev = 1.0 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
(buy, sell, buy_tag) = self.strategy.get_signal(
pair,
@ -528,7 +528,7 @@ class FreqtradeBot(LoggingMixin):
stake_amount: float,
price: Optional[float] = None,
forcebuy: bool = False,
leverage: Optional[float] = 1.0,
leverage: float = 1.0,
is_short: bool = False,
buy_tag: Optional[str] = None
) -> bool:
@ -540,16 +540,17 @@ class FreqtradeBot(LoggingMixin):
: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
trade_type = 'short' if is_short else 'buy'
side = 'sell' if is_short else 'buy'
name = 'Short' if is_short else 'Buy'
if price:
enter_limit_requested = price
else:
# 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:
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(
pair=pair,
@ -570,61 +571,82 @@ class FreqtradeBot(LoggingMixin):
if not stake_amount:
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: "
f"{stake_amount} ...")
amount = stake_amount / enter_limit_requested
order_type = self.strategy.order_types[trade_type]
amount = (stake_amount / enter_limit_requested) * leverage
order_type = self.strategy.order_types[side] # TODO-mg: Don't knoww what to do here
if forcebuy:
# 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)
# TODO-mg:Will this work for shorting?
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,
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
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,
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_status = order.get('status', None)
# 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
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
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.',
order_tif, order_type, pair, order_status, self.exchange.name)
name, order_tif, order_type, pair, order_status, self.exchange.name)
return False
else:
# the order is partially fulfilled
# in case of IOC orders we can check immediately
# 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).',
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']
)
stake_amount = order['cost']
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
elif order_status == 'closed':
stake_amount = order['cost']
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 = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
@ -636,14 +658,19 @@ class FreqtradeBot(LoggingMixin):
amount_requested=amount_requested,
fee_open=fee,
fee_close=fee,
open_rate=buy_limit_filled_price,
open_rate=enter_limit_filled_price,
open_rate_requested=enter_limit_requested,
open_date=datetime.utcnow(),
exchange=self.exchange.id,
open_order_id=order_id,
strategy=self.strategy.get_strategy_name(),
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)

View File

@ -2177,7 +2177,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
pair = 'ETH/BTC'
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],
until=trades_history[-1][0])