import requests
from binance.client import Client
from datetime import datetime
import time
import threading
from dotenv import load_dotenv
import os
import sys
import itertools
import signal
# ANSI Colors
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
CYAN = "\033[96m"
RESET = "\033[0m"
# Load API keys from .env
load_dotenv()
BINANCE_API_KEY = os.getenv("BINANCE_API_KEY")
BINANCE_API_SECRET = os.getenv("BINANCE_API_SECRET")
# Initialize Binance Client
client = Client(BINANCE_API_KEY, BINANCE_API_SECRET)
# Configuration
CHECK_INTERVAL = 3600 # 1 hour between NVT checks
COINS_TO_MONITOR = ['BTC', 'ETH', 'XRP', 'ADA', 'DOGE']
NVT_UNDERVALUE_THRESHOLD = 35
PRICE_JUMP_THRESHOLDS = {'1m': 1.5, '5m': 2.0, '15m': 3.0}
# CoinGecko ID Mapping (Global Scope)
COINGECKO_IDS = {
'BTC': 'bitcoin',
'ETH': 'ethereum',
'XRP': 'ripple',
'ADA': 'cardano',
'DOGE': 'dogecoin'
}
# Activity monitoring
stop_event = threading.Event()
spinner_chars = itertools.cycle(['|', '/', '-', '\\'])
def spinner():
"""Show rotating spinner while processing."""
while not stop_event.is_set():
sys.stdout.write(f"{CYAN}\rActive {next(spinner_chars)} {RESET}")
sys.stdout.flush()
time.sleep(0.2)
sys.stdout.write('\r \r') # Clear spinner
def get_nvt_ratio(symbol):
"""Calculate NVT Ratio using CoinGecko/Blockchain.com data."""
try:
time.sleep(1) # Rate limit delay
coin_id = COINGECKO_IDS[symbol]
cg_data = requests.get(
f"https://api.coingecko.com/api/v3/coins/{coin_id}"
).json()
if 'market_data' not in cg_data:
print(f"{RED}CoinGecko data missing for {symbol}{RESET}")
return None
market_cap = cg_data['market_data']['market_cap']['usd']
# Get on-chain volume
onchain_volume = requests.get(
"https://api.blockchain.info/charts/estimated-transaction-volume-usd",
params={'timespan': '1days', 'format': 'json'}
).json()['values'][0]['y']
return market_cap / onchain_volume
except Exception as e:
print(f"{RED}NVT Error for {symbol}: {str(e)}{RESET}")
return None
def price_jump_monitor(symbol):
"""Monitor price jumps across intervals."""
print(f"{YELLOW}Starting price monitor for {symbol}{RESET}")
while not stop_event.is_set():
try:
for interval, threshold in PRICE_JUMP_THRESHOLDS.items():
candles = client.get_klines(
symbol=f"{symbol}USDT",
interval=interval,
limit=5
)
if len(candles) < 2:
continue
old_price = float(candles[0][4])
new_price = float(candles[-1][4])
change_pct = ((new_price - old_price)/old_price) * 100
if abs(change_pct) >= threshold:
direction = "↑" if change_pct > 0 else "↓"
print(
f"{RED}{datetime.now().strftime('%H:%M:%S')} - "
f"{symbol} {interval}: {direction}{abs(change_pct):.2f}% "
f"({old_price:.2f} → {new_price:.2f}){RESET}"
)
time.sleep(30)
except Exception as e:
print(f"{RED}Price error ({symbol}): {str(e)}{RESET}")
def nvt_screener():
"""Main screening/monitoring function."""
monitored_coins = set()
print(f"{CYAN}System started at {datetime.now().strftime('%Y-%m-%d %H:%M')}{RESET}")
while not stop_event.is_set():
print(f"\n{YELLOW}=== NVT Screening Cycle ==={RESET}")
for symbol in COINS_TO_MONITOR:
nvt = get_nvt_ratio(symbol)
if nvt is None:
continue
status_color = GREEN if nvt < NVT_UNDERVALUE_THRESHOLD else RED
print(f"{status_color}{symbol}: NVT {nvt:.1f}{RESET}")
if nvt < NVT_UNDERVALUE_THRESHOLD:
if symbol not in monitored_coins:
monitored_coins.add(symbol)
thread = threading.Thread(
target=price_jump_monitor,
args=(symbol,),
daemon=True
)
thread.start()
else:
if symbol in monitored_coins:
monitored_coins.remove(symbol)
time.sleep(CHECK_INTERVAL)
def signal_handler(sig, frame):
"""Handle Ctrl+C gracefully."""
print(f"\n{YELLOW}Shutting down...{RESET}")
stop_event.set()
sys.exit(0)
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
spinner_thread = threading.Thread(target=spinner, daemon=True)
spinner_thread.start()
nvt_screener()