-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsentiment_analyzer.py
More file actions
587 lines (474 loc) · 27.7 KB
/
sentiment_analyzer.py
File metadata and controls
587 lines (474 loc) · 27.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
"""
Moduł analizy sentymentu rynkowego
Zawiera funkcje do pomiaru sentymentu rynku kryptowalut
"""
import logging
import requests
import time
import random
import numpy as np
from datetime import datetime, timedelta
logger = logging.getLogger("sentiment_analyzer")
class SentimentAnalyzer:
"""
Klasa do analizy sentymentu rynkowego kryptowalut.
Zawiera metody do pobierania różnych wskaźników sentymentu:
- Fear & Greed Index
- Funding rate
- Long/Short ratio
- Przepływy giełdowe
"""
def __init__(self, api_keys=None):
"""
Inicjalizacja analizatora sentymentu.
Args:
api_keys (dict): Słownik z kluczami API do różnych serwisów
"""
# Ustawienie domyślnych kluczy API jeśli nie są podane
default_api_keys = {
'glassnode': '',
'coingecko': 'CG-qiF2zUuBB98vE14AvzSGJjwn', # Domyślny klucz CoinGecko
'cryptoquant': ''
}
# Jeśli podano klucze, nadpisz domyślne
if api_keys:
default_api_keys.update(api_keys)
self.api_keys = default_api_keys
# Cache dla danych, aby uniknąć zbyt częstych zapytań API
self.cache = {}
self.cache_expiry = {}
self.default_cache_expiry = 300 # 5 minut domyślnie
def get_fear_greed_index(self):
"""
Pobiera Fear & Greed Index dla rynku krypto.
Returns:
float: Znormalizowana wartość indeksu w zakresie [-1, 1]
gdzie -1 to skrajny strach, 1 to skrajna chciwość
"""
cache_key = "fear_greed_index"
if cache_key in self.cache and datetime.now() < self.cache_expiry.get(cache_key, datetime.min):
return self.cache[cache_key]
try:
url = "https://api.alternative.me/fng/"
response = requests.get(url, timeout=10)
if response.status_code == 200:
data = response.json()
if 'data' in data and len(data['data']) > 0:
fear_greed_value = int(data['data'][0]['value'])
# Normalizacja do [-1, 1]
normalized_fear_greed = (fear_greed_value - 50) / 50
logger.info(f"Fear & Greed Index: {fear_greed_value} -> {normalized_fear_greed:.2f}")
# Zapisanie do cache
self.cache[cache_key] = normalized_fear_greed
self.cache_expiry[cache_key] = datetime.now() + timedelta(minutes=60) # Ważny przez godzinę
return normalized_fear_greed
else:
logger.warning(f"Nieprawidłowa struktura danych z API Fear & Greed: {data}")
else:
logger.warning(f"Błąd HTTP podczas pobierania Fear & Greed Index: {response.status_code}")
except Exception as e:
logger.warning(f"Błąd podczas pobierania Fear & Greed Index (szczegóły): {e}")
# W przypadku błędu próbujemy pobrać dane z CoinGecko
try:
# Pobranie aktualnej ceny i 24h change z CoinGecko
coingecko_api_key = self.api_keys.get('coingecko')
params = {
'ids': 'bitcoin',
'vs_currencies': 'usd',
'include_24hr_change': 'true'
}
# Dodaj klucz demo API jeśli jest dostępny
if coingecko_api_key:
params['x_cg_demo_api_key'] = coingecko_api_key
cg_url = "https://api.coingecko.com/api/v3/simple/price"
response = requests.get(cg_url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
if 'bitcoin' in data and 'usd_24h_change' in data['bitcoin']:
change_24h = data['bitcoin']['usd_24h_change']
# Normalizacja 24h change do [-1, 1]
# Zakładamy, że ±20% to ekstremalne przypadki
normalized_change = max(min(change_24h / 20, 1.0), -1.0)
logger.info(f"BTC 24h change: {change_24h:.2f}% -> {normalized_change:.2f}")
# Zapisanie do cache
self.cache[cache_key] = normalized_change
self.cache_expiry[cache_key] = datetime.now() + timedelta(minutes=30) # Ważny przez 30 minut
return normalized_change
else:
logger.warning(f"Nieprawidłowa struktura danych z CoinGecko: {data}")
else:
logger.warning(f"Błąd HTTP podczas pobierania danych z CoinGecko: {response.status_code}")
except Exception as e:
logger.warning(f"Błąd podczas pobierania danych z CoinGecko (szczegóły): {e}")
# Jeśli nie udało się pobrać żadnych danych, zwracamy losową wartość
random_sentiment = random.uniform(-0.3, 0.3)
logger.warning(f"Używanie losowego sentymentu: {random_sentiment:.2f}")
return random_sentiment
def get_funding_rate(self, exchange="binance", symbol="BTCUSDT"):
"""
Pobiera funding rate z giełdy futures.
Args:
exchange (str): Nazwa giełdy (binance, bybit, ftx)
symbol (str): Symbol kontraktu futures
Returns:
float: Znormalizowana wartość funding rate w zakresie [-1, 1]
gdzie wartości ujemne oznaczają presję na spadki,
a dodatnie presję na wzrosty
"""
cache_key = f"funding_rate_{exchange}_{symbol}"
if cache_key in self.cache and datetime.now() < self.cache_expiry.get(cache_key, datetime.min):
return self.cache[cache_key]
try:
funding_rate = 0
if exchange.lower() == "binance":
url = f"https://fapi.binance.com/fapi/v1/premiumIndex?symbol={symbol}"
response = requests.get(url, timeout=10)
if response.status_code == 200:
data = response.json()
if 'lastFundingRate' in data:
funding_rate = float(data['lastFundingRate'])
else:
logger.warning(f"Brak pola lastFundingRate w odpowiedzi Binance: {data}")
else:
logger.warning(f"Błąd HTTP podczas pobierania funding rate z Binance: {response.status_code}")
elif exchange.lower() == "bybit":
url = f"https://api.bybit.com/v2/public/tickers?symbol={symbol}"
response = requests.get(url, timeout=10)
if response.status_code == 200:
data = response.json()
if 'result' in data and len(data['result']) > 0:
funding_rate = float(data['result'][0]['funding_rate'])
else:
logger.warning(f"Nieprawidłowa struktura danych z Bybit: {data}")
else:
logger.warning(f"Błąd HTTP podczas pobierania funding rate z Bybit: {response.status_code}")
# Normalizacja - typowe funding rate są w zakresie -0.1% do 0.1%
# Wartości powyżej 0.05% lub poniżej -0.05% są już dość ekstremalne
normalized_funding_rate = np.clip(funding_rate * 20, -1.0, 1.0)
logger.info(f"Funding Rate ({exchange}, {symbol}): {funding_rate:.6f} -> {normalized_funding_rate:.2f}")
# Zapisanie do cache
self.cache[cache_key] = normalized_funding_rate
self.cache_expiry[cache_key] = datetime.now() + timedelta(minutes=60) # Ważny przez godzinę
return normalized_funding_rate
except Exception as e:
logger.warning(f"Błąd podczas pobierania funding rate (szczegóły): {e}")
return 0.0 # Neutralna wartość w przypadku błędu
def get_long_short_ratio(self, exchange="binance", symbol="BTCUSDT"):
"""
Pobiera stosunek długich do krótkich pozycji (long/short ratio).
Args:
exchange (str): Nazwa giełdy
symbol (str): Symbol kontraktu futures
Returns:
float: Znormalizowana wartość long/short ratio w zakresie [0, 1]
gdzie 0 oznacza, że wszyscy są na krótko,
1 oznacza, że wszyscy są na długo,
a 0.5 to równowaga
"""
cache_key = f"long_short_ratio_{exchange}_{symbol}"
if cache_key in self.cache and datetime.now() < self.cache_expiry.get(cache_key, datetime.min):
return self.cache[cache_key]
try:
ratio = 0.5 # Domyślnie zakładamy równowagę
if exchange.lower() == "binance":
# Binance udostępnia dane bezpośrednio
url = f"https://fapi.binance.com/futures/data/globalLongShortAccountRatio?symbol={symbol}&period=5m"
response = requests.get(url, timeout=10)
if response.status_code == 200:
data = response.json()
if data and len(data) > 0:
ratio = float(data[0]['longShortRatio'])
# Konwersja na wartość z zakresu [0, 1]
ratio = ratio / (1 + ratio)
else:
logger.warning(f"Pusta lub nieprawidłowa odpowiedź z Binance dla Long/Short Ratio: {data}")
else:
logger.warning(f"Błąd HTTP podczas pobierania Long/Short Ratio z Binance: {response.status_code}")
elif exchange.lower() == "bybit":
url = f"https://api.bybit.com/v2/public/account-ratio?symbol={symbol}&period=5min"
response = requests.get(url, timeout=10)
if response.status_code == 200:
data = response.json()
if 'result' in data and len(data['result']) > 0:
buy_ratio = float(data['result'][0]['buy_ratio'])
ratio = buy_ratio / 100 # Bybit podaje % kupujących
else:
logger.warning(f"Nieprawidłowa struktura danych z Bybit dla Long/Short Ratio: {data}")
else:
logger.warning(f"Błąd HTTP podczas pobierania Long/Short Ratio z Bybit: {response.status_code}")
logger.info(f"Long/Short Ratio ({exchange}, {symbol}): {ratio:.2f}")
# Zapisanie do cache
self.cache[cache_key] = ratio
self.cache_expiry[cache_key] = datetime.now() + timedelta(minutes=15) # Ważny przez 15 minut
return ratio
except Exception as e:
logger.warning(f"Błąd podczas pobierania long/short ratio (szczegóły): {e}")
return 0.5 # Neutralna wartość w przypadku błędu
def get_exchange_flows_coingecko(self, timeframe="1d"):
"""
Alternatywna metoda szacowania przepływów giełdowych przy użyciu CoinGecko API.
Wykorzystuje zmiany w wolumenie i rezerwach giełd jako proxy dla przepływów.
Args:
timeframe (str): Przedział czasowy ("1h", "1d", "7d")
Returns:
float: Znormalizowana wartość przepływów w zakresie [-1, 1]
"""
cache_key = f"exchange_flows_coingecko_{timeframe}"
if cache_key in self.cache and datetime.now() < self.cache_expiry.get(cache_key, datetime.min):
return self.cache[cache_key]
# Pobierz klucz API CoinGecko
coingecko_api_key = self.api_keys.get('coingecko')
base_url = "https://api.coingecko.com/api/v3"
logger.info("Próba użycia CoinGecko API do analizy przepływów giełdowych")
try:
# Pobranie danych o wolumenie dla głównych giełd
exchanges = ["binance", "coinbase", "kucoin", "huobi", "kraken"]
total_volume_change = 0
valid_exchanges = 0
for exchange in exchanges:
try:
# Przygotuj parametry z kluczem API
params = {}
if coingecko_api_key:
params['x_cg_demo_api_key'] = coingecko_api_key
url = f"{base_url}/exchanges/{exchange}"
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
# Pobierz zmianę wolumenu jako proxy dla przepływów
volume_change = data.get('trade_volume_24h_btc_percentage_change', 0)
if volume_change:
total_volume_change += volume_change
valid_exchanges += 1
logger.debug(f"Giełda {exchange}: zmiana wolumenu {volume_change:.2f}%")
elif response.status_code == 429:
logger.warning(f"Przekroczony limit zapytań CoinGecko dla {exchange}")
# Dodaj opóźnienie, aby nie przekraczać limitów
time.sleep(5)
else:
logger.debug(f"Błąd HTTP podczas pobierania danych dla giełdy {exchange}: {response.status_code}")
except Exception as e:
logger.debug(f"Błąd podczas pobierania danych dla giełdy {exchange} (szczegóły): {e}")
# Jeśli nie udało się pobrać danych dla żadnej giełdy
if valid_exchanges == 0:
logger.warning("Nie udało się pobrać danych o wolumenie dla żadnej giełdy z CoinGecko")
# Oblicz średnią zmianę wolumenu
avg_volume_change = total_volume_change / valid_exchanges if valid_exchanges > 0 else 0
# Sprawdź również całkowity wolumen BTC dla lepszego obrazu
try:
# Przygotuj parametry z kluczem API
params = {}
if coingecko_api_key:
params['x_cg_demo_api_key'] = coingecko_api_key
url = f"{base_url}/coins/bitcoin"
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
market_data = data.get('market_data', {})
# Zmiana wolumenu BTC może być wskaźnikiem przepływów
volume_change_btc = market_data.get('volume_change_percentage_24h', 0)
# Pobierz również dane o aktywności on-chain jeśli dostępne
total_volume = market_data.get('total_volume', {}).get('usd', 0)
market_cap = market_data.get('market_cap', {}).get('usd', 0)
# Wskaźnik zmienności (volume/market_cap) może wskazywać na przepływy
volatility_indicator = 0
if market_cap > 0:
volatility_indicator = total_volume / market_cap
logger.debug(f"Wskaźnik zmienności BTC (volume/market_cap): {volatility_indicator:.4f}")
# Połącz oba wskaźniki
combined_indicator = (avg_volume_change * 0.6 + volume_change_btc * 0.4) / 100
# Interpretacja: Nagły wzrost wolumenu może wskazywać na przepływy DO giełd (negatywny sygnał)
# Ujemny wskaźnik oznacza potencjalne wypływy z giełd (pozytywny sygnał)
normalized_flow = -np.clip(combined_indicator, -1.0, 1.0)
logger.info(f"Exchange Net Flow (CoinGecko): Wolumen {avg_volume_change:.2f}%, BTC {volume_change_btc:.2f}% -> {normalized_flow:.2f}")
# Zapisanie do cache
self.cache[cache_key] = normalized_flow
self.cache_expiry[cache_key] = datetime.now() + timedelta(minutes=30)
return normalized_flow
elif response.status_code == 429:
logger.warning(f"Przekroczony limit zapytań CoinGecko dla danych BTC")
time.sleep(5)
else:
logger.warning(f"Błąd HTTP podczas pobierania danych o BTC z CoinGecko: {response.status_code}, {response.text}")
except Exception as e:
logger.warning(f"Błąd podczas pobierania danych o BTC z CoinGecko (szczegóły): {e}")
return 0.0 # Neutralna wartość w przypadku braku danych
except Exception as e:
logger.warning(f"Błąd podczas szacowania przepływów giełdowych z CoinGecko (szczegóły): {e}")
return 0.0 # Neutralna wartość w przypadku błędu
def get_exchange_flows_webscraping(self, timeframe="1d"):
"""
Alternatywna metoda szacowania przepływów giełdowych przy użyciu web scrapingu.
Ściąga dane z publicznie dostępnych dashboardów lub alertów.
Args:
timeframe (str): Przedział czasowy ("1h", "1d", "7d")
Returns:
float: Znormalizowana wartość przepływów w zakresie [-1, 1]
"""
cache_key = f"exchange_flows_scraping_{timeframe}"
if cache_key in self.cache and datetime.now() < self.cache_expiry.get(cache_key, datetime.min):
return self.cache[cache_key]
try:
# Scraping danych z Whale Alert
import re
from bs4 import BeautifulSoup
# Określenie parametrów scrappingu
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
# Pobieranie danych z Whale Alert
url = "https://whale-alert.io/"
response = requests.get(url, headers=headers, timeout=15)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
# Analiza ostatnich alertów
alerts = []
alert_elements = soup.select('.transaction-item')
logger.debug(f"Znaleziono {len(alert_elements)} alertów do analizy")
for alert in alert_elements:
try:
# Wyciągnij tekst alertu
alert_text = alert.get_text()
# Sprawdź czy to alert o BTC
if 'BTC' in alert_text:
# Sprawdź czy to przepływ do/z giełdy
is_to_exchange = 'to exchange' in alert_text.lower() or 'to binance' in alert_text.lower() or 'to coinbase' in alert_text.lower()
is_from_exchange = 'from exchange' in alert_text.lower() or 'from binance' in alert_text.lower() or 'from coinbase' in alert_text.lower()
# Wyciągnij kwotę
amount_match = re.search(r'([\d,]+) BTC', alert_text)
if amount_match:
amount = float(amount_match.group(1).replace(',', ''))
# Dodaj do listy alertów
alerts.append({
'amount': amount,
'to_exchange': is_to_exchange,
'from_exchange': is_from_exchange,
'time': datetime.now() # Przybliżony czas, dokładny czas mógłby być wyciągnięty z alertu
})
except Exception as e:
logger.debug(f"Błąd podczas analizy alertu (szczegóły): {e}")
# Analiza przepływów netto
if alerts:
inflow = sum(alert['amount'] for alert in alerts if alert['to_exchange'])
outflow = sum(alert['amount'] for alert in alerts if alert['from_exchange'])
# Oblicz przepływ netto
net_flow = outflow - inflow # Dodatni oznacza więcej wypływów (pozytywny sygnał)
# Normalizacja - typowe wartości są w zakresie -1000 BTC do 1000 BTC dla alertów z ostatnich 24h
normalized_flow = np.clip(net_flow / 1000, -1.0, 1.0)
logger.info(f"Exchange Net Flow (Web Scraping): {net_flow:.2f} BTC -> {normalized_flow:.2f}")
# Zapisanie do cache
self.cache[cache_key] = normalized_flow
self.cache_expiry[cache_key] = datetime.now() + timedelta(minutes=45)
return normalized_flow
else:
logger.warning("Nie znaleziono żadnych alertów związanych z przepływem BTC")
else:
logger.warning(f"Błąd HTTP podczas pobierania danych z Whale Alert: {response.status_code}")
except ImportError as e:
logger.warning(f"Brak wymaganych bibliotek do web scrapingu: {e}")
except Exception as e:
logger.warning(f"Błąd podczas scrapowania danych o przepływach giełdowych (szczegóły): {e}")
return 0.0 # Neutralna wartość w przypadku błędu
def get_exchange_flows(self, timeframe="1h"):
"""
Pobiera informacje o przepływach BTC do/z giełd.
Wykorzystuje alternatywne źródła danych, jeśli Glassnode API nie jest dostępne.
Args:
timeframe (str): Przedział czasowy ("1h", "1d", "7d")
Returns:
float: Znormalizowana wartość przepływów w zakresie [-1, 1]
gdzie -1 oznacza duże przepływy DO giełd (negatywny sygnał),
a 1 oznacza duże przepływy Z giełd (pozytywny sygnał)
"""
cache_key = f"exchange_flows_{timeframe}"
if cache_key in self.cache and datetime.now() < self.cache_expiry.get(cache_key, datetime.min):
return self.cache[cache_key]
# Próba użycia Glassnode API, jeśli klucz jest dostępny
glassnode_api_key = self.api_keys.get('glassnode')
if glassnode_api_key:
try:
# Pobranie danych z Glassnode
url = f"https://api.glassnode.com/v1/metrics/transactions/transfers_volume_exchanges_net"
params = {
'api_key': glassnode_api_key,
'a': 'BTC',
'i': timeframe
}
response = requests.get(url, params=params, timeout=15)
if response.status_code == 200:
data = response.json()
if data and len(data) > 0:
# Ostatnia wartość w danych
net_flow = data[-1]['v']
# Normalizacja - typowe wartości są w zakresie -5000 BTC do 5000 BTC
# dla dziennego timeframe
normalized_flow = np.clip(net_flow / 5000, -1.0, 1.0)
logger.info(f"Exchange Net Flow (Glassnode, {timeframe}): {net_flow:.2f} BTC -> {normalized_flow:.2f}")
# Zapisanie do cache
self.cache[cache_key] = normalized_flow
self.cache_expiry[cache_key] = datetime.now() + timedelta(hours=1)
return normalized_flow
else:
logger.warning(f"Pusta lub nieprawidłowa odpowiedź z Glassnode: {data}")
else:
logger.warning(f"Błąd HTTP podczas pobierania danych z Glassnode: {response.status_code}")
except Exception as e:
logger.warning(f"Błąd podczas pobierania danych z Glassnode (szczegóły): {e}")
else:
logger.info("Brak klucza API Glassnode, użycie alternatywnych źródeł danych...")
# Alternatywa 1: CoinGecko API
try:
coingecko_flow = self.get_exchange_flows_coingecko(timeframe)
if coingecko_flow != 0.0:
return coingecko_flow
except Exception as e:
logger.warning(f"Błąd podczas pobierania danych z CoinGecko (szczegóły): {e}")
# Alternatywa 2: Web Scraping (tylko jeśli CoinGecko zawiodło)
try:
import bs4
scraping_flow = self.get_exchange_flows_webscraping(timeframe)
if scraping_flow != 0.0:
return scraping_flow
else:
logger.warning("Web scraping nie dostarczył danych o przepływach giełdowych")
except ImportError:
logger.warning("Biblioteka BeautifulSoup nie jest zainstalowana, web scraping niedostępny")
except Exception as e:
logger.warning(f"Błąd podczas pobierania danych przez web scraping (szczegóły): {e}")
# Jeśli wszystkie metody zawiodły, zwróć neutralną wartość
logger.warning("Wszystkie metody pobierania danych o przepływach giełdowych zawiodły")
return 0.0
def get_integrated_sentiment(self):
"""
Oblicza zintegrowany wskaźnik sentymentu na podstawie wszystkich dostępnych danych.
Returns:
float: Zintegrowany wskaźnik sentymentu w zakresie [-1, 1]
"""
# Pobieramy wszystkie wskaźniki
fear_greed = self.get_fear_greed_index()
funding_rate = self.get_funding_rate()
long_short_ratio = self.get_long_short_ratio()
exchange_flows = self.get_exchange_flows()
# Wagi poszczególnych wskaźników
weights = {
'fear_greed': 0.4,
'funding_rate': 0.3,
'long_short_ratio': 0.2,
'exchange_flows': 0.1
}
# Przeciwność sentymentu dla funding rate (wysoki funding rate jest zwykle sygnałem przeciwnym)
inverse_funding_rate = -funding_rate
# Przeciwność long/short ratio (odchylenie od równowagi 0.5)
inverse_long_short = 2 * (0.5 - long_short_ratio)
# Obliczenie ważonego sentymentu
integrated_sentiment = (
fear_greed * weights['fear_greed'] +
inverse_funding_rate * weights['funding_rate'] +
inverse_long_short * weights['long_short_ratio'] +
exchange_flows * weights['exchange_flows']
)
# Ograniczenie do zakresu [-1, 1]
integrated_sentiment = np.clip(integrated_sentiment, -1.0, 1.0)
logger.info(f"Zintegrowany sentyment: {integrated_sentiment:.2f} [Fear&Greed: {fear_greed:.2f}, "
f"Funding: {funding_rate:.2f}, L/S: {long_short_ratio:.2f}, Flows: {exchange_flows:.2f}]")
return integrated_sentiment