Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ The Hummingbot codebase is free and publicly available under the Apache 2.0 open
* [Discord](https://discord.gg/hummingbot): The main gathering spot for the global Hummingbot community
* [YouTube](https://www.youtube.com/c/hummingbot): Videos that teach you how to get the most of of Hummingbot
* [Twitter](https://twitter.com/_hummingbot): Get the latest announcements about Hummingbot
* [Reported Volumes](https://p.datadoghq.com/sb/a96a744f5-a15479d77992ccba0d23aecfd4c87a52): Reported trading volumes across all Hummingbot instances
* [Newsletter](https://hummingbot.substack.com): Get our newsletter whenever we ship a new release


Expand Down
3 changes: 0 additions & 3 deletions conf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,4 @@
web3_test_private_key_a = os.getenv("TEST_WALLET_PRIVATE_KEY_A")
web3_test_private_key_b = os.getenv("TEST_WALLET_PRIVATE_KEY_B")
web3_test_private_key_c = os.getenv("TEST_WALLET_PRIVATE_KEY_C")

coinalpha_order_book_api_username = "***REMOVED***"
coinalpha_order_book_api_password = "***REMOVED***"
"""
77 changes: 1 addition & 76 deletions hummingbot/client/config/client_config_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@
from hummingbot.client.config.config_validators import validate_bool, validate_float
from hummingbot.client.settings import DEFAULT_GATEWAY_CERTS_PATH, DEFAULT_LOG_FILE_PATH, AllConnectorSettings
from hummingbot.connector.connector_base import ConnectorBase
from hummingbot.connector.connector_metrics_collector import (
DummyMetricsCollector,
MetricsCollector,
TradeVolumeMetricCollector,
)
from hummingbot.connector.connector_metrics_collector import DummyMetricsCollector
from hummingbot.connector.exchange.binance.binance_utils import BinanceConfigMap
from hummingbot.connector.exchange.gate_io.gate_io_utils import GateIOConfigMap
from hummingbot.connector.exchange.kraken.kraken_utils import KrakenConfigMap
Expand Down Expand Up @@ -387,60 +383,6 @@ class CommandsTimeoutConfigMap(BaseClientModel):
model_config = ConfigDict(title="commands_timeout")


class AnonymizedMetricsMode(BaseClientModel, ABC):
@abstractmethod
def get_collector(
self,
connector: ConnectorBase,
rate_provider: RateOracle,
instance_id: str,
valuation_token: str = "USDT",
) -> MetricsCollector:
...


class AnonymizedMetricsDisabledMode(AnonymizedMetricsMode):
model_config = ConfigDict(title="anonymized_metrics_disabled")

def get_collector(
self,
connector: ConnectorBase,
rate_provider: RateOracle,
instance_id: str,
valuation_token: str = "USDT",
) -> MetricsCollector:
return DummyMetricsCollector()


class AnonymizedMetricsEnabledMode(AnonymizedMetricsMode):
anonymized_metrics_interval_min: Decimal = Field(
default=Decimal("15"),
gt=Decimal("0"),
json_schema_extra={"prompt": lambda cm: "How often do you want to send the anonymized metrics (in minutes)"},
)
model_config = ConfigDict(title="anonymized_metrics_enabled")

def get_collector(
self,
connector: ConnectorBase,
rate_provider: RateOracle,
instance_id: str,
valuation_token: str = "USDT",
) -> MetricsCollector:
instance = TradeVolumeMetricCollector(
connector=connector,
activation_interval=self.anonymized_metrics_interval_min,
rate_provider=rate_provider,
instance_id=instance_id,
valuation_token=valuation_token,
)
return instance


METRICS_MODES = {
AnonymizedMetricsDisabledMode.model_config["title"]: AnonymizedMetricsDisabledMode,
AnonymizedMetricsEnabledMode.model_config["title"]: AnonymizedMetricsEnabledMode,
}


class RateSourceModeBase(BaseClientModel, ABC):
Expand Down Expand Up @@ -717,11 +659,6 @@ class ClientConfigMap(BaseClientModel):
json_schema_extra={"prompt": lambda cm: "Where would you like to save certificates that connect your bot to Gateway? (default 'certs')"},
)

anonymized_metrics_mode: Union[tuple(METRICS_MODES.values())] = Field(
default=AnonymizedMetricsEnabledMode(),
description="Whether to enable aggregated order and trade data collection",
json_schema_extra={"prompt": lambda cm: f"Select the desired metrics mode ({'/'.join(list(METRICS_MODES.keys()))})"},
)
command_shortcuts: List[CommandShortcutModel] = Field(
default=[
CommandShortcutModel(
Expand Down Expand Up @@ -828,18 +765,6 @@ def validate_db_mode(cls, v: Union[(str, Dict) + tuple(DB_MODES.values())]):
sub_model = DB_MODES[v].model_construct()
return sub_model

@field_validator("anonymized_metrics_mode", mode="before")
@classmethod
def validate_anonymized_metrics_mode(cls, v: Union[(str, Dict) + tuple(METRICS_MODES.values())]):
if isinstance(v, tuple(METRICS_MODES.values()) + (Dict,)):
sub_model = v
elif v not in METRICS_MODES:
raise ValueError(
f"Invalid metrics mode, please choose a value from {list(METRICS_MODES.keys())}."
)
else:
sub_model = METRICS_MODES[v].model_construct()
return sub_model

@field_validator("rate_oracle_source", mode="before")
@classmethod
Expand Down
10 changes: 0 additions & 10 deletions hummingbot/client/config/conf_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

from hummingbot import root_path
from hummingbot.client.config.client_config_map import (
AnonymizedMetricsDisabledMode,
AnonymizedMetricsEnabledMode,
ClientConfigMap,
ColorConfigMap,
DBOtherMode,
Expand Down Expand Up @@ -181,14 +179,6 @@ def _migrate_global_config_modes(client_config_map: ClientConfigAdapter, data: D
client_config_map.mqtt_bridge, data, "mqtt_autostart"
)

anonymized_metrics_enabled = data.pop("anonymized_metrics_enabled")
anonymized_metrics_interval_min = data.pop("anonymized_metrics_interval_min")
if anonymized_metrics_enabled:
client_config_map.anonymized_metrics_mode = AnonymizedMetricsEnabledMode(
anonymized_metrics_interval_min=anonymized_metrics_interval_min
)
else:
client_config_map.anonymized_metrics_mode = AnonymizedMetricsDisabledMode()

_migrate_global_config_field(
client_config_map.global_token, data, "global_token", "global_token_name"
Expand Down
8 changes: 3 additions & 5 deletions hummingbot/connector/connector_base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ from hummingbot.core.event.events import MarketEvent, OrderFilledEvent
from hummingbot.core.network_iterator import NetworkIterator
from hummingbot.core.rate_oracle.rate_oracle import RateOracle
from hummingbot.core.utils.estimate_fee import estimate_fee
from hummingbot.connector.connector_metrics_collector import DummyMetricsCollector

if TYPE_CHECKING:
from hummingbot.client.config.client_config_map import ClientConfigMap
Expand Down Expand Up @@ -65,11 +66,8 @@ cdef class ConnectorBase(NetworkIterator):
self._current_trade_fills = set()
self._exchange_order_ids = dict()
self._trade_fee_schema = None
self._trade_volume_metric_collector = client_config_map.anonymized_metrics_mode.get_collector(
connector=self,
rate_provider=RateOracle.get_instance(),
instance_id=client_config_map.instance_id,
)
# Telemetry removed: use a no-op collector to maintain interface
self._trade_volume_metric_collector = DummyMetricsCollector()
self._client_config: Union[ClientConfigAdapter, ClientConfigMap] = client_config_map # for IDE autocomplete

@property
Expand Down
143 changes: 10 additions & 133 deletions hummingbot/connector/connector_metrics_collector.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
import asyncio
import json
"""Local-only metrics collector implementations.

This module previously implemented telemetry features that sent trade volume
metrics to an external cloud service. To comply with strict on-premise
requirements, all remote reporting functionality has been removed. Only a
``DummyMetricsCollector`` placeholder remains so that existing calls do not
break, but it performs no network activity.
"""

import logging
import platform
from abc import ABC, abstractmethod
from decimal import Decimal
from os.path import dirname, join, realpath
from typing import TYPE_CHECKING, List, Tuple
from typing import TYPE_CHECKING

from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair
from hummingbot.core.event.event_forwarder import EventForwarder
from hummingbot.core.event.events import MarketEvent, OrderFilledEvent
from hummingbot.core.rate_oracle.rate_oracle import RateOracle
from hummingbot.core.utils.async_utils import safe_ensure_future
from hummingbot.logger import HummingbotLogger
from hummingbot.logger.log_server_client import LogServerClient

if TYPE_CHECKING:
from hummingbot.connector.connector_base import ConnectorBase

with open(realpath(join(dirname(__file__), '../VERSION'))) as version_file:
CLIENT_VERSION = version_file.read().strip()


class MetricsCollector(ABC):

DEFAULT_METRICS_SERVER_URL = "https://api.coinalpha.com/reporting-proxy-v2"

@abstractmethod
def start(self):
raise NotImplementedError
Expand All @@ -52,118 +44,3 @@ def stop(self):
def process_tick(self, timestamp: float):
# Nothing is required
pass


class TradeVolumeMetricCollector(MetricsCollector):

_logger = None

METRIC_NAME = "filled_usdt_volume"

def __init__(self,
connector: 'ConnectorBase',
activation_interval: Decimal,
rate_provider: RateOracle,
instance_id: str,
valuation_token: str = "USDT"):
super().__init__()
self._connector = connector
self._activation_interval = activation_interval
self._dispatcher = LogServerClient(log_server_url=self.DEFAULT_METRICS_SERVER_URL)
self._rate_provider = rate_provider
self._instance_id = instance_id
self._client_version = CLIENT_VERSION
self._valuation_token = valuation_token
self._last_process_tick_timestamp = 0
self._last_executed_collection_process = None
self._collected_events = []

self._fill_event_forwarder = EventForwarder(self._register_fill_event)

self._event_pairs: List[Tuple[MarketEvent, EventForwarder]] = [
(MarketEvent.OrderFilled, self._fill_event_forwarder),
]

@classmethod
def logger(cls) -> HummingbotLogger:
if cls._logger is None:
cls._logger = logging.getLogger(__name__)
return cls._logger

def start(self):
self._dispatcher.start()
for event_pair in self._event_pairs:
self._connector.add_listener(event_pair[0], event_pair[1])

def stop(self):
self.trigger_metrics_collection_process()
for event_pair in self._event_pairs:
self._connector.remove_listener(event_pair[0], event_pair[1])
self._dispatcher.stop()

def process_tick(self, timestamp: float):
inactivity_time = timestamp - self._last_process_tick_timestamp
if inactivity_time >= self._activation_interval:
self._last_process_tick_timestamp = timestamp
self.trigger_metrics_collection_process()

def trigger_metrics_collection_process(self):
events_to_process = self._collected_events
self._collected_events = []
self._last_executed_collection_process = safe_ensure_future(
self.collect_metrics(events=events_to_process))

async def collect_metrics(self, events: List[OrderFilledEvent]):
try:
total_volume = Decimal("0")

for fill_event in events:
trade_base, trade_quote = split_hb_trading_pair(fill_event.trading_pair)
from_quote_conversion_pair = combine_to_hb_trading_pair(base=trade_quote, quote=self._valuation_token)
rate = await self._rate_provider.stored_or_live_rate(from_quote_conversion_pair)

if rate is not None:
total_volume += fill_event.amount * fill_event.price * rate
else:
from_base_conversion_pair = combine_to_hb_trading_pair(base=trade_base, quote=self._valuation_token)
rate = await self._rate_provider.stored_or_live_rate(from_base_conversion_pair)
if rate is not None:
total_volume += fill_event.amount * rate
else:
self.logger().debug(f"Could not find a conversion rate rate using Rate Oracle for any of "
f"the pairs {from_quote_conversion_pair} or {from_base_conversion_pair}")

if total_volume > Decimal("0"):
self._dispatch_trade_volume(total_volume)
except asyncio.CancelledError:
raise
except Exception:
self._collected_events.extend(events)

def _dispatch_trade_volume(self, volume: Decimal):
metric_request = {
"url": f"{self._dispatcher.log_server_url}/client_metrics",
"method": "POST",
"request_obj": {
"headers": {
'Content-Type': "application/json"
},
"data": json.dumps({
"source": "hummingbot",
"name": self.METRIC_NAME,
"instance_id": self._instance_id,
"exchange": self._connector.name,
"version": self._client_version,
"system": f"{platform.system()} {platform.release()}({platform.platform()})",
"value": str(volume)}),
"params": {"ddtags": f"instance_id:{self._instance_id},"
f"client_version:{self._client_version},"
f"type:metrics",
"ddsource": "hummingbot-client"}
}
}

self._dispatcher.request(metric_request)

def _register_fill_event(self, event: OrderFilledEvent):
self._collected_events.append(event)
Loading