Files
FX-QUANT-SCAN/viz/strength_meter.py
T
2025-08-29 11:10:28 +02:00

289 lines
9.9 KiB
Python

# viz/strength_meter.py
# viz/strength_meter.py
import streamlit as st
import pandas as pd
import numpy as np
import yfinance as yf
from datetime import datetime, timezone
import plotly.express as px
import plotly.graph_objects as go
# === CONFIG (Same as your heatmap) ===
CURRENCY_LIST = ['USD','CAD', 'EUR', 'GBP', 'CHF', 'NOK', 'SGD','JPY', 'AUD', 'NZD']
def get_daily_pct_change(ticker):
"""Get daily percentage change for a currency pair (same as heatmap)"""
try:
data = yf.download(ticker, period="2d", interval="1d", progress=False, auto_adjust=False)
if len(data) < 2:
return None
open_val = data['Open'].iloc[-1].item()
close_val = data['Close'].iloc[-1].item()
return (close_val - open_val) / open_val * 100
except Exception as e:
print(f"[⚠️] Error fetching {ticker}: {e}")
return None
def calculate_currency_strength():
"""Calculate individual currency strength by averaging against all pairs"""
# Dictionary to store each currency's performance against others
currency_scores = {currency: [] for currency in CURRENCY_LIST}
# Progress bar for data fetching
progress_bar = st.progress(0)
status_text = st.empty()
total_pairs = len(CURRENCY_LIST) * (len(CURRENCY_LIST) - 1)
current_pair = 0
for base in CURRENCY_LIST:
for quote in CURRENCY_LIST:
if base == quote:
continue # Skip same currency pairs
pair = f"{base}{quote}=X"
status_text.text(f"Analyzing {pair}...")
pct_change = get_daily_pct_change(pair)
if pct_change is not None:
# Base currency gains strength when pair goes UP
currency_scores[base].append(pct_change)
# Quote currency gains strength when pair goes DOWN
currency_scores[quote].append(-pct_change)
current_pair += 1
progress_bar.progress(current_pair / total_pairs)
# Clear progress indicators
progress_bar.empty()
status_text.empty()
# Calculate average strength for each currency
strength_data = []
for currency, scores in currency_scores.items():
if scores: # Only if we have data
avg_strength = np.mean(scores)
strength_data.append({
'Currency': currency,
'Strength_Score': round(avg_strength, 3),
'Data_Points': len(scores)
})
# Convert to DataFrame and sort by strength
df = pd.DataFrame(strength_data)
df = df.sort_values('Strength_Score', ascending=False).reset_index(drop=True)
df['Rank'] = df.index + 1
return df
def create_strength_chart(df):
"""Create beautiful strength meter visualization"""
# Create colors based on strength (Green = Strong, Red = Weak)
colors = []
for score in df['Strength_Score']:
if score > 0.5:
colors.append('#63BE7B') # Strong Green
elif score > 0.1:
colors.append('#90EE90') # Light Green
elif score > -0.1:
colors.append('#FFEB84') # Yellow (Neutral)
elif score > -0.5:
colors.append('#FFB6C1') # Light Red
else:
colors.append('#F8696B') # Strong Red
# Create horizontal bar chart
fig = go.Figure()
fig.add_trace(go.Bar(
y=df['Currency'],
x=df['Strength_Score'],
orientation='h',
marker=dict(
color=colors,
line=dict(color='rgba(0,0,0,0.8)', width=1)
),
text=[f"{score:.2f}%" for score in df['Strength_Score']],
textposition='outside',
textfont=dict(size=12, color='white', family='Arial Black'),
hovertemplate='<b>%{y}</b><br>Strength: %{x:.2f}%<br>Rank: #%{customdata}<extra></extra>',
customdata=df['Rank']
))
fig.update_layout(
title={
'text': f"💪 Currency Strength Meter - {datetime.now().strftime('%Y-%m-%d')}",
'x': 0.5,
'font': {'size': 20, 'color': 'white', 'family': 'Arial Black'}
},
xaxis_title="Strength Score (%)",
yaxis_title="Currency",
font=dict(size=12, color='white'),
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
height=600,
margin=dict(l=80, r=120, t=100, b=80),
showlegend=False
)
# Add vertical line at zero
fig.add_vline(x=0, line_dash="dash", line_color="gray", opacity=0.7)
fig.update_xaxes(tickfont=dict(size=12, color='white'))
fig.update_yaxes(tickfont=dict(size=12, color='white', family='Arial Black'))
return fig
def create_strength_table(df):
"""Create a formatted strength ranking table"""
# Add visual indicators
df_display = df.copy()
# Add emoji indicators based on strength
def get_strength_emoji(score):
if score > 0.5:
return "🚀"
elif score > 0.1:
return "📈"
elif score > -0.1:
return "➡️"
elif score > -0.5:
return "📉"
else:
return "🔻"
df_display['Status'] = df_display['Strength_Score'].apply(get_strength_emoji)
df_display['Strength %'] = df_display['Strength_Score'].apply(lambda x: f"{x:.2f}%")
# Select columns for display
display_df = df_display[['Rank', 'Currency', 'Status', 'Strength %', 'Data_Points']]
return display_df
def strength_meter():
st.title("💪 Currency Strength Meter")
# Add refresh button and info (same pattern as heatmap)
col1, col2, col3 = st.columns([1, 2, 1])
with col1:
refresh_data = st.button("🔄 Refresh Live Data", help="Calculate latest currency strength")
with col3:
st.info(f"🕐 {datetime.now().strftime('%H:%M UTC')}")
# Generate or use cached data
if refresh_data or 'strength_meter_cache' not in st.session_state:
st.info("🚀 Calculating currency strength...")
with st.spinner("Analyzing currency pairs..."):
strength_df = calculate_currency_strength()
st.session_state.strength_meter_cache = strength_df
st.session_state.strength_timestamp = datetime.now()
else:
strength_df = st.session_state.strength_meter_cache
cache_time = st.session_state.get('strength_timestamp', datetime.now())
st.caption(f"📋 Cached data from: {cache_time.strftime('%H:%M:%S')}")
# Display results
if not strength_df.empty:
# Main strength chart
fig = create_strength_chart(strength_df)
st.plotly_chart(fig, use_container_width=True)
# Show ranking table
st.subheader("🏆 Currency Rankings")
display_df = create_strength_table(strength_df)
# Use columns to make it look nicer
col1, col2 = st.columns([2, 1])
with col1:
st.dataframe(
display_df.style.apply(
lambda x: ['background-color: #63BE7B; color: black' if i < 3
else 'background-color: #F8696B; color: white' if i >= len(x) - 3
else '' for i in range(len(x))],
axis=0
),
use_container_width=True,
hide_index=True
)
with col2:
st.info("""
**💡 How to Read:**
🚀 **Very Strong** (>0.5%)
📈 **Strong** (>0.1%)
➡️ **Neutral** (-0.1% to 0.1%)
📉 **Weak** (<-0.1%)
🔻 **Very Weak** (<-0.5%)
""")
# Summary stats
st.subheader("📊 Market Summary")
col1, col2, col3, col4 = st.columns(4)
with col1:
strongest = strength_df.iloc[0]
st.metric(
"🥇 Strongest",
strongest['Currency'],
f"{strongest['Strength_Score']:.2f}%"
)
with col2:
weakest = strength_df.iloc[-1]
st.metric(
"🥉 Weakest",
weakest['Currency'],
f"{weakest['Strength_Score']:.2f}%"
)
with col3:
avg_strength = strength_df['Strength_Score'].mean()
st.metric("📈 Average", f"{avg_strength:.2f}%")
with col4:
strength_range = strength_df['Strength_Score'].max() - strength_df['Strength_Score'].min()
st.metric("📏 Range", f"{strength_range:.2f}%")
# Export functionality
st.subheader("💾 Export Data")
col1, col2 = st.columns(2)
with col1:
csv_data = strength_df.to_csv(index=False).encode('utf-8')
st.download_button(
"📥 Download CSV",
csv_data,
file_name=f"currency_strength_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
mime="text/csv"
)
with col2:
# Show raw data
if st.checkbox("📋 Show Raw Data"):
st.dataframe(strength_df, use_container_width=True)
else:
st.error("❌ Could not calculate currency strength - no data available")
# Additional info
st.markdown("---")
st.markdown("""
**📖 About Currency Strength:**
The strength meter calculates how each currency performs against all other major currencies.
A positive score means the currency is gaining strength, while negative means it's weakening.
**📊 Calculation Method:**
- For each currency pair (e.g., EURUSD), if EUR goes up, EUR gets +points and USD gets -points
- Each currency's final score is the average of all its pair performances
- Rankings show relative strength in the current market session
""")