forked from hummingbot/hummingbot
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dev/pmm-changes' into development
- Loading branch information
Showing
22 changed files
with
3,184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import asyncio | ||
import aiohttp | ||
import logging | ||
import time | ||
from typing import Optional | ||
from hummingbot.core.network_base import NetworkBase, NetworkStatus | ||
from hummingbot.logger import HummingbotLogger | ||
from hummingbot.core.utils.async_utils import safe_ensure_future | ||
from decimal import Decimal | ||
from urllib.parse import urlparse | ||
|
||
|
||
class MarketIndicatorDataFeed(NetworkBase): | ||
cadf_logger: Optional[HummingbotLogger] = None | ||
|
||
@classmethod | ||
def logger(cls) -> HummingbotLogger: | ||
if cls.cadf_logger is None: | ||
cls.cadf_logger = logging.getLogger(__name__) | ||
return cls.cadf_logger | ||
|
||
def __init__(self, | ||
api_url, | ||
api_key: str = "", | ||
update_interval: float = 30.0, | ||
check_expiry: bool = False, | ||
expire_time: int = 300, | ||
use_indicator_time: bool = False): | ||
super().__init__() | ||
self._ready_event = asyncio.Event() | ||
self._shared_client: Optional[aiohttp.ClientSession] = None | ||
self._api_url = api_url | ||
self._api_name = urlparse(api_url).netloc | ||
self._api_auth_params = {'api_key': api_key} | ||
self._check_network_interval = 120.0 | ||
self._ev_loop = asyncio.get_event_loop() | ||
self._price: Decimal = 0 | ||
self._update_interval = 30.0 if (update_interval is None or update_interval < 1) else update_interval | ||
self._fetch_trend_task: Optional[asyncio.Task] = None | ||
self._market_trend = None | ||
self._last_check = 0 | ||
self._check_expiry = check_expiry | ||
self._expire_time = 300 if (expire_time is None or expire_time < 1) else (expire_time * 60) # Seconds | ||
self._use_indicator_time = use_indicator_time | ||
|
||
@property | ||
def name(self): | ||
return self._api_name | ||
|
||
@property | ||
def health_check_endpoint(self): | ||
return self._api_url | ||
|
||
def _http_client(self) -> aiohttp.ClientSession: | ||
if self._shared_client is None: | ||
self._shared_client = aiohttp.ClientSession() | ||
return self._shared_client | ||
|
||
async def check_network(self) -> NetworkStatus: | ||
client = self._http_client() | ||
async with client.request("GET", | ||
self.health_check_endpoint, | ||
params=self._api_auth_params) as resp: | ||
status_text = await resp.text() | ||
if resp.status != 200: | ||
raise Exception(f"Market Indicator Feed {self.name} server error: {status_text}") | ||
return NetworkStatus.CONNECTED | ||
|
||
def trend_is_up(self) -> bool: | ||
if not self._check_expiry or self._last_check > int(time.time() - self._expire_time): | ||
if self._market_trend is True: | ||
return True | ||
return False | ||
return None | ||
|
||
def trend_is_down(self) -> bool: | ||
if not self._check_expiry or self._last_check > int(time.time() - self._expire_time): | ||
if self._market_trend is False: | ||
return True | ||
return False | ||
return None | ||
|
||
async def fetch_trend_loop(self): | ||
while True: | ||
try: | ||
await self.fetch_trend() | ||
except asyncio.CancelledError: | ||
raise | ||
except Exception: | ||
self.logger().network(f"Error fetching a new price from {self._api_url}.", exc_info=True, | ||
app_warning_msg="Couldn't fetch newest price from CustomAPI. " | ||
"Check network connection.") | ||
|
||
await asyncio.sleep(self._update_interval) | ||
|
||
async def fetch_trend(self): | ||
try: | ||
rjson = {} | ||
client = self._http_client() | ||
async with client.request("GET", | ||
self._api_url, | ||
params=self._api_auth_params) as resp: | ||
if resp.status != 200: | ||
resp_text = await resp.text() | ||
raise Exception(f"Custom API Feed {self.name} server error: {resp_text}") | ||
rjson = await resp.json() | ||
respKeys = list(rjson.keys()) | ||
if 'market_indicator' in respKeys: | ||
self._market_trend = True if rjson['market_indicator'] == 'up' else False | ||
time_key = None | ||
if "timestamp" in respKeys and self._use_indicator_time: | ||
time_key = "timestamp" | ||
elif "time" in respKeys and self._use_indicator_time: | ||
time_key = "time" | ||
self._last_check = int(time.time()) | ||
if time_key is not None: | ||
try: | ||
self._last_check = int(rjson[time_key]) | ||
except Exception: | ||
pass | ||
self._ready_event.set() | ||
except Exception as e: | ||
raise Exception(f"Custom API Feed {self.name} server error: {e}") | ||
|
||
async def start_network(self): | ||
await self.stop_network() | ||
self._fetch_trend_task = safe_ensure_future(self.fetch_trend_loop()) | ||
|
||
async def stop_network(self): | ||
if self._fetch_trend_task is not None: | ||
self._fetch_trend_task.cancel() | ||
self._fetch_trend_task = None | ||
|
||
def start(self): | ||
NetworkBase.start(self) | ||
|
||
def stop(self): | ||
NetworkBase.stop(self) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#!/usr/bin/env python | ||
|
||
from .the_money_pit import TheMoneyPitStrategy | ||
from .asset_price_delegate import AssetPriceDelegate | ||
from .order_book_asset_price_delegate import OrderBookAssetPriceDelegate | ||
from .api_asset_price_delegate import APIAssetPriceDelegate | ||
from .inventory_cost_price_delegate import InventoryCostPriceDelegate | ||
from .market_indicator_delegate import MarketIndicatorDelegate | ||
__all__ = [ | ||
TheMoneyPitStrategy, | ||
AssetPriceDelegate, | ||
OrderBookAssetPriceDelegate, | ||
APIAssetPriceDelegate, | ||
InventoryCostPriceDelegate, | ||
MarketIndicatorDelegate, | ||
] |
4 changes: 4 additions & 0 deletions
4
hummingbot/strategy/the_money_pit/api_asset_price_delegate.pxd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from .asset_price_delegate cimport AssetPriceDelegate | ||
|
||
cdef class APIAssetPriceDelegate(AssetPriceDelegate): | ||
cdef object _custom_api_feed |
19 changes: 19 additions & 0 deletions
19
hummingbot/strategy/the_money_pit/api_asset_price_delegate.pyx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from .asset_price_delegate cimport AssetPriceDelegate | ||
from hummingbot.data_feed.custom_api_data_feed import CustomAPIDataFeed, NetworkStatus | ||
|
||
cdef class APIAssetPriceDelegate(AssetPriceDelegate): | ||
def __init__(self, api_url: str): | ||
super().__init__() | ||
self._custom_api_feed = CustomAPIDataFeed(api_url=api_url) | ||
self._custom_api_feed.start() | ||
|
||
cdef object c_get_mid_price(self): | ||
return self._custom_api_feed.get_price() | ||
|
||
@property | ||
def ready(self) -> bool: | ||
return self._custom_api_feed.network_status == NetworkStatus.CONNECTED | ||
|
||
@property | ||
def custom_api_feed(self) -> CustomAPIDataFeed: | ||
return self._custom_api_feed |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
|
||
cdef class AssetPriceDelegate: | ||
cdef object c_get_mid_price(self) |
16 changes: 16 additions & 0 deletions
16
hummingbot/strategy/the_money_pit/asset_price_delegate.pyx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from decimal import Decimal | ||
|
||
|
||
cdef class AssetPriceDelegate: | ||
# The following exposed Python functions are meant for unit tests | ||
# --------------------------------------------------------------- | ||
def get_mid_price(self) -> Decimal: | ||
return self.c_get_mid_price() | ||
# --------------------------------------------------------------- | ||
|
||
cdef object c_get_mid_price(self): | ||
raise NotImplementedError | ||
|
||
@property | ||
def ready(self) -> bool: | ||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#!/usr/bin/env python | ||
from typing import ( | ||
NamedTuple, | ||
List | ||
) | ||
from decimal import Decimal | ||
from hummingbot.core.event.events import OrderType | ||
|
||
ORDER_PROPOSAL_ACTION_CREATE_ORDERS = 1 | ||
ORDER_PROPOSAL_ACTION_CANCEL_ORDERS = 1 << 1 | ||
|
||
|
||
class OrdersProposal(NamedTuple): | ||
actions: int | ||
buy_order_type: OrderType | ||
buy_order_prices: List[Decimal] | ||
buy_order_sizes: List[Decimal] | ||
sell_order_type: OrderType | ||
sell_order_prices: List[Decimal] | ||
sell_order_sizes: List[Decimal] | ||
cancel_order_ids: List[str] | ||
|
||
|
||
class PricingProposal(NamedTuple): | ||
buy_order_prices: List[Decimal] | ||
sell_order_prices: List[Decimal] | ||
|
||
|
||
class SizingProposal(NamedTuple): | ||
buy_order_sizes: List[Decimal] | ||
sell_order_sizes: List[Decimal] | ||
|
||
|
||
class InventorySkewBidAskRatios(NamedTuple): | ||
bid_ratio: float | ||
ask_ratio: float | ||
|
||
|
||
class PriceSize: | ||
def __init__(self, price: Decimal, size: Decimal): | ||
self.price: Decimal = price | ||
self.size: Decimal = size | ||
|
||
def __repr__(self): | ||
return f"[ p: {self.price} s: {self.size} ]" | ||
|
||
|
||
class Proposal: | ||
def __init__(self, buys: List[PriceSize], sells: List[PriceSize]): | ||
self.buys: List[PriceSize] = buys | ||
self.sells: List[PriceSize] = sells | ||
|
||
def __repr__(self): | ||
return f"{len(self.buys)} buys: {', '.join([str(o) for o in self.buys])} " \ | ||
f"{len(self.sells)} sells: {', '.join([str(o) for o in self.sells])}" |
70 changes: 70 additions & 0 deletions
70
hummingbot/strategy/the_money_pit/inventory_cost_price_delegate.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from decimal import Decimal, InvalidOperation | ||
from typing import Optional | ||
|
||
from hummingbot.core.event.events import OrderFilledEvent, TradeType | ||
from hummingbot.model.inventory_cost import InventoryCost | ||
from hummingbot.model.sql_connection_manager import SQLConnectionManager | ||
|
||
s_decimal_0 = Decimal("0") | ||
|
||
|
||
class InventoryCostPriceDelegate: | ||
def __init__(self, sql: SQLConnectionManager, trading_pair: str) -> None: | ||
self.base_asset, self.quote_asset = trading_pair.split("-") | ||
self._session = sql.get_shared_session() | ||
|
||
@property | ||
def ready(self) -> bool: | ||
return True | ||
|
||
def get_price(self) -> Optional[Decimal]: | ||
record = InventoryCost.get_record( | ||
self._session, self.base_asset, self.quote_asset | ||
) | ||
|
||
if record is None or record.base_volume is None or record.base_volume is None: | ||
return None | ||
|
||
try: | ||
price = record.quote_volume / record.base_volume | ||
except InvalidOperation: | ||
# decimal.InvalidOperation: [<class 'decimal.DivisionUndefined'>] - both volumes are 0 | ||
return None | ||
return Decimal(price) | ||
|
||
def process_order_fill_event(self, fill_event: OrderFilledEvent) -> None: | ||
base_asset, quote_asset = fill_event.trading_pair.split("-") | ||
quote_volume = fill_event.amount * fill_event.price | ||
base_volume = fill_event.amount | ||
|
||
for fee_asset, fee_amount in fill_event.trade_fee.flat_fees: | ||
if fill_event.trade_type == TradeType.BUY: | ||
if fee_asset == base_asset: | ||
base_volume -= fee_amount | ||
elif fee_asset == quote_asset: | ||
quote_volume += fee_amount | ||
else: | ||
# Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity | ||
base_volume /= 1 + fill_event.trade_fee.percent | ||
else: | ||
if fee_asset == base_asset: | ||
base_volume += fee_amount | ||
elif fee_asset == quote_asset: | ||
# TODO: with new logic, this quote volume adjustment does not impacts anything | ||
quote_volume -= fee_amount | ||
else: | ||
# Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity | ||
base_volume /= 1 + fill_event.trade_fee.percent | ||
|
||
if fill_event.trade_type == TradeType.SELL: | ||
record = InventoryCost.get_record(self._session, base_asset, quote_asset) | ||
if not record: | ||
raise RuntimeError("Sold asset without having inventory price set. This should not happen.") | ||
|
||
# We're keeping initial buy price intact. Profits are not changing inventory price intentionally. | ||
quote_volume = -(Decimal(record.quote_volume / record.base_volume) * base_volume) | ||
base_volume = -base_volume | ||
|
||
InventoryCost.add_volume( | ||
self._session, base_asset, quote_asset, base_volume, quote_volume | ||
) |
5 changes: 5 additions & 0 deletions
5
hummingbot/strategy/the_money_pit/inventory_skew_calculator.pxd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
cdef object c_calculate_bid_ask_ratios_from_base_asset_ratio(double base_asset_amount, | ||
double quote_asset_amount, | ||
double price, | ||
double target_base_asset_ratio, | ||
double base_asset_range) |
Oops, something went wrong.