Skip to content
Open
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
2 changes: 1 addition & 1 deletion Evaluator/Social/news_evaluator/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .news import TwitterNewsEvaluator
from .news import TwitterNewsEvaluator, CryptoNewsEvaluator
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"language": "en"
}
4 changes: 2 additions & 2 deletions Evaluator/Social/news_evaluator/metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1.2.0",
"origin_package": "OctoBot-Default-Tentacles",
"tentacles": ["TwitterNewsEvaluator"],
"tentacles-requirements": ["text_analysis", "twitter_service_feed"]
"tentacles": ["TwitterNewsEvaluator", "CryptoNewsEvaluator"],
"tentacles-requirements": ["text_analysis", "twitter_service_feed", "coindesk_service_feed"]
}
65 changes: 62 additions & 3 deletions Evaluator/Social/news_evaluator/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@

import octobot_commons.constants as commons_constants
import octobot_commons.enums as commons_enums

import octobot_commons.tentacles_management as tentacles_management
import octobot_services.constants as services_constants
import octobot_evaluators.evaluators as evaluators
from tentacles.Evaluator.Util.text_analysis import TextAnalysis
import tentacles.Services.Services_feeds as Services_feeds
import tentacles.Evaluator.Util as EvaluatorUtil


# disable inheritance to disable tentacle visibility. Disabled as starting from feb 9 2023, API is now paid only
Expand Down Expand Up @@ -177,4 +176,64 @@ def _get_config_elements(self, config_cryptocurrencies, key):
return {}

async def prepare(self):
self.sentiment_analyser = tentacles_management.get_single_deepest_child_class(TextAnalysis)()
self.sentiment_analyser = tentacles_management.get_single_deepest_child_class(EvaluatorUtil.TextAnalysis)()


NEWS_CONFIG_LANGUAGE = "language"

# Should use any feed available to fetch crypto news (coindesk, etc.)
class CryptoNewsEvaluator(evaluators.SocialEvaluator):
SERVICE_FEED_CLASS = Services_feeds.CoindeskServiceFeed

def __init__(self, tentacles_setup_config):
evaluators.SocialEvaluator.__init__(self, tentacles_setup_config)
self.stats_analyser = None
self.language = None

def init_user_inputs(self, inputs: dict) -> None:
self.language = self.UI.user_input(NEWS_CONFIG_LANGUAGE,
commons_enums.UserInputTypes.TEXT,
self.language, inputs,
title="Language to use to fetch crypto news.",
options=["en", "fr"])
self.feed_config = {
services_constants.CONFIG_COINDESK_TOPICS: [services_constants.COINDESK_TOPIC_NEWS],
services_constants.CONFIG_COINDESK_LANGUAGE: self.language
}

@classmethod
def get_is_cryptocurrencies_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency dependant else False
"""
return True

@classmethod
def get_is_cryptocurrency_name_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency name dependant else False
"""
return True

async def _feed_callback(self, data):
if self._is_interested_by_this_notification(data[services_constants.FEED_METADATA]):
latest_news = self.get_data_cache(self.get_current_exchange_time(), key=services_constants.COINDESK_TOPIC_NEWS)
if latest_news is not None and len(latest_news) > 0:
sentiment_sum = 0
news_count = 0
for news in latest_news:
sentiment = news.sentiment
sentiment_sum += 0 if sentiment is None else -1 if sentiment == "NEGATIVE" else 1 if sentiment == "POSITIVE" else 0
news_count += 1

if news_count > 0:
self.eval_note = sentiment_sum / news_count
await self.evaluation_completed(eval_time=self.get_current_exchange_time())
else:
self.debug(f"No news found")

def _is_interested_by_this_notification(self, notification_description):
return notification_description == services_constants.COINDESK_TOPIC_NEWS

async def prepare(self):
self.sentiment_analyser = tentacles_management.get_single_deepest_child_class(EvaluatorUtil.TextAnalysis)()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Analyzes overall crypto market sentiment through cryptocurrency news articles.

This evaluator interprets aggregated news signals (e.g., article headlines, content,
and sentiment classifications) to produce a normalized score
indicating bullish or bearish market sentiment based on recent news coverage.

Data source: ([CoinDesk Data API](https://developers.coindesk.com/documentation/data-api/news))
1 change: 1 addition & 0 deletions Evaluator/Social/sentiment_evaluator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .sentiment import FearAndGreedIndexEvaluator, SocialScoreEvaluator
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"trend_averages" : [40, 30, 20, 15, 10]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
6 changes: 6 additions & 0 deletions Evaluator/Social/sentiment_evaluator/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": "1.2.0",
"origin_package": "OctoBot-Default-Tentacles",
"tentacles": ["FearAndGreedIndexEvaluator", "SocialScoreEvaluator"],
"tentacles-requirements": ["alternative_me_service_feed", "lunarcrush_service_feed"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Analyzes overall crypto market sentiment through a Fear & Greed Index.

This evaluator interprets aggregated market signals (e.g., volatility, volume/momentum,
social media sentiment, dominance, and trends) to produce a normalized score
indicating prevailing fear or greed.

Data source: ([alternative.me](https://alternative.me/crypto/fear-and-greed-index/))
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Analyzes cryptocurrency-specific social sentiment through LunarCrush social metrics.

This evaluator interprets aggregated social signals (e.g., social volume, social engagement,
social dominance, and community sentiment) to produce a normalized score
indicating bullish or bearish social sentiment for a specific cryptocurrency.

Data source: ([LunarCrush](https://lunarcrush.com/faq/what-metrics-are-available-on-lunarcrush))
111 changes: 111 additions & 0 deletions Evaluator/Social/sentiment_evaluator/sentiment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Drakkar-Software OctoBot-Tentacles
# 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 octobot_commons.enums as commons_enums
import octobot_commons.tentacles_management as tentacles_management
import octobot_evaluators.evaluators as evaluators
import octobot_services.constants as services_constants
import tentacles.Evaluator.Util as EvaluatorUtil
import tentacles.Services.Services_feeds as Services_feeds

CONFIG_TREND_AVERAGES = "trend_averages"

class FearAndGreedIndexEvaluator(evaluators.SocialEvaluator):
SERVICE_FEED_CLASS = Services_feeds.AlternativeMeServiceFeed

def __init__(self, tentacles_setup_config):
evaluators.SocialEvaluator.__init__(self, tentacles_setup_config)
self.stats_analyser = None
self.history_data = None
self.feed_config = {
services_constants.CONFIG_ALTERNATIVE_ME_TOPICS: [services_constants.ALTERNATIVE_ME_TOPIC_FEAR_AND_GREED]
}
self.trend_averages = [40, 30, 20, 15, 10]

def init_user_inputs(self, inputs: dict) -> None:
self.trend_averages = self.UI.user_input(CONFIG_TREND_AVERAGES,
commons_enums.UserInputTypes.OBJECT_ARRAY,
self.trend_averages, inputs,
title="Averages to use to compute the trend evaluation.")

@classmethod
def get_is_cryptocurrencies_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency dependant else False
"""
return True

@classmethod
def get_is_cryptocurrency_name_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency name dependant else False
"""
return True

async def _feed_callback(self, data):
if self._is_interested_by_this_notification(data[services_constants.FEED_METADATA]):
fear_and_greed_history = self.get_data_cache(self.get_current_exchange_time(), key=services_constants.ALTERNATIVE_ME_TOPIC_FEAR_AND_GREED)
if fear_and_greed_history is not None and len(fear_and_greed_history) > 0:
fear_and_greed_history_values = [item.value for item in fear_and_greed_history]
self.eval_note = self.stats_analyser.get_trend(fear_and_greed_history_values, self.trend_averages)
await self.evaluation_completed(eval_time=self.get_current_exchange_time())

def _is_interested_by_this_notification(self, notification_description):
return notification_description == services_constants.ALTERNATIVE_ME_TOPIC_FEAR_AND_GREED

async def prepare(self):
self.stats_analyser = tentacles_management.get_single_deepest_child_class(EvaluatorUtil.TrendAnalysis)()

class SocialScoreEvaluator(evaluators.SocialEvaluator):
SERVICE_FEED_CLASS = Services_feeds.LunarCrushServiceFeed

def __init__(self, tentacles_setup_config):
evaluators.SocialEvaluator.__init__(self, tentacles_setup_config)
self.stats_analyser = None

def init_user_inputs(self, inputs: dict) -> None:
self.feed_config = {
services_constants.CONFIG_LUNARCRUSH_COINS: [self.cryptocurrency]
}

@classmethod
def get_is_cryptocurrencies_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency dependant else False
"""
return False

@classmethod
def get_is_cryptocurrency_name_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency name dependant else False
"""
return False

async def _feed_callback(self, data):
if self._is_interested_by_this_notification(data[services_constants.FEED_METADATA]):
coin, _ = data[services_constants.FEED_METADATA].split(";")
coin_data = self.get_data_cache(self.get_current_exchange_time(), key=f"{coin};{services_constants.LUNARCRUSH_COIN_METRICS}")
if coin_data is not None and len(coin_data) > 0:
self.eval_note = coin_data[-1].sentiment
await self.evaluation_completed(cryptocurrency=self.cryptocurrency, eval_time=self.get_current_exchange_time())

def _is_interested_by_this_notification(self, notification_description):
try:
coin, topic = notification_description.split(";")
return coin == self.cryptocurrency and topic == services_constants.LUNARCRUSH_COIN_METRICS
except KeyError:
pass
return False
2 changes: 1 addition & 1 deletion Evaluator/Social/trends_evaluator/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .trends import GoogleTrendsEvaluator
from .trends import GoogleTrendsEvaluator, MarketCapEvaluator
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"trend_averages" : [40, 30, 20, 15, 10]
}

4 changes: 2 additions & 2 deletions Evaluator/Social/trends_evaluator/metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1.2.0",
"origin_package": "OctoBot-Default-Tentacles",
"tentacles": ["GoogleTrendsEvaluator"],
"tentacles-requirements": ["statistics_analysis", "google_service_feed"]
"tentacles": ["GoogleTrendsEvaluator", "MarketCapEvaluator"],
"tentacles-requirements": ["statistics_analysis", "google_service_feed", "coindesk_service_feed"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Analyzes overall crypto market trends through total market capitalization data.

This evaluator interprets aggregated market cap signals (e.g., historical market cap values,
trend changes, and long-term averages) to produce a normalized score
indicating bullish or bearish market trends based on capitalization movements.

Data source: ([CoinDesk Data API](https://developers.coindesk.com/documentation/data-api/overview_v1_latest_marketcap_all_tick))
48 changes: 47 additions & 1 deletion Evaluator/Social/trends_evaluator/trends.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import tentacles.Evaluator.Util as EvaluatorUtil
import tentacles.Services.Services_feeds as Services_feeds


class GoogleTrendsEvaluator(evaluators.SocialEvaluator):
SERVICE_FEED_CLASS = Services_feeds.GoogleServiceFeed

Expand Down Expand Up @@ -81,3 +80,50 @@ def _build_trend_topics(self):

async def prepare(self):
self.stats_analyser = tentacles_management.get_single_deepest_child_class(EvaluatorUtil.StatisticAnalysis)()

CONFIG_TREND_AVERAGES = "trend_averages"

class MarketCapEvaluator(evaluators.SocialEvaluator):
SERVICE_FEED_CLASS = Services_feeds.CoindeskServiceFeed

def __init__(self, tentacles_setup_config):
evaluators.SocialEvaluator.__init__(self, tentacles_setup_config)
self.stats_analyser = None
self.feed_config = {
services_constants.CONFIG_COINDESK_TOPICS: [services_constants.COINDESK_TOPIC_MARKETCAP]
}
self.trend_averages = [40, 30, 20, 15, 10]

def init_user_inputs(self, inputs: dict) -> None:
self.trend_averages = self.UI.user_input(CONFIG_TREND_AVERAGES,
commons_enums.UserInputTypes.OBJECT_ARRAY,
self.trend_averages, inputs,
title="Averages to use to compute the trend evaluation.")

@classmethod
def get_is_cryptocurrencies_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency dependant else False
"""
return True

@classmethod
def get_is_cryptocurrency_name_wildcard(cls) -> bool:
"""
:return: True if the evaluator is not cryptocurrency name dependant else False
"""
return True

async def _feed_callback(self, data):
if self._is_interested_by_this_notification(data[services_constants.FEED_METADATA]):
marketcap_data = self.get_data_cache(self.get_current_exchange_time(), key=services_constants.COINDESK_TOPIC_MARKETCAP)
if marketcap_data is not None and len(marketcap_data) > 0:
marketcap_history = [item.close for item in marketcap_data]
self.eval_note = self.stats_analyser.get_trend(marketcap_history, self.trend_averages)
await self.evaluation_completed(eval_time=self.get_current_exchange_time())

def _is_interested_by_this_notification(self, notification_description):
return notification_description == services_constants.COINDESK_TOPIC_MARKETCAP

async def prepare(self):
self.stats_analyser = tentacles_management.get_single_deepest_child_class(EvaluatorUtil.TrendAnalysis)()
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ def init_user_inputs(self, inputs: dict) -> None:
default_config[self.BACKGROUND_SOCIAL_EVALUATORS],
inputs, other_schema_values={"minItems": 0, "uniqueItems": True},
options=["RedditForumEvaluator", "TwitterNewsEvaluator",
"TelegramSignalEvaluator", "GoogleTrendsEvaluator"],
"TelegramSignalEvaluator", "GoogleTrendsEvaluator",
"FearAndGreedIndexEvaluator", "SocialScoreEvaluator",
"CryptoNewsEvaluator", "MarketCapEvaluator"],
title="Social evaluator to consider as background evaluators: they won't trigger technical "
"evaluators re-evaluation when updated. Avoiding unnecessary updates increases "
"performances.")
Expand Down
1 change: 1 addition & 0 deletions Services/Services_bases/alternative_me_service/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .alternative_me import AlternativeMeService
42 changes: 42 additions & 0 deletions Services/Services_bases/alternative_me_service/alternative_me.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Drakkar-Software OctoBot-Tentacles
# 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 octobot_services.constants as services_constants
import octobot_services.services as services


class AlternativeMeService(services.AbstractService):
@staticmethod
def is_setup_correctly(config):
return True

@staticmethod
def get_is_enabled(config):
return True

def has_required_configuration(self):
return True

def get_endpoint(self) -> None:
return None

def get_type(self) -> None:
return services_constants.CONFIG_ALTERNATIVE_ME

async def prepare(self) -> None:
pass

def get_successful_startup_message(self):
return "", True
Loading
Loading