Compare commits
14 Commits
hyperactiv
...
inverse-fu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7953fbc90b | ||
|
|
e3bbd8a4fc | ||
|
|
53992318ff | ||
|
|
25fb188331 | ||
|
|
b2ec3d42ce | ||
|
|
71adce0cca | ||
|
|
8b359f06df | ||
|
|
b0ca75394d | ||
|
|
b7c07d2913 | ||
|
|
9c252a50d4 | ||
|
|
243d4bb9dc | ||
|
|
b4cc5be234 | ||
|
|
8a052cb55b | ||
|
|
d7216eac15 |
@@ -85,7 +85,7 @@ config = {
|
||||
],
|
||||
},
|
||||
|
||||
# https://www.binance.com
|
||||
# https://www.binance.com/en/futures/BTC_USDT
|
||||
'Binance Futures': {
|
||||
'fee': 0.0004,
|
||||
|
||||
@@ -105,6 +105,26 @@ config = {
|
||||
],
|
||||
},
|
||||
|
||||
# https://www.binance.com/en/delivery/btcusd_perpetual
|
||||
'Binance Inverse Futures': {
|
||||
'fee': 0.0004,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
'type': 'inverse futures',
|
||||
|
||||
# accepted values are: 'cross' and 'isolated'
|
||||
'futures_leverage_mode': 'cross',
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
# Price per contract, also called the contract-multiplier
|
||||
'contract_size': 100,
|
||||
|
||||
'assets': [
|
||||
{'asset': 'BTC', 'balance': 1},
|
||||
{'asset': 'ETH', 'balance': 10},
|
||||
],
|
||||
},
|
||||
|
||||
# https://testnet.binancefuture.com
|
||||
'Testnet Binance Futures': {
|
||||
'fee': 0.0004,
|
||||
|
||||
@@ -16,7 +16,10 @@ CACHED_CONFIG = dict()
|
||||
|
||||
def app_currency() -> str:
|
||||
from jesse.routes import router
|
||||
return quote_asset(router.routes[0].symbol)
|
||||
underlying = quote_asset(router.routes[0].symbol)
|
||||
if underlying.upper() == 'PERP':
|
||||
underlying = base_asset(router.routes[0].symbol)
|
||||
return underlying
|
||||
|
||||
|
||||
def app_mode() -> str:
|
||||
|
||||
@@ -48,9 +48,9 @@ class Exchange(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_realized_pnl(self, realized_pnl: float):
|
||||
def add_realized_pnl(self, symbol: str, realized_pnl: float):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def charge_fee(self, amount):
|
||||
def charge_fee(self, symbol: str, amount):
|
||||
pass
|
||||
|
||||
@@ -87,7 +87,7 @@ class FuturesExchange(Exchange):
|
||||
|
||||
return temp_credit
|
||||
|
||||
def charge_fee(self, amount):
|
||||
def charge_fee(self, symbol: str, amount):
|
||||
fee_amount = abs(amount) * self.fee_rate
|
||||
new_balance = self.assets[self.settlement_currency] - fee_amount
|
||||
logger.info(
|
||||
@@ -99,7 +99,7 @@ class FuturesExchange(Exchange):
|
||||
)
|
||||
self.assets[self.settlement_currency] = new_balance
|
||||
|
||||
def add_realized_pnl(self, realized_pnl: float):
|
||||
def add_realized_pnl(self, symbol: str, realized_pnl: float):
|
||||
new_balance = self.assets[self.settlement_currency] + realized_pnl
|
||||
logger.info('Added realized PNL of {}. Balance for {} on {} changed from {} to {}'.format(
|
||||
round(realized_pnl, 2),
|
||||
@@ -171,4 +171,4 @@ class FuturesExchange(Exchange):
|
||||
if item[0] == order.qty and item[1] == order.price:
|
||||
self.sell_orders[base_asset][index] = np.array([0, 0])
|
||||
break
|
||||
return
|
||||
return
|
||||
|
||||
187
jesse/models/InverseFuturesExchange.py
Normal file
187
jesse/models/InverseFuturesExchange.py
Normal file
@@ -0,0 +1,187 @@
|
||||
import jesse.helpers as jh
|
||||
import jesse.services.logger as logger
|
||||
from jesse.exceptions import InsufficientMargin
|
||||
from jesse.models import Order
|
||||
from jesse.enums import sides, order_types
|
||||
from jesse.libs import DynamicNumpyArray
|
||||
import numpy as np
|
||||
from jesse.services import selectors
|
||||
from .Exchange import Exchange
|
||||
|
||||
|
||||
class InverseFuturesExchange(Exchange):
|
||||
"""
|
||||
In inverse future contracts, the settlement_currency is the base asset itself.
|
||||
"""
|
||||
|
||||
# current holding assets
|
||||
assets = {}
|
||||
# current available assets (dynamically changes based on active orders)
|
||||
available_assets = {}
|
||||
|
||||
buy_orders = {}
|
||||
sell_orders = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
starting_assets: list,
|
||||
fee_rate: float,
|
||||
futures_leverage_mode: str,
|
||||
futures_leverage: int,
|
||||
contract_size: int,
|
||||
):
|
||||
super().__init__(name, starting_assets, fee_rate, 'inverse futures')
|
||||
|
||||
self.futures_leverage_mode = futures_leverage_mode
|
||||
self.futures_leverage = futures_leverage
|
||||
self.contract_size = contract_size
|
||||
|
||||
for item in starting_assets:
|
||||
self.buy_orders[item['asset']] = DynamicNumpyArray((10, 2))
|
||||
self.sell_orders[item['asset']] = DynamicNumpyArray((10, 2))
|
||||
|
||||
# make sure trading routes exist in starting_assets
|
||||
from jesse.routes import router
|
||||
for r in router.routes:
|
||||
base = jh.base_asset(r.symbol)
|
||||
if base not in self.assets:
|
||||
self.assets[base] = 0
|
||||
if base not in self.buy_orders:
|
||||
self.buy_orders[base] = DynamicNumpyArray((10, 2))
|
||||
if base not in self.sell_orders:
|
||||
self.sell_orders[base] = DynamicNumpyArray((10, 2))
|
||||
|
||||
self.starting_assets = self.assets.copy()
|
||||
self.available_assets = self.assets.copy()
|
||||
|
||||
# start from 0 balance for self.available_assets which acts as a temp variable
|
||||
for k in self.available_assets:
|
||||
self.available_assets[k] = 0
|
||||
|
||||
def wallet_balance(self, symbol=''):
|
||||
if symbol == '':
|
||||
raise ValueError
|
||||
settlement_currency = jh.base_asset(symbol)
|
||||
return self.assets[settlement_currency]
|
||||
|
||||
def available_margin(self, symbol=''):
|
||||
if symbol == '':
|
||||
raise ValueError
|
||||
settlement_currency = jh.base_asset(symbol)
|
||||
temp_credit = self.assets[settlement_currency] * self.futures_leverage
|
||||
# we need to consider buy and sell orders of ALL pairs
|
||||
# also, consider the value of all open positions
|
||||
for asset in self.assets:
|
||||
if asset == settlement_currency:
|
||||
continue
|
||||
|
||||
position = selectors.get_position(self.name, asset + "-" + settlement_currency)
|
||||
if position is None:
|
||||
continue
|
||||
|
||||
if position.is_open:
|
||||
# add unrealized PNL
|
||||
temp_credit += position.pnl
|
||||
|
||||
# subtract worst scenario orders' used margin
|
||||
sum_buy_orders = (self.buy_orders[asset][:][:, 0] * self.buy_orders[asset][:][:, 1]).sum()
|
||||
sum_sell_orders = (self.sell_orders[asset][:][:, 0] * self.sell_orders[asset][:][:, 1]).sum()
|
||||
if position.is_open:
|
||||
if position.type == 'long':
|
||||
sum_buy_orders += position.value
|
||||
else:
|
||||
sum_sell_orders -= abs(position.value)
|
||||
temp_credit -= max(abs(sum_buy_orders), abs(sum_sell_orders))
|
||||
|
||||
return temp_credit
|
||||
|
||||
def charge_fee(self, symbol, amount):
|
||||
settlement_currency = jh.base_asset(symbol)
|
||||
fee_amount = abs(amount) * self.fee_rate
|
||||
new_balance = self.assets[settlement_currency] - fee_amount
|
||||
logger.info(
|
||||
'Charged {} as fee. Balance for {} on {} changed from {} to {}'.format(
|
||||
round(fee_amount, 2), settlement_currency, self.name,
|
||||
round(self.assets[settlement_currency], 2),
|
||||
round(new_balance, 2),
|
||||
)
|
||||
)
|
||||
self.assets[settlement_currency] = new_balance
|
||||
|
||||
def add_realized_pnl(self, symbol: str, realized_pnl: float):
|
||||
settlement_currency = jh.base_asset(symbol)
|
||||
new_balance = self.assets[settlement_currency] + realized_pnl
|
||||
logger.info('Added realized PNL of {}. Balance for {} on {} changed from {} to {}'.format(
|
||||
round(realized_pnl, 2),
|
||||
settlement_currency, self.name,
|
||||
round(self.assets[settlement_currency], 2),
|
||||
round(new_balance, 2),
|
||||
))
|
||||
self.assets[settlement_currency] = new_balance
|
||||
|
||||
def on_order_submission(self, order: Order, skip_market_order=True):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
|
||||
# make sure we don't spend more than we're allowed considering current allowed leverage
|
||||
if order.type != order_types.MARKET or skip_market_order:
|
||||
if not order.is_reduce_only:
|
||||
# TODO: add contract_multiplier to the equation
|
||||
order_size = abs(order.qty / order.price)
|
||||
remaining_margin = self.available_margin(order.symbol)
|
||||
if order_size > remaining_margin:
|
||||
raise InsufficientMargin(
|
||||
'You cannot submit an order for ${} when your margin balance is ${}'.format(
|
||||
round(order_size), round(remaining_margin)
|
||||
))
|
||||
|
||||
# skip market order at the time of submission because we don't have
|
||||
# the exact order.price. Instead, we call on_order_submission() one
|
||||
# more time at time of execution without "skip_market_order=False".
|
||||
if order.type == order_types.MARKET and skip_market_order:
|
||||
return
|
||||
|
||||
self.available_assets[base_asset] += order.qty
|
||||
if order.side == sides.BUY:
|
||||
self.buy_orders[base_asset].append(np.array([order.qty, order.price]))
|
||||
else:
|
||||
self.sell_orders[base_asset].append(np.array([order.qty, order.price]))
|
||||
|
||||
def on_order_execution(self, order: Order):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
|
||||
if order.type == order_types.MARKET:
|
||||
self.on_order_submission(order, skip_market_order=False)
|
||||
|
||||
if order.side == sides.BUY:
|
||||
# find and set order to [0, 0] (same as removing it)
|
||||
for index, item in enumerate(self.buy_orders[base_asset]):
|
||||
if item[0] == order.qty and item[1] == order.price:
|
||||
self.buy_orders[base_asset][index] = np.array([0, 0])
|
||||
break
|
||||
else:
|
||||
# find and set order to [0, 0] (same as removing it)
|
||||
for index, item in enumerate(self.sell_orders[base_asset]):
|
||||
if item[0] == order.qty and item[1] == order.price:
|
||||
self.sell_orders[base_asset][index] = np.array([0, 0])
|
||||
break
|
||||
return
|
||||
|
||||
def on_order_cancellation(self, order: Order):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
|
||||
self.available_assets[base_asset] -= order.qty
|
||||
# self.available_assets[quote_asset] += order.qty * order.price
|
||||
if order.side == sides.BUY:
|
||||
# find and set order to [0, 0] (same as removing it)
|
||||
for index, item in enumerate(self.buy_orders[base_asset]):
|
||||
if item[0] == order.qty and item[1] == order.price:
|
||||
self.buy_orders[base_asset][index] = np.array([0, 0])
|
||||
break
|
||||
else:
|
||||
# find and set order to [0, 0] (same as removing it)
|
||||
for index, item in enumerate(self.sell_orders[base_asset]):
|
||||
if item[0] == order.qty and item[1] == order.price:
|
||||
self.sell_orders[base_asset][index] = np.array([0, 0])
|
||||
break
|
||||
return
|
||||
@@ -39,6 +39,11 @@ class Position:
|
||||
# # TODO: make sure that it is available only for live trading in futures markets
|
||||
# return self._mark_price
|
||||
|
||||
# TODO: more properties to add:
|
||||
# - Margin Ratio
|
||||
# * Liquidation price
|
||||
# * mark price?!
|
||||
|
||||
@property
|
||||
def value(self) -> float:
|
||||
"""
|
||||
@@ -75,7 +80,8 @@ class Position:
|
||||
def roi(self) -> float:
|
||||
"""
|
||||
Return on Investment in percentage
|
||||
More at: https://www.binance.com/en/support/faq/5b9ad93cb4854f5990b9fb97c03cfbeb
|
||||
|
||||
For futures: https://www.binance.com/en/support/faq/5b9ad93cb4854f5990b9fb97c03cfbeb
|
||||
"""
|
||||
return self.pnl / self.total_cost * 100
|
||||
|
||||
@@ -110,9 +116,12 @@ class Position:
|
||||
if self.qty == 0:
|
||||
return 0
|
||||
|
||||
diff = self.value - abs(self.entry_price * self.qty)
|
||||
if self.exchange.type != 'inverse futures':
|
||||
diff = self.value - abs(self.entry_price * self.qty)
|
||||
return -diff if self.type == 'short' else diff
|
||||
|
||||
return -diff if self.type == 'short' else diff
|
||||
# PNL = side * Qty * contract_multiplier * (1 / entry price - 1 / exit price)
|
||||
return self.qty * self.exchange.contract_size * (1 / self.entry_price - 1 / self.current_price)
|
||||
|
||||
@property
|
||||
def is_open(self) -> bool:
|
||||
@@ -133,23 +142,12 @@ class Position:
|
||||
return self.qty == 0
|
||||
|
||||
@property
|
||||
def mode(self) -> str:
|
||||
def margin_mode(self) -> str:
|
||||
if self.exchange.type == 'spot':
|
||||
return 'spot'
|
||||
else:
|
||||
return self.exchange.futures_leverage_mode
|
||||
|
||||
# - Margin Ratio
|
||||
# * Liquidation price
|
||||
# * mark price?!
|
||||
# * ROE(PNL?)
|
||||
# * Maintenance futures
|
||||
# * futures balance
|
||||
|
||||
# @property
|
||||
# def futures_ratio(self):
|
||||
# return 0
|
||||
|
||||
def _close(self, close_price: float) -> None:
|
||||
if self.is_open is False:
|
||||
raise EmptyPosition('The position is already closed.')
|
||||
@@ -166,7 +164,7 @@ class Position:
|
||||
self.exit_price = close_price
|
||||
|
||||
if self.exchange:
|
||||
self.exchange.add_realized_pnl(estimated_profit)
|
||||
self.exchange.add_realized_pnl(self.symbol, estimated_profit)
|
||||
self.exchange.temp_reduced_amount[jh.base_asset(self.symbol)] += abs(close_qty * close_price)
|
||||
self.qty = 0
|
||||
self.entry_price = None
|
||||
@@ -196,7 +194,7 @@ class Position:
|
||||
|
||||
if self.exchange:
|
||||
# self.exchange.increase_futures_balance(qty * self.entry_price + estimated_profit)
|
||||
self.exchange.add_realized_pnl(estimated_profit)
|
||||
self.exchange.add_realized_pnl(self.symbol, estimated_profit)
|
||||
self.exchange.temp_reduced_amount[jh.base_asset(self.symbol)] += abs(qty * price)
|
||||
|
||||
if self.type == trade_types.LONG:
|
||||
@@ -267,7 +265,7 @@ class Position:
|
||||
|
||||
# TODO: detect reduce_only order, and if so, see if you need to adjust qty and price (above variables)
|
||||
|
||||
self.exchange.charge_fee(qty * price)
|
||||
self.exchange.charge_fee(self.symbol, qty * price)
|
||||
|
||||
# order opens position
|
||||
if self.qty == 0:
|
||||
|
||||
@@ -7,10 +7,10 @@ from .Exchange import Exchange
|
||||
|
||||
|
||||
class SpotExchange(Exchange):
|
||||
def add_realized_pnl(self, realized_pnl: float):
|
||||
def add_realized_pnl(self, symbol: str, realized_pnl: float):
|
||||
pass
|
||||
|
||||
def charge_fee(self, amount):
|
||||
def charge_fee(self, symbol: str, amount):
|
||||
pass
|
||||
|
||||
# current holding assets
|
||||
|
||||
@@ -3,6 +3,7 @@ from .CompletedTrade import CompletedTrade
|
||||
from .Exchange import Exchange
|
||||
from .SpotExchange import SpotExchange
|
||||
from .FuturesExchange import FuturesExchange
|
||||
from .InverseFuturesExchange import InverseFuturesExchange
|
||||
from .Order import Order
|
||||
from .Position import Position
|
||||
from .Route import Route
|
||||
|
||||
@@ -75,7 +75,7 @@ config = {
|
||||
],
|
||||
},
|
||||
|
||||
# https://www.binance.com
|
||||
# https://www.binance.com/en/futures/BTC_USDT
|
||||
'Binance Futures': {
|
||||
'fee': 0.0004,
|
||||
|
||||
@@ -95,6 +95,26 @@ config = {
|
||||
],
|
||||
},
|
||||
|
||||
# https://www.binance.com/en/delivery/btcusd_perpetual
|
||||
'Binance Inverse Futures': {
|
||||
'fee': 0.0004,
|
||||
|
||||
# backtest mode only: accepted are 'spot' and 'futures'
|
||||
'type': 'inverse futures',
|
||||
|
||||
# accepted values are: 'cross' and 'isolated'
|
||||
'futures_leverage_mode': 'cross',
|
||||
# 1x, 2x, 10x, 50x, etc. Enter as integers
|
||||
'futures_leverage': 1,
|
||||
# Price per contract, also called the contract-multiplier
|
||||
'contract_size': 100,
|
||||
|
||||
'assets': [
|
||||
{'asset': 'BTC', 'balance': 1},
|
||||
{'asset': 'ETH', 'balance': 10},
|
||||
],
|
||||
},
|
||||
|
||||
# https://testnet.binancefuture.com
|
||||
'Testnet Binance Futures': {
|
||||
'fee': 0.0004,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Union, ValuesView
|
||||
|
||||
from jesse.config import config
|
||||
from jesse.models import SpotExchange, FuturesExchange
|
||||
from jesse.models import SpotExchange, FuturesExchange, InverseFuturesExchange
|
||||
from jesse.exceptions import InvalidConfig
|
||||
import jesse.helpers as jh
|
||||
|
||||
@@ -24,5 +24,12 @@ class ExchangesState:
|
||||
futures_leverage_mode=jh.get_config('env.exchanges.{}.futures_leverage_mode'.format(name)),
|
||||
futures_leverage=jh.get_config('env.exchanges.{}.futures_leverage'.format(name)),
|
||||
)
|
||||
elif exchange_type == 'inverse futures':
|
||||
self.storage[name] = InverseFuturesExchange(
|
||||
name, starting_assets, fee,
|
||||
futures_leverage_mode=jh.get_config('env.exchanges.{}.futures_leverage_mode'.format(name)),
|
||||
futures_leverage=jh.get_config('env.exchanges.{}.futures_leverage'.format(name)),
|
||||
contract_size=jh.get_config('env.exchanges.{}.contract_size'.format(name)),
|
||||
)
|
||||
else:
|
||||
raise InvalidConfig('Value for exchange type in your config file in not valid. Supported values are "spot" and "futures"')
|
||||
|
||||
@@ -10,7 +10,7 @@ import jesse.services.logger as logger
|
||||
import jesse.services.selectors as selectors
|
||||
from jesse import exceptions
|
||||
from jesse.enums import sides, trade_types, order_roles
|
||||
from jesse.models import CompletedTrade, Order, Route, FuturesExchange, SpotExchange, Position
|
||||
from jesse.models import CompletedTrade, Order, Route, FuturesExchange, SpotExchange, InverseFuturesExchange, Position
|
||||
from jesse.models.utils import store_completed_trade_into_db, store_order_into_db
|
||||
from jesse.services import metrics
|
||||
from jesse.services.broker import Broker
|
||||
@@ -1201,5 +1201,18 @@ class Strategy(ABC):
|
||||
return 1
|
||||
elif type(self.position.exchange) is FuturesExchange:
|
||||
return self.position.exchange.futures_leverage
|
||||
elif type(self.position.exchange) is InverseFuturesExchange:
|
||||
return self.position.exchange.futures_leverage
|
||||
else:
|
||||
raise ValueError('exchange type not supported!')
|
||||
|
||||
@property
|
||||
def contract_size(self):
|
||||
if self.position.exchange.type != 'inverse futures':
|
||||
raise exceptions.InvalidStrategy(
|
||||
'Only inverse futures have access to the contract_size property. You are accessing it from a {} exchange'.format(
|
||||
self.position.exchange.type
|
||||
)
|
||||
)
|
||||
|
||||
return self.position.exchange.contract_size
|
||||
|
||||
27
jesse/strategies/TestCanDetectInverseFutures/__init__.py
Normal file
27
jesse/strategies/TestCanDetectInverseFutures/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from jesse.strategies import Strategy
|
||||
from jesse import utils
|
||||
|
||||
|
||||
class TestCanDetectInverseFutures(Strategy):
|
||||
def should_long(self) -> bool:
|
||||
if self.index == 0:
|
||||
# current capital should be 1 BTC
|
||||
assert self.capital == 100
|
||||
assert self.position.exchange.type == 'inverse futures'
|
||||
assert self.symbol == 'BTC-PERP'
|
||||
assert self.leverage == 2
|
||||
assert self.contract_size == 1
|
||||
|
||||
return False
|
||||
|
||||
def should_short(self) -> bool:
|
||||
return False
|
||||
|
||||
def go_long(self):
|
||||
pass
|
||||
|
||||
def go_short(self):
|
||||
pass
|
||||
|
||||
def should_cancel(self):
|
||||
return False
|
||||
31
jesse/strategies/TestInverseFuturesLongTrade/__init__.py
Normal file
31
jesse/strategies/TestInverseFuturesLongTrade/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from jesse.strategies import Strategy
|
||||
from jesse import utils
|
||||
|
||||
|
||||
class TestInverseFuturesLongTrade(Strategy):
|
||||
def should_long(self) -> bool:
|
||||
if self.index == 0:
|
||||
assert self.position.exchange.contract_size == 100
|
||||
|
||||
return self.price == 10
|
||||
|
||||
def should_short(self) -> bool:
|
||||
return False
|
||||
|
||||
def go_long(self):
|
||||
entry = 10
|
||||
# assuming contract size is 100, buy with all capital
|
||||
qty = self.capital * self.price / 100
|
||||
self.buy = qty, entry
|
||||
self.take_profit = qty, 20
|
||||
|
||||
def go_short(self):
|
||||
pass
|
||||
|
||||
def should_cancel(self):
|
||||
return False
|
||||
|
||||
def update_position(self):
|
||||
if 20 > self.price > 10:
|
||||
print(self.position.qty)
|
||||
assert self.position.qty == 10
|
||||
@@ -13,6 +13,11 @@ def test_app_currency():
|
||||
])
|
||||
assert jh.app_currency() == 'USD'
|
||||
|
||||
router.set_routes([
|
||||
(exchanges.BITFINEX, 'ETH-PERP', timeframes.HOUR_3, 'Test19'),
|
||||
])
|
||||
assert jh.app_currency() == 'ETH'
|
||||
|
||||
|
||||
def test_app_mode():
|
||||
assert jh.app_mode() == 'backtest'
|
||||
@@ -29,6 +34,8 @@ def test_base_asset():
|
||||
assert jh.base_asset('DEFI-USDT') == 'DEFI'
|
||||
assert jh.base_asset('DEFI-USD') == 'DEFI'
|
||||
|
||||
assert jh.base_asset('BTC-PERP') == 'BTC'
|
||||
|
||||
|
||||
def test_binary_search():
|
||||
arr = [0, 11, 22, 33, 44, 54, 55]
|
||||
|
||||
14
tests/test_inverse_futures.py
Normal file
14
tests/test_inverse_futures.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from .utils import get_btc_candles, get_btc_and_eth_candles, set_up, single_route_backtest
|
||||
|
||||
|
||||
def test_can_detect_inverse_futures():
|
||||
single_route_backtest('TestCanDetectInverseFutures', is_futures_trading=True, is_inverse_futures=True, leverage=2)
|
||||
|
||||
|
||||
def test_long_trade():
|
||||
single_route_backtest(
|
||||
'TestInverseFuturesLongTrade',
|
||||
is_futures_trading=True,
|
||||
is_inverse_futures=True,
|
||||
contract_size=100
|
||||
)
|
||||
@@ -23,32 +23,51 @@ def get_btc_and_eth_candles():
|
||||
return candles
|
||||
|
||||
|
||||
def get_btc_candles():
|
||||
def get_btc_candles(symbol='BTC-USDT'):
|
||||
candles = {}
|
||||
candles[jh.key(exchanges.SANDBOX, 'BTC-USDT')] = {
|
||||
candles[jh.key(exchanges.SANDBOX, symbol)] = {
|
||||
'exchange': exchanges.SANDBOX,
|
||||
'symbol': 'BTC-USDT',
|
||||
'symbol': symbol,
|
||||
'candles': fake_range_candle_from_range_prices(range(1, 100))
|
||||
}
|
||||
return candles
|
||||
|
||||
|
||||
def set_up(routes=None, is_futures_trading=True, leverage=1, leverage_mode='cross', zero_fee=False):
|
||||
def set_up(
|
||||
routes=None,
|
||||
is_futures_trading=True,
|
||||
leverage=1,
|
||||
leverage_mode='cross',
|
||||
zero_fee=False,
|
||||
is_inverse_futures=False,
|
||||
contract_size=1,
|
||||
):
|
||||
reset_config()
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['assets'] = [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
{'asset': 'ETH', 'balance': 0},
|
||||
]
|
||||
|
||||
if is_inverse_futures:
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['assets'] = [
|
||||
{'asset': 'USDT', 'balance': 0},
|
||||
{'asset': 'BTC', 'balance': 100},
|
||||
]
|
||||
else:
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['assets'] = [
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
{'asset': 'ETH', 'balance': 0},
|
||||
]
|
||||
|
||||
if zero_fee:
|
||||
config['env']['exchanges']['Sandbox']['fee'] = 0
|
||||
|
||||
if is_futures_trading:
|
||||
# used only in futures trading
|
||||
if is_futures_trading and not is_inverse_futures:
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'futures'
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['futures_leverage_mode'] = leverage_mode
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['futures_leverage'] = leverage
|
||||
elif is_inverse_futures:
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'inverse futures'
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['futures_leverage_mode'] = leverage_mode
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['futures_leverage'] = leverage
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['contract_size'] = contract_size
|
||||
else:
|
||||
config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'spot'
|
||||
|
||||
@@ -58,14 +77,16 @@ def set_up(routes=None, is_futures_trading=True, leverage=1, leverage_mode='cros
|
||||
store.reset(True)
|
||||
|
||||
|
||||
def single_route_backtest(strategy_name: str, is_futures_trading=True, leverage=1):
|
||||
def single_route_backtest(strategy_name: str, is_futures_trading=True, is_inverse_futures=False, leverage=1, contract_size=1):
|
||||
"""
|
||||
used to simplify simple tests
|
||||
"""
|
||||
set_up(
|
||||
[(exchanges.SANDBOX, 'BTC-USDT', timeframes.MINUTE_1, strategy_name)],
|
||||
[(exchanges.SANDBOX, 'BTC-USDT' if not is_inverse_futures else 'BTC-PERP', timeframes.MINUTE_1, strategy_name)],
|
||||
is_futures_trading=is_futures_trading,
|
||||
leverage=leverage
|
||||
is_inverse_futures=is_inverse_futures,
|
||||
leverage=leverage,
|
||||
contract_size=contract_size
|
||||
)
|
||||
# dates are fake. just to pass required parameters
|
||||
backtest_mode.run('2019-04-01', '2019-04-02', get_btc_candles())
|
||||
backtest_mode.run('2019-04-01', '2019-04-02', get_btc_candles('BTC-USDT' if not is_inverse_futures else 'BTC-PERP'))
|
||||
|
||||
Reference in New Issue
Block a user