Skip to content

Commit

Permalink
[Funding] Add implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Herklos committed Mar 14, 2020
1 parent 4c21609 commit ed93c9e
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 9 deletions.
25 changes: 25 additions & 0 deletions octobot_trading/channels/funding.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# cython: language_level=3
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.

from octobot_trading.channels.exchange_channel cimport ExchangeChannel, ExchangeChannelProducer


cdef class FundingProducer(ExchangeChannelProducer):
pass

cdef class FundingChannel(ExchangeChannel):
pass
57 changes: 57 additions & 0 deletions octobot_trading/channels/funding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
from asyncio import CancelledError

from octobot_channels.constants import CHANNEL_WILDCARD
from octobot_trading.channels.exchange_channel import ExchangeChannel, ExchangeChannelProducer, ExchangeChannelConsumer


class FundingProducer(ExchangeChannelProducer):
async def push(self, symbol, funding_rate, next_funding_time, timestamp):
await self.perform(symbol, funding_rate, next_funding_time, timestamp)

async def perform(self, symbol, funding_rate, next_funding_time, timestamp):
try:
if self.channel.get_filtered_consumers(
symbol=CHANNEL_WILDCARD) or self.channel.get_filtered_consumers(symbol=symbol):
await self.channel.exchange_manager.get_symbol_data(symbol) \
.handle_funding_update(funding_rate=funding_rate,
next_funding_time=next_funding_time,
timestamp=timestamp)
await self.send(symbol=symbol,
funding_rate=funding_rate,
next_funding_time=next_funding_time,
timestamp=timestamp)
except CancelledError:
self.logger.info("Update tasks cancelled.")
except Exception as e:
self.logger.exception(e, True, f"Exception when triggering update: {e}")

async def send(self, symbol, funding_rate, next_funding_time, timestamp):
for consumer in self.channel.get_filtered_consumers(symbol=symbol):
await consumer.queue.put({
"exchange": self.channel.exchange_manager.exchange_name,
"exchange_id": self.channel.exchange_manager.id,
"symbol": symbol,
"funding_rate": funding_rate,
"next_funding_time": next_funding_time,
"timestamp": timestamp
})


class FundingChannel(ExchangeChannel):
PRODUCER_CLASS = FundingProducer
CONSUMER_CLASS = ExchangeChannelConsumer
5 changes: 1 addition & 4 deletions octobot_trading/channels/positions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@
from asyncio import CancelledError

from octobot_channels.constants import CHANNEL_WILDCARD
from octobot_channels.producer import Producer
from octobot_commons.logging.logging_util import get_logger

from octobot_trading.channels.exchange_channel import ExchangeChannel, ExchangeChannelProducer, ExchangeChannelConsumer
from octobot_trading.enums import ExchangeConstantsOrderColumns, ExchangeConstantsPositionColumns, PositionStatus
from octobot_trading.enums import ExchangeConstantsOrderColumns, ExchangeConstantsPositionColumns


class PositionsProducer(ExchangeChannelProducer):
Expand Down
4 changes: 3 additions & 1 deletion octobot_trading/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
KLINE_CHANNEL = "Kline"
OHLCV_CHANNEL = "OHLCV"
MARK_PRICE_CHANNEL = "MarkPrice"
FUNDING_CHANNEL = "Funding"

# Exchange personal data
TRADES_CHANNEL = "Trades"
Expand All @@ -98,5 +99,6 @@
ORDERS_CHANNEL: [Feeds.ORDERS],
MARK_PRICE_CHANNEL: [Feeds.MARK_PRICE],
BALANCE_CHANNEL: [Feeds.PORTFOLIO],
POSITIONS_CHANNEL: [Feeds.POSITION]
POSITIONS_CHANNEL: [Feeds.POSITION],
FUNDING_CHANNEL: [Feeds.FUNDING]
}
29 changes: 29 additions & 0 deletions octobot_trading/data_manager/funding_manager.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# cython: language_level=3
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
from octobot_trading.util.initializable cimport Initializable

cdef class FundingManager(Initializable):
cdef object logger

cdef public double funding_rate
cdef public double next_updated
cdef public double last_updated

cpdef void reset_funding(self)

cpdef funding_update(self, double funding_rate, double next_funding_time, double timestamp)

43 changes: 43 additions & 0 deletions octobot_trading/data_manager/funding_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
from math import nan

from octobot_commons.logging.logging_util import get_logger
from octobot_trading.util.initializable import Initializable


class FundingManager(Initializable):
def __init__(self):
super().__init__()
self.logger = get_logger(self.__class__.__name__)
self.funding_rate = nan
self.next_updated = 0
self.last_updated = 0
self.reset_funding()

async def initialize_impl(self):
self.reset_funding()

def reset_funding(self):
self.funding_rate = nan
self.next_updated = 0
self.last_updated = 0

def funding_update(self, funding_rate, next_funding_time, timestamp):
if funding_rate and next_funding_time:
self.funding_rate = funding_rate
self.next_updated = next_funding_time
self.last_updated = timestamp
7 changes: 7 additions & 0 deletions octobot_trading/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ class TraderOrderType(Enum):
UNKNOWN = "unknown" # default value when the order type info is missing in the exchange data


class ExchangeConstantsFundingColumns(Enum):
SYMBOL = "symbol"
TIMESTAMP = "timestamp"
FUNDING_RATE = "funding_rate"
NEXT_FUNDING_TIME = "next_funding_time"


class ExchangeConstantsTickersColumns(Enum):
SYMBOL = "symbol"
TIMESTAMP = "timestamp"
Expand Down
2 changes: 2 additions & 0 deletions octobot_trading/exchanges/data/exchange_symbol_data.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
from octobot_trading.data_manager.funding_manager cimport FundingManager
from octobot_trading.data_manager.order_book_manager cimport OrderBookManager
from octobot_trading.data_manager.prices_manager cimport PricesManager
from octobot_trading.data_manager.recent_trades_manager cimport RecentTradesManager
Expand All @@ -32,6 +33,7 @@ cdef class ExchangeSymbolData:
cdef public PricesManager prices_manager
cdef public RecentTradesManager recent_trades_manager
cdef public TickerManager ticker_manager
cdef public FundingManager funding_manager

cpdef list handle_recent_trade_update(self, object recent_trades, bint replace_all=*, bint partial=*) # recent trades can be list or dict
cpdef void handle_order_book_update(self, list asks, list bids, bint is_delta=*)
Expand Down
6 changes: 6 additions & 0 deletions octobot_trading/exchanges/data/exchange_symbol_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from octobot_commons.logging.logging_util import get_logger

from octobot_trading.data_manager.candles_manager import CandlesManager
from octobot_trading.data_manager.funding_manager import FundingManager
from octobot_trading.data_manager.kline_manager import KlineManager
from octobot_trading.data_manager.order_book_manager import OrderBookManager
from octobot_trading.data_manager.prices_manager import PricesManager
Expand All @@ -36,6 +37,7 @@ def __init__(self, exchange_manager, symbol):
self.prices_manager = PricesManager()
self.recent_trades_manager = RecentTradesManager()
self.ticker_manager = TickerManager()
self.funding_manager = FundingManager() if self.exchange_manager.is_margin else None

self.symbol_candles = {}
self.symbol_klines = {}
Expand Down Expand Up @@ -103,3 +105,7 @@ async def handle_kline_update(self, time_frame, kline):
return

symbol_klines.kline_update(kline)

async def handle_funding_update(self, funding_rate, next_funding_time, timestamp):
if self.funding_manager:
self.funding_manager.funding_update(funding_rate, next_funding_time, timestamp)
13 changes: 13 additions & 0 deletions octobot_trading/exchanges/margin/margin_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@


class MarginExchange(RestExchange):
# Mark price params
MARK_PRICE_IN_POSITION = False

# Funding rate params
FUNDING_IN_TICKER = False
FUNDING_WITH_MARK_PRICE = False

"""
CCXT margin library wrapper
"""
Expand All @@ -37,6 +44,12 @@ async def get_positions_history(self):
async def get_symbol_leverage(self, symbol: str):
raise NotImplementedError("get_symbol_leverage is not implemented")

async def get_mark_price(self, symbol: str, limit: int = 1):
raise NotImplementedError("get_mark_price is not implemented")

async def get_funding_rate(self, symbol: str, limit: int = 1):
raise NotImplementedError("get_funding_rate is not implemented")

async def set_symbol_leverage(self, symbol: str, leverage: int):
raise NotImplementedError("set_symbol_leverage is not implemented")

Expand Down
3 changes: 2 additions & 1 deletion octobot_trading/producers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
from octobot_trading.producers.funding_updater import FundingUpdater
from octobot_trading.producers.orders_updater import OpenOrdersUpdater, CloseOrdersUpdater
from octobot_trading.producers.prices_updater import MarkPriceUpdater

Expand All @@ -35,6 +36,6 @@ def __init__(self, order_id):


UNAUTHENTICATED_UPDATER_PRODUCERS = [OHLCVUpdater, OrderBookUpdater, RecentTradeUpdater, TickerUpdater, KlineUpdater,
MarkPriceUpdater]
MarkPriceUpdater, FundingUpdater]
AUTHENTICATED_UPDATER_PRODUCERS = [BalanceUpdater, CloseOrdersUpdater, OpenOrdersUpdater, TradesUpdater,
PositionsUpdater, BalanceProfitabilityUpdater]
22 changes: 22 additions & 0 deletions octobot_trading/producers/funding_updater.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# cython: language_level=3
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.

from octobot_trading.channels.funding cimport FundingProducer


cdef class FundingUpdater(FundingProducer):
pass
80 changes: 80 additions & 0 deletions octobot_trading/producers/funding_updater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# pylint: disable=E0611
# Drakkar-Software OctoBot-Trading
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
import asyncio
import time

from ccxt.base.errors import NotSupported
from octobot_commons.constants import HOURS_TO_SECONDS

from octobot_trading.constants import FUNDING_CHANNEL
from octobot_trading.channels.funding import FundingProducer
from octobot_trading.enums import ExchangeConstantsFundingColumns


class FundingUpdater(FundingProducer):
CHANNEL_NAME = FUNDING_CHANNEL

FUNDING_REFRESH_TIME = 2 * HOURS_TO_SECONDS
FUNDING_REFRESH_TIME_MIN = 0.2 * HOURS_TO_SECONDS
FUNDING_REFRESH_TIME_MAX = 8 * HOURS_TO_SECONDS

def __init__(self, channel):
super().__init__(channel)

async def start(self):
if not self._should_run():
return

while not self.should_stop and not self.channel.is_paused:
next_funding_time = None
sleep_time = self.FUNDING_REFRESH_TIME
try:
for pair in self.channel.exchange_manager.exchange_config.traded_symbol_pairs:
funding: dict = await self.channel.exchange_manager.exchange.get_funding_rate(pair)

if funding:
next_funding_time = funding[ExchangeConstantsFundingColumns.NEXT_FUNDING_TIME]
await self.push(symbol=pair,
funding_rate=funding[ExchangeConstantsFundingColumns.FUNDING_RATE],
next_funding_time=next_funding_time,
timestamp=funding[ExchangeConstantsFundingColumns.TIMESTAMP])
except NotSupported:
self.logger.warning(f"{self.channel.exchange_manager.exchange_name} is not supporting updates")
await self.pause()
except Exception as e:
sleep_time = self.FUNDING_REFRESH_TIME_MIN
self.logger.exception(e, True, f"Fail to update funding rate : {e}")
finally:
if next_funding_time:
should_sleep_time = time.time() - next_funding_time
sleep_time = should_sleep_time if should_sleep_time < self.FUNDING_REFRESH_TIME_MAX else \
self.FUNDING_REFRESH_TIME
await asyncio.sleep(sleep_time)

def _should_run(self) -> bool:
if not self.channel.exchange_manager.is_margin:
return False
else:
return not self.channel.exchange_manager.exchange.FUNDING_IN_TICKER and \
not self.channel.exchange_manager.exchange.FUNDING_WITH_MARK_PRICE

async def resume(self) -> None:
if not self._should_run():
return
await super().resume()
if not self.is_running:
await self.run()
3 changes: 1 addition & 2 deletions octobot_trading/producers/positions_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@

from ccxt.base.errors import NotSupported

from octobot_trading.constants import POSITIONS_CHANNEL
from octobot_trading.channels.positions import PositionsProducer
from octobot_trading.enums import ExchangeConstantsOrderColumns
from octobot_trading.constants import POSITIONS_CHANNEL


class PositionsUpdater(PositionsProducer):
Expand Down

0 comments on commit ed93c9e

Please sign in to comment.