This Python script provides a real-time visual display of Bitcoin (BTC) price data using the `curses` library to create a terminal-based interface. Here’s a summary of its functionality:
1. **Price Fetching**: It fetches the latest BTC/USDT price from the Binance API.
2. **Indicators Calculation**:
- **RSI (Relative Strength Index)**: Calculates and displays the RSI to gauge the price momentum.
- **Bollinger Bands**: Computes and displays Bollinger Bands, which include the upper, middle, and lower bands, to analyze price volatility.
3. **Visualization**:
- **Price Plot**: Plots the price changes as a line graph.
- **Bollinger Bands**: Draws the Bollinger Bands on the graph.
- **RSI**: Displays the RSI value at the bottom of the screen.
4. **Display Updates**: Continuously updates the graph and indicators every second and shows the current, maximum, and minimum prices at the top-right corner.
5. **Exit**: Allows the user to exit the program by pressing the 'q' key.
The script runs in a terminal environment and uses `curses` to handle real-time screen updates and user interactions.
import requests
import curses
import time
import numpy as np
# Function to get the latest BTC/USDT price from Binance
def get_btcusdt_price():
url = 'https://api.binance.com/api/v3/ticker/price'
params = {'symbol': 'BTCUSDT'}
response = requests.get(url, params=params)
data = response.json()
return float(data['price'])
# Function to calculate RSI
def calculate_rsi(prices, period=14):
if len(prices) < period:
return 50 # Default RSI value when there's not enough data
deltas = np.diff(prices)
seed = deltas[:period]
up = seed[seed >= 0].sum() / period
down = -seed[seed < 0].sum() / period
rs = up / down if down != 0 else 0
rsi = 100 - (100 / (1 + rs))
for delta in deltas[period:]:
if delta > 0:
upval = delta
downval = 0
else:
upval = 0
downval = -delta
up = (up * (period - 1) + upval) / period
down = (down * (period - 1) + downval) / period
rs = up / down if down != 0 else 0
rsi = 100 - (100 / (1 + rs))
return rsi
# Function to calculate Bollinger Bands
def calculate_bollinger_bands(prices, period=20):
if len(prices) < period:
return None, None, None # Not enough data
sma = np.mean(prices[-period:])
std = np.std(prices[-period:])
upper_band = sma + (2 * std)
middle_band = sma
lower_band = sma - (2 * std)
return upper_band, middle_band, lower_band
# Function to draw the RSI as a bar graph
def draw_rsi(stdscr, rsi, max_y, max_x):
rsi_str = f'RSI: {rsi:.2f}'
# Ensure the RSI display stays within screen bounds
if max_x > len(rsi_str) + 2:
stdscr.addstr(max_y - 1, max_x - len(rsi_str) - 2, rsi_str, curses.A_BOLD | curses.color_pair(5))
# Function to draw fixed Bollinger Bands with values
def draw_bollinger_bands(stdscr, prices, upper_band, middle_band, lower_band, max_y, min_price, price_range, max_x):
if upper_band is not None and middle_band is not None and lower_band is not None:
y_upper = max_y - 1 - int((upper_band - min_price) / price_range * (max_y - 1))
y_middle = max_y - 1 - int((middle_band - min_price) / price_range * (max_y - 1))
y_lower = max_y - 1 - int((lower_band - min_price) / price_range * (max_y - 1))
# Draw upper band
if 0 <= y_upper < max_y:
upper_str = f'U: {upper_band:.2f}'
try:
stdscr.addstr(y_upper, 0, upper_str, curses.A_BOLD | curses.color_pair(1))
upper_line = '#' * (max_x - len(upper_str) - 1)
stdscr.addstr(y_upper, len(upper_str), upper_line, curses.color_pair(1))
except curses.error:
pass
# Draw middle band
if 0 <= y_middle < max_y:
middle_str = f'M: {middle_band:.2f}'
try:
stdscr.addstr(y_middle, 0, middle_str, curses.A_BOLD | curses.color_pair(2))
middle_line = '#' * (max_x - len(middle_str) - 1)
stdscr.addstr(y_middle, len(middle_str), middle_line, curses.color_pair(2))
except curses.error:
pass
# Draw lower band
if 0 <= y_lower < max_y:
lower_str = f'L: {lower_band:.2f}'
try:
stdscr.addstr(y_lower, 0, lower_str, curses.A_BOLD | curses.color_pair(3))
lower_line = '#' * (max_x - len(lower_str) - 1)
stdscr.addstr(y_lower, len(lower_str), lower_line, curses.color_pair(3))
except curses.error:
pass
# Function to initialize the screen and start drawing the graph
def draw_graph(stdscr):
curses.curs_set(0)
stdscr.nodelay(1)
stdscr.timeout(1000)
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(5, curses.COLOR_YELLOW, curses.COLOR_BLACK)
max_y, max_x = stdscr.getmaxyx()
prices = []
max_price = 0
min_price = float('inf')
while True:
stdscr.clear()
price = get_btcusdt_price()
prices.append(price)
if len(prices) > max_x - 2:
prices.pop(0)
max_price = max(prices)
min_price = min(prices)
price_range = max_price - min_price
if price_range == 0:
price_range = 1 # Prevent division by zero
upper_band, middle_band, lower_band = calculate_bollinger_bands(prices)
rsi = calculate_rsi(prices)
# Draw Bollinger Bands
draw_bollinger_bands(stdscr, prices, upper_band, middle_band, lower_band, max_y, min_price, price_range, max_x)
# Draw RSI
draw_rsi(stdscr, rsi, max_y, max_x)
# Draw vertical lines for graph
for i in range(min(max_x - 1, len(prices) - 1)):
y1 = max_y - 1 - int((prices[i] - min_price) / price_range * (max_y - 1))
y2 = max_y - 1 - int((prices[i + 1] - min_price) / price_range * (max_y - 1))
if 0 <= y1 < max_y and 0 <= y2 < max_y:
if y1 == y2:
stdscr.addch(y1, i, '-')
elif y1 < y2:
for y in range(y1, y2 + 1):
stdscr.addch(y, i, '|')
else:
for y in range(y2, y1 + 1):
stdscr.addch(y, i, '|')
# Draw current price, max, and min prices at the top right corner
current_price_str = f'Price: {price:.2f}'
max_price_str = f'Max: {max_price:.2f}'
min_price_str = f'Min: {min_price:.2f}'
try:
stdscr.addstr(1, max_x - len(current_price_str) - 2, current_price_str, curses.A_BOLD | curses.color_pair(4))
stdscr.addstr(2, max_x - len(max_price_str) - 2, max_price_str, curses.A_BOLD | curses.color_pair(4))
stdscr.addstr(3, max_x - len(min_price_str) - 2, min_price_str, curses.A_BOLD | curses.color_pair(4))
except curses.error:
pass
stdscr.refresh()
# Exit on 'q'
if stdscr.getch() == ord('q'):
break
def main():
curses.wrapper(draw_graph)
if __name__ == "__main__":
main()