Adding templates for leverage/short tests

All previous pytests pass
This commit is contained in:
Sam Germain 2021-06-21 21:26:31 -06:00
parent 10979361c1
commit 000932eed0
6 changed files with 197 additions and 39 deletions

View File

@ -66,7 +66,8 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
close_profit_abs = get_column_def( close_profit_abs = get_column_def(
cols, 'close_profit_abs', cols, 'close_profit_abs',
f"(amount * close_rate * (1 - {fee_close})) - {open_trade_value}") f"(amount * close_rate * (1 - {fee_close})) - {open_trade_value}")
close_order_status = get_column_def(cols, 'close_order_status', 'null') # TODO-mg: update to exit order status
sell_order_status = get_column_def(cols, 'sell_order_status', 'null')
amount_requested = get_column_def(cols, 'amount_requested', 'amount') amount_requested = get_column_def(cols, 'amount_requested', 'amount')
# Schema migration necessary # Schema migration necessary
@ -88,7 +89,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stake_amount, amount, amount_requested, open_date, close_date, open_order_id,
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update, stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, close_order_status, strategy, max_rate, min_rate, sell_reason, sell_order_status, strategy,
timeframe, open_trade_value, close_profit_abs, timeframe, open_trade_value, close_profit_abs,
leverage, borrowed, borrowed_currency, collateral_currency, interest_rate, liquidation_price, is_short leverage, borrowed, borrowed_currency, collateral_currency, interest_rate, liquidation_price, is_short
) )
@ -111,7 +112,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{initial_stop_loss_pct} initial_stop_loss_pct, {initial_stop_loss_pct} initial_stop_loss_pct,
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{close_order_status} close_order_status, {sell_order_status} sell_order_status,
{strategy} strategy, {timeframe} timeframe, {strategy} strategy, {timeframe} timeframe,
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
{leverage} leverage, {borrowed} borrowed, {borrowed_currency} borrowed_currency, {leverage} leverage, {borrowed} borrowed, {borrowed_currency} borrowed_currency,
@ -121,6 +122,8 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
""")) """))
# TODO: Does leverage go in here? # TODO: Does leverage go in here?
def migrate_open_orders_to_trades(engine): def migrate_open_orders_to_trades(engine):
with engine.begin() as connection: with engine.begin() as connection:
connection.execute(text(""" connection.execute(text("""

View File

@ -132,7 +132,7 @@ class Order(_DECL_BASE):
order_filled_date = Column(DateTime, nullable=True) order_filled_date = Column(DateTime, nullable=True)
order_update_date = Column(DateTime, nullable=True) order_update_date = Column(DateTime, nullable=True)
leverage = Column(Float, nullable=True, default=0.0) leverage = Column(Float, nullable=True, default=1.0)
def __repr__(self): def __repr__(self):
return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, ' return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, '
@ -258,12 +258,12 @@ class LocalTrade():
# Lowest price reached # Lowest price reached
min_rate: float = 0.0 min_rate: float = 0.0
sell_reason: str = '' sell_reason: str = ''
close_order_status: str = '' sell_order_status: str = ''
strategy: str = '' strategy: str = ''
timeframe: Optional[int] = None timeframe: Optional[int] = None
# Margin trading properties # Margin trading properties
leverage: Optional[float] = 0.0 leverage: Optional[float] = 1.0
borrowed: float = 0.0 borrowed: float = 0.0
borrowed_currency: str = None borrowed_currency: str = None
collateral_currency: str = None collateral_currency: str = None
@ -287,6 +287,8 @@ class LocalTrade():
for key in kwargs: for key in kwargs:
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])
if not self.is_short:
self.is_short = False
self.recalc_open_trade_value() self.recalc_open_trade_value()
def __repr__(self): def __repr__(self):
@ -348,7 +350,7 @@ class LocalTrade():
'profit_abs': self.close_profit_abs, 'profit_abs': self.close_profit_abs,
'sell_reason': self.sell_reason, 'sell_reason': self.sell_reason,
'close_order_status': self.close_order_status, 'sell_order_status': self.sell_order_status,
'stop_loss_abs': self.stop_loss, 'stop_loss_abs': self.stop_loss,
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
@ -371,7 +373,7 @@ class LocalTrade():
'collateral_currency': self.collateral_currency, 'collateral_currency': self.collateral_currency,
'interest_rate': self.interest_rate, 'interest_rate': self.interest_rate,
'liquidation_price': self.liquidation_price, 'liquidation_price': self.liquidation_price,
'leverage': self.leverage, 'is_short': self.is_short,
'open_order_id': self.open_order_id, 'open_order_id': self.open_order_id,
} }
@ -474,12 +476,12 @@ class LocalTrade():
self.recalc_open_trade_value() self.recalc_open_trade_value()
if self.is_open: if self.is_open:
payment = "SELL" if self.is_short else "BUY" payment = "SELL" if self.is_short else "BUY"
logger.info(f'{order_type.upper()}_{payment} order has been fulfilled for {self}.') logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.')
self.open_order_id = None self.open_order_id = None
elif order_type in ('market', 'limit') and self.is_closing_trade(order['side']): elif order_type in ('market', 'limit') and self.is_closing_trade(order['side']):
if self.is_open: if self.is_open:
payment = "BUY" if self.is_short else "SELL" payment = "BUY" if self.is_short else "SELL"
logger.info(f'{order_type.upper()}_{payment} order has been fulfilled for {self}.') logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.')
self.close(safe_value_fallback(order, 'average', 'price')) # TODO: Double check this self.close(safe_value_fallback(order, 'average', 'price')) # TODO: Double check this
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
self.stoploss_order_id = None self.stoploss_order_id = None
@ -502,7 +504,7 @@ class LocalTrade():
self.close_profit = self.calc_profit_ratio() self.close_profit = self.calc_profit_ratio()
self.close_profit_abs = self.calc_profit() self.close_profit_abs = self.calc_profit()
self.is_open = False self.is_open = False
self.close_order_status = 'closed' self.sell_order_status = 'closed'
self.open_order_id = None self.open_order_id = None
if show_msg: if show_msg:
logger.info( logger.info(
@ -576,8 +578,18 @@ class LocalTrade():
close_trade = Decimal(self.amount) * Decimal(rate or self.close_rate) # type: ignore close_trade = Decimal(self.amount) * Decimal(rate or self.close_rate) # type: ignore
fees = close_trade * Decimal(fee or self.fee_close) fees = close_trade * Decimal(fee or self.fee_close)
#TODO: Interest rate could be hourly instead of daily
interest = ((Decimal(self.interest_rate) * Decimal(self.borrowed)) * Decimal((datetime.utcnow() - self.open_date).days)) or 0 # Interest/day * num of days # TODO: Need to set other conditions because sometimes self.open_date is not defined, but why would it ever not be set
try:
open = self.open_date.replace(tzinfo=None)
now = datetime.now()
# breakpoint()
interest = ((Decimal(self.interest_rate or 0) * Decimal(self.borrowed or 0)) *
Decimal((now - open).total_seconds())/86400) or 0 # Interest/day * (seconds in trade)/(seconds per day)
except:
interest = 0
if (self.is_short): if (self.is_short):
return float(close_trade + fees + interest) return float(close_trade + fees + interest)
else: else:
@ -617,12 +629,17 @@ class LocalTrade():
rate=(rate or self.close_rate), rate=(rate or self.close_rate),
fee=(fee or self.fee_close) fee=(fee or self.fee_close)
) )
if self.open_trade_value == 0.0:
return 0.0
if self.is_short: if self.is_short:
profit_ratio = (close_trade_value / self.open_trade_value) - 1 if close_trade_value == 0.0:
return 0.0
else: else:
profit_ratio = (self.open_trade_value / close_trade_value) - 1 profit_ratio = (self.open_trade_value / close_trade_value) - 1
else:
if self.open_trade_value == 0.0:
return 0.0
else:
profit_ratio = (close_trade_value / self.open_trade_value) - 1
return float(f"{profit_ratio:.8f}") return float(f"{profit_ratio:.8f}")
def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]: def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]:
@ -769,12 +786,12 @@ class Trade(_DECL_BASE, LocalTrade):
# Lowest price reached # Lowest price reached
min_rate = Column(Float, nullable=True) min_rate = Column(Float, nullable=True)
sell_reason = Column(String(100), nullable=True) # TODO: Change to close_reason sell_reason = Column(String(100), nullable=True) # TODO: Change to close_reason
close_order_status = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True) timeframe = Column(Integer, nullable=True)
# Margin trading properties # Margin trading properties
leverage = Column(Float, nullable=True, default=0.0) leverage = Column(Float, nullable=True, default=1.0)
borrowed = Column(Float, nullable=False, default=0.0) borrowed = Column(Float, nullable=False, default=0.0)
borrowed_currency = Column(Float, nullable=True) borrowed_currency = Column(Float, nullable=True)
collateral_currency = Column(String(25), nullable=True) collateral_currency = Column(String(25), nullable=True)

View File

@ -221,6 +221,8 @@ def create_mock_trades(fee, use_db: bool = True):
trade = mock_trade_6(fee) trade = mock_trade_6(fee)
add_trade(trade) add_trade(trade)
# TODO-mg: Add margin trades
if use_db: if use_db:
Trade.query.session.flush() Trade.query.session.flush()
@ -250,6 +252,7 @@ def patch_coingekko(mocker) -> None:
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def init_persistence(default_conf): def init_persistence(default_conf):
init_db(default_conf['db_url'], default_conf['dry_run']) init_db(default_conf['db_url'], default_conf['dry_run'])
# TODO-mg: margin with leverage and/or borrowed?
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
@ -914,8 +917,7 @@ def limit_sell_order_old():
@pytest.fixture @pytest.fixture
def limit_buy_order_old_partial(): def limit_buy_order_old_partial():
return { return {'id': 'mocked_limit_buy_old_partial',
'id': 'mocked_limit_buy_old_partial',
'type': 'limit', 'type': 'limit',
'side': 'buy', 'side': 'buy',
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
@ -1769,6 +1771,7 @@ def rpc_balance():
'used': 0.0 'used': 0.0
}, },
} }
# TODO-mg: Add shorts and leverage?
@pytest.fixture @pytest.fixture
@ -2084,3 +2087,95 @@ def saved_hyperopt_results():
].total_seconds() ].total_seconds()
return hyperopt_res return hyperopt_res
# * Margin Tests
@pytest.fixture
def leveraged_fee():
return
@pytest.fixture
def short_fee():
return
@pytest.fixture
def ticker_short():
return
@pytest.fixture
def ticker_exit_short_up():
return
@pytest.fixture
def ticker_exit_short_down():
return
@pytest.fixture
def leveraged_markets():
return
@pytest.fixture(scope='function')
def limit_short_order_open():
return
@pytest.fixture(scope='function')
def limit_short_order(limit_short_order_open):
return
@pytest.fixture(scope='function')
def market_short_order():
return
@pytest.fixture
def market_short_exit_order():
return
@pytest.fixture
def limit_short_order_old():
return
@pytest.fixture
def limit_exit_short_order_old():
return
@pytest.fixture
def limit_short_order_old_partial():
return
@pytest.fixture
def limit_short_order_old_partial_canceled(limit_short_order_old_partial):
return
@pytest.fixture(scope='function')
def limit_short_order_canceled_empty(request):
return
@pytest.fixture
def limit_exit_short_order_open():
return
@pytest.fixture
def limit_exit_short_order(limit_sell_order_open):
return
@pytest.fixture
def short_order_fee():
return

View File

@ -3,7 +3,7 @@ from datetime import datetime, timedelta, timezone
from freqtrade.persistence.models import Order, Trade from freqtrade.persistence.models import Order, Trade
MOCK_TRADE_COUNT = 6 MOCK_TRADE_COUNT = 6 # TODO-mg: Increase for short and leverage
def mock_order_1(): def mock_order_1():
@ -303,3 +303,5 @@ def mock_trade_6(fee):
o = Order.parse_from_ccxt_object(mock_order_6_sell(), 'LTC/BTC', 'sell') o = Order.parse_from_ccxt_object(mock_order_6_sell(), 'LTC/BTC', 'sell')
trade.orders.append(o) trade.orders.append(o)
return trade return trade
# TODO-mg: Mock orders for leveraged and short trades

View File

@ -107,6 +107,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878, 'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None, 'open_order': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': 1.0,
'borrowed': 0.0,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': 0.0,
'liquidation_price': None,
'is_short': False,
} }
mocker.patch('freqtrade.exchange.Exchange.get_rate', mocker.patch('freqtrade.exchange.Exchange.get_rate',
@ -173,6 +181,15 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878, 'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None, 'open_order': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': 1.0,
'borrowed': 0.0,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': 0.0,
'liquidation_price': None,
'is_short': False,
} }

View File

@ -129,6 +129,9 @@ def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog):
r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).", r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).",
caplog) caplog)
# TODO-mg: create a short order
# TODO-mg: create a leveraged long order
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_update_market_order(market_buy_order, market_sell_order, fee, caplog): def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
@ -167,6 +170,9 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).", r"pair=ETH/BTC, amount=91.99181073, open_rate=0.00004099, open_since=.*\).",
caplog) caplog)
# TODO-mg: market short
# TODO-mg: market leveraged long
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee): def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
@ -659,11 +665,13 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
order_date DATETIME, order_date DATETIME,
order_filled_date DATETIME, order_filled_date DATETIME,
order_update_date DATETIME, order_update_date DATETIME,
leverage FLOAT,
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id), CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id),
FOREIGN KEY(ft_trade_id) REFERENCES trades (id) FOREIGN KEY(ft_trade_id) REFERENCES trades (id)
) )
""")) """))
# TODO-mg: Had to add field leverage to this table, check that this is correct
connection.execute(text(""" connection.execute(text("""
insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
@ -912,6 +920,14 @@ def test_to_json(default_conf, fee):
'strategy': None, 'strategy': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None,
'borrowed': None,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
} }
# Simulate dry_run entries # Simulate dry_run entries
@ -977,6 +993,14 @@ def test_to_json(default_conf, fee):
'strategy': None, 'strategy': None,
'timeframe': None, 'timeframe': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': None,
'borrowed': None,
'borrowed_currency': None,
'collateral_currency': None,
'interest_rate': None,
'liquidation_price': None,
'is_short': None,
} }
@ -1315,7 +1339,7 @@ def test_Trade_object_idem():
'get_overall_performance', 'get_overall_performance',
'get_total_closed_profit', 'get_total_closed_profit',
'total_open_trades_stakes', 'total_open_trades_stakes',
'get_sold_trades_without_assigned_fees', 'get_closed_trades_without_assigned_fees',
'get_open_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees',
'get_open_order_trades', 'get_open_order_trades',
'get_trades', 'get_trades',