Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class AbstractAuthenticatedExchangeTester:
DEFAULT_MAX_DEFAULT_ORDERS_COUNT = trading_constants.DEFAULT_MAX_DEFAULT_ORDERS_COUNT
DEFAULT_MAX_STOP_ORDERS_COUNT = trading_constants.DEFAULT_MAX_STOP_ORDERS_COUNT
SLEEP_SECONDS_BEFORE_CHECKING_PORTFOLIO = 0 # used to wait before fetching portfolio after creating/cancelling an order
ENABLE_MARKET_ORDER_CONVERSION_CHECKS = False # set True to run after_market_order_created during market order tests

# Implement all "test_[name]" methods, call super() to run the test, pass to ignore it.
# Override the "inner_test_[name]" method to override a test content.
Expand Down Expand Up @@ -714,6 +715,8 @@ async def inner_test_create_and_fill_market_orders(self):
post_buy_portfolio = {}
try:
self.check_created_market_order(first_market_order, size, side)
if self.ENABLE_MARKET_ORDER_CONVERSION_CHECKS:
await self.after_market_order_created(first_market_order)
filled_order = await self.wait_for_fill(first_market_order)
parsed_filled_order = personal_data.create_order_instance_from_raw(
self.exchange_manager.trader,
Expand All @@ -735,6 +738,8 @@ async def inner_test_create_and_fill_market_orders(self):
other_side = trading_enums.TradeOrderSide.SELL if side == trading_enums.TradeOrderSide.BUY else trading_enums.TradeOrderSide.BUY
second_market_order = await self.create_market_order(current_price, mirror_size, other_side)
self.check_created_market_order(second_market_order, mirror_size, other_side)
if self.ENABLE_MARKET_ORDER_CONVERSION_CHECKS:
await self.after_market_order_created(second_market_order)
await self.wait_for_fill(second_market_order)
await self.sleep_before_checking_portfolio()
post_sell_portfolio = await self.get_portfolio()
Expand Down Expand Up @@ -1702,6 +1707,9 @@ async def _get_recent_trades_until(self, validation_func, timeout):
f"message: {message}"
)

async def after_market_order_created(self, market_order) -> None:
pass

async def wait_for_fill(self, order):
def parse_is_filled(raw_order):
return personal_data.parse_order_status(raw_order) in {trading_enums.OrderStatus.FILLED,
Expand Down
45 changes: 40 additions & 5 deletions octobot/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import octobot.logger as octobot_logger
import octobot.community as octobot_community
import octobot.community.errors
import octobot.community.node_journal.lifecycle as journal_startup
import octobot.limits as limits
except ImportError as err:
traceback.print_exc()
Expand Down Expand Up @@ -478,8 +479,36 @@ def _log_startup_distribution_mode(logger, args) -> None:
logger.info(startup_message)


def _record_cli_startup_failure(
error: Exception,
startup_phase: str,
config: configuration.Configuration | None = None,
) -> None:
try:
if config is not None:
distribution = configuration_manager.get_distribution(config.config)
elif constants.FORCED_DISTRIBUTION:
distribution = enums.OctoBotDistribution(constants.FORCED_DISTRIBUTION)
else:
return
if distribution is not enums.OctoBotDistribution.NODE:
return
journal_startup.record_node_startup_failed(
error,
startup_phase=startup_phase,
force_exit=True,
config=config,
)
except Exception:
octobot_commons.logging.get_logger("OctoBot").debug(
"Failed to record CLI startup failure in node journal",
exc_info=True,
)


def start_octobot(args, default_config_file=None):
logger = None
config = None
try:
if args.version:
print(constants.LONG_VERSION)
Expand Down Expand Up @@ -518,8 +547,6 @@ def start_octobot(args, default_config_file=None):
if not config.is_loaded():
raise errors.ConfigError

octobot_community.ActivityMetrics.initialize_tracker(config)

# Handle utility methods before bot initializing if possible
if args.encrypter:
commands.exchange_keys_encrypter()
Expand All @@ -531,6 +558,8 @@ def start_octobot(args, default_config_file=None):
community_auth = None if args.backtesting else asyncio.run(
_get_authenticated_community_if_possible(config, logger)
)
octobot_community.init_sentry_tracker()
journal_startup.initialize_journal(config)

# Startup order matters: sync user and tentacles/community config must run before
# profile activation. First boot (empty user/) has no profiles until tentacles
Expand Down Expand Up @@ -604,20 +633,23 @@ def start_octobot(args, default_config_file=None):
force_error_exit = False
except errors.RemoteConfigError as err:
logger.exception(err)
_record_cli_startup_failure(err, "remote_config", config)
force_error_exit = True

except errors.ConfigError as err:
logger.error("OctoBot can't start without a valid " + common_constants.CONFIG_FILE
+ " configuration file.\nError: " + str(err) + "\nYou can use " +
constants.DEFAULT_CONFIG_FILE + " as an example to fix it.")
_record_cli_startup_failure(err, "config", config)
force_error_exit = True

except errors.NoProfileError:
except errors.NoProfileError as err:
logger.error("Missing default profiles. OctoBot can't start without a valid default profile configuration. "
"Please make sure that the {config.profiles_path} "
f"folder is accessible. To reinstall default profiles, delete the "
f"'{tentacles_manager_constants.TENTACLES_PATH}' "
f"folder or start OctoBot with the following arguments: tentacles --install --all")
_record_cli_startup_failure(err, "no_profile", config)
force_error_exit = True

except ModuleNotFoundError as err:
Expand All @@ -626,18 +658,21 @@ def start_octobot(args, default_config_file=None):
"please use the following command:\nstart.py tentacles --install --all")
else:
logger.exception(err)
_record_cli_startup_failure(err, "module_not_found", config)
force_error_exit = True

except errors.ConfigEvaluatorError:
except errors.ConfigEvaluatorError as err:
logger.error("OctoBot can't start without a valid configuration file.\n"
"This file is generated on tentacle "
"installation using the following command:\nstart.py tentacles --install --all")
_record_cli_startup_failure(err, "config_evaluator", config)
force_error_exit = True

except errors.ConfigTradingError:
except errors.ConfigTradingError as err:
logger.error("OctoBot can't start without a valid configuration file.\n"
"This file is generated on tentacle "
"installation using the following command:\nstart.py tentacles --install --all")
_record_cli_startup_failure(err, "config_trading", config)
force_error_exit = True
if force_error_exit:
octobot_community.flush_tracker()
Expand Down
15 changes: 15 additions & 0 deletions octobot/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import octobot.constants as constants
import octobot.community.tentacles_packages as community_tentacles_packages
import octobot.configuration_manager as configuration_manager
import octobot.enums as enums
import octobot.community.node_journal.lifecycle as journal_startup

COMMANDS_LOGGER_NAME = "Commands"
IGNORED_COMMAND_WHEN_RESTART = ["-u", "--update"]
Expand Down Expand Up @@ -340,6 +342,18 @@ def run_bot(bot, logger):
bot.task_manager.run_forever(start_bot(bot, logger))


def _record_bot_initialize_failure(bot, error: Exception, *, catch: bool) -> None:
if configuration_manager.get_distribution(bot.config) is not enums.OctoBotDistribution.NODE:
return
edited_config = bot.get_edited_config(constants.CONFIG_KEY, dict_only=False)
journal_startup.record_node_startup_failed(
error,
startup_phase="bot_initialize",
force_exit=not catch,
config=edited_config,
)


async def start_bot(bot, logger, catch=False):
try:
# load tentacles details
Expand All @@ -354,6 +368,7 @@ async def start_bot(bot, logger, catch=False):

except Exception as e:
logger.exception(e)
_record_bot_initialize_failure(bot, e, catch=catch)
if not catch:
raise
stop_bot(bot)
Expand Down
4 changes: 0 additions & 4 deletions octobot/community/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@
get_current_octobots_stats,
can_read_metrics,
)
from octobot.community.activity_analysis.activity_metrics import (
ActivityMetrics,
)
from octobot.community.authentication import (
CommunityAuthentication,
)
Expand Down Expand Up @@ -123,7 +120,6 @@
"get_community_metrics",
"get_current_octobots_stats",
"can_read_metrics",
"ActivityMetrics",
"CommunityAuthentication",
"CommunityTentaclesPackage",
"CommunitySupports",
Expand Down
50 changes: 0 additions & 50 deletions octobot/community/activity_analysis/bot_id_resolver.py

This file was deleted.

8 changes: 5 additions & 3 deletions octobot/community/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import time
import threading
import typing
import octobot.community.activity_analysis.config_path_binding as config_path_binding
import octobot.community.activity_analysis.activity_metrics as activity_metrics
import octobot.community.config_path_binding as config_path_binding
import decimal

import octobot.constants as constants
Expand Down Expand Up @@ -150,6 +149,7 @@ def create(configuration: commons_configuration.Configuration, **kwargs):
)

def update(self, configuration: commons_configuration.Configuration):
self.config = configuration
self.configuration_storage.set_configuration(configuration)
self._wallet_backend = wallet_backend.WalletBackend(
self._get_wallet_sync_storage(), self.logger
Expand Down Expand Up @@ -583,7 +583,6 @@ def _clear_bot_scoped_config(self):
"and webhook url will be different on this bot."
)
self._save_bot_id("")
activity_metrics.ActivityMetrics.clear_activity_bot_id(self.config)
self.save_tradingview_email("")
# also reset mqtt id to force a new mqtt id creation
self._save_mqtt_device_uuid("")
Expand Down Expand Up @@ -723,6 +722,9 @@ def get_wallet(self, address: str) -> sync_chain.Wallet:
def get_wallet_by_user_id(self, user_id: str) -> sync_chain.Wallet:
return self._wallet_backend.get_wallet_by_user_id(user_id)

def has_wallet_for_user_id(self, user_id: str) -> bool:
return self._wallet_backend.has_wallet_for_user_id(user_id)

async def get_session_for_address(self, address: str) -> starfish_spaces.Session:
"""Build (and cache) a dk-namespace starfish_spaces Session for the given wallet.

Expand Down
2 changes: 1 addition & 1 deletion octobot/community/community_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async def get_community_metrics():


def can_read_metrics(config):
return constants.IS_CLOUD_ENV or config.get_metrics_enabled()
return False


def _format_top_elements(top_elements):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,30 @@
#
# You should have received a copy of the GNU General Public
# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.

"""Periodic community bot config/stats updates.

``CommunityBotStats`` is temporary until full migration to OctoBot v3 replaces this path.
"""

import asyncio

import octobot_commons.logging as logging
import octobot_commons.configuration as configuration
import octobot_commons.authentication as authentication
import octobot_commons.constants as common_constants

import octobot_trading.api as trading_api

import octobot.community.activity_analysis.bot_id_resolver as bot_id_resolver
import octobot.community.errors_upload.sentry_tracker as tracker
import octobot.constants as constants
import octobot.enums as enums


class ActivityMetrics:
class CommunityBotStats:

def __init__(self, octobot_api):
self.octobot_api = octobot_api
self.edited_config: configuration.Configuration = octobot_api.get_edited_config(dict_only=False)
self.enabled = constants.IS_CLOUD_ENV or self.edited_config.get_metrics_enabled()
self.logger = logging.get_logger(self.__class__.__name__)
self.keep_running = True

@staticmethod
def initialize_tracker(config: configuration.Configuration) -> None:
tracker.init_sentry_tracker(metrics_enabled=config.get_metrics_enabled())

@staticmethod
def clear_activity_bot_id(config: configuration.Configuration) -> None:
metrics_section = config.config.setdefault(common_constants.CONFIG_METRICS, {})
if isinstance(metrics_section, dict):
metrics_section[common_constants.CONFIG_METRICS_ACTIVITY_BOT_ID] = ""

def setup_activity_tracking(self, distribution: enums.OctoBotDistribution) -> None:
if not self.enabled:
return
resolution = bot_id_resolver.ensure_activity_bot_id(self.edited_config)
if tracker.activity_tracking_is_active():
tracker.update_tracker_bot_id(resolution.bot_id)
if distribution is enums.OctoBotDistribution.NODE and resolution.was_created:
tracker.track_usage_event(
"node_first_start",
distribution="node",
version=constants.LONG_VERSION,
)

@staticmethod
def report_child_octobot_first_start() -> None:
if not tracker.has_tracker_bot_id():
return
tracker.track_usage_event("child_octobot_first_start")
self.task_enabled = False

async def start_community_task(self):
if not self.enabled:
return
try:
while self.keep_running:
await asyncio.sleep(common_constants.TIMER_BETWEEN_METRICS_UPTIME_UPDATE)
Expand Down
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 General Public
# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.

import dataclasses
import hashlib
import os
Expand Down
Loading
Loading