Lullooo commited on
Commit
17fd61b
·
verified ·
1 Parent(s): 281e26b

BTC volatility forecasting & nowcasting

Browse files
Files changed (1) hide show
  1. app.py +216 -0
app.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ################### PACKAGES
2
+ import pandas as pd
3
+ import numpy as np
4
+ from datetime import datetime, timedelta
5
+ import time
6
+ import requests
7
+ from pytrends.request import TrendReq
8
+ import joblib
9
+ import gradio as gr
10
+ from sklearn.preprocessing import StandardScaler
11
+
12
+
13
+ ################### MODEL LOADING
14
+ # Load XGBoost and NGBoost
15
+ xgb_artifact = joblib.load("xgb_volatility_model.joblib")
16
+ xgb_model = xgb_artifact["model"]
17
+ xgb_features = xgb_artifact["feature_names"]
18
+
19
+ ngb_artifact = joblib.load("ngb_volatility_model.joblib")
20
+ ngb_model = ngb_artifact["model"]
21
+ ngb_features = ngb_artifact["feature_names"]
22
+
23
+ forecast_ngb_artifact = joblib.load("Forecast_ngb_volatility_model.joblib")
24
+ forecast_ngb_model = forecast_ngb_artifact["model"]
25
+ forecast_ngb_features = forecast_ngb_artifact["feature_names"]
26
+
27
+ # Load KMeans + scaler
28
+ kmeans_artifact = joblib.load("kmeans_model.joblib")
29
+ kmeans_model = kmeans_artifact["model"]
30
+ cluster_scaler = kmeans_artifact["scaler"]
31
+ # Only use the features that were actually used during training
32
+ #cluster_features_names = ["close", "volume", "trend", "fg_index"]
33
+ cluster_features_names = kmeans_artifact["feature_names"]
34
+
35
+ ################### FEATURE GATHERING
36
+ def fetch_ohlcv_last_n_days(date="2026-01-18", n_days=90):
37
+ end = pd.to_datetime(date)
38
+ start = end - timedelta(days=n_days)
39
+
40
+ url = "https://api.coingecko.com/api/v3/coins/bitcoin/market_chart/range"
41
+ params = {
42
+ "vs_currency": "usd",
43
+ "from": int(start.timestamp()),
44
+ "to": int(end.timestamp())
45
+ }
46
+
47
+ r = requests.get(url, params=params, timeout=20)
48
+ r.raise_for_status()
49
+ data = r.json()
50
+
51
+ prices = pd.DataFrame(data["prices"], columns=["timestamp", "close"])
52
+ volumes = pd.DataFrame(data["total_volumes"], columns=["timestamp", "volume"])
53
+
54
+ df = prices.merge(volumes, on="timestamp")
55
+ df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
56
+ df.set_index("timestamp", inplace=True)
57
+
58
+ df = df.resample("1D").agg(
59
+ open=("close", "first"),
60
+ high=("close", "max"),
61
+ low=("close", "min"),
62
+ close=("close", "last"),
63
+ volume=("volume", "sum")
64
+ ).dropna()
65
+
66
+ return df
67
+
68
+ def fetch_fg_last_6_days(df):
69
+ url = "https://api.alternative.me/fng/?limit=0&format=json"
70
+ response = requests.get(url)
71
+ fg_df = pd.DataFrame(response.json()["data"])
72
+ fg_df["timestamp"] = pd.to_datetime(fg_df["timestamp"], unit="s")
73
+ fg_df.set_index("timestamp", inplace=True)
74
+ fg_df = fg_df[["value"]].astype(float)
75
+ fg_df.rename(columns={"value":"fg_index"}, inplace=True)
76
+ fg_df = fg_df.reindex(df.index, method="ffill")
77
+ df["fg_index"] = fg_df["fg_index"].values
78
+ return df
79
+
80
+ def fetch_google_trend_for_date(keyword="Bitcoin", target_date="2024-01-15", window=7):
81
+ pytrends = TrendReq(hl="en-US", tz=360)
82
+ target_date = pd.to_datetime(target_date)
83
+ start_date = (target_date - timedelta(days=window)).strftime("%Y-%m-%d")
84
+ end_date = target_date.strftime("%Y-%m-%d")
85
+ timeframe = f"{start_date} {end_date}"
86
+ try:
87
+ pytrends.build_payload([keyword], timeframe=timeframe)
88
+ df = pytrends.interest_over_time()
89
+ if df.empty:
90
+ return 0
91
+ df = df.rename(columns={keyword: "trend"})
92
+ df = df.drop(columns=["isPartial"], errors="ignore")
93
+ df.index = pd.to_datetime(df.index)
94
+ if target_date in df.index:
95
+ return df.loc[target_date, "trend"]
96
+ else:
97
+ return 0
98
+ except:
99
+ return 0
100
+
101
+
102
+ ################### FEATURE ENGINEERING
103
+ def engineer_features(df):
104
+ df["log_return"] = np.log(df["close"] / df["close"].shift(1))
105
+ df["hl_spread"] = df["high"] - df["low"]
106
+ df["co_spread"] = df["close"] - df["open"]
107
+ df["momentum_3"] = df["close"] - df["close"].shift(3)
108
+ df["vol_change"] = df["volume"] - df["volume"].shift(1)
109
+ df["rolling_std_5"] = df["log_return"].rolling(5).std()
110
+ # Fill NaNs for first few rows
111
+ df = df.fillna(0)
112
+ return df
113
+
114
+ ################### ASSIGN CLUSTER
115
+ def assign_cluster(df):
116
+ cluster_features = df[cluster_features_names]
117
+ scaled = cluster_scaler.transform(cluster_features)
118
+ df["cluster"] = kmeans_model.predict(scaled)
119
+ return df
120
+
121
+ ################### PREDICTION FUNCTION
122
+ def predict_volatility(date):
123
+ # Fetch raw data
124
+ df = fetch_ohlcv_last_n_days(date=date, n_days=90)
125
+ df = fetch_fg_last_6_days(df)
126
+ trend_value = fetch_google_trend_for_date("Bitcoin", date)
127
+ df["trend"] = trend_value
128
+ df["trend"] = df["trend"].ffill().fillna(0)
129
+
130
+ # Feature engineering
131
+ df = engineer_features(df)
132
+
133
+ # Clustering
134
+ df = assign_cluster(df)
135
+
136
+ assert "cluster" in df.columns, "Cluster feature missing after assignment"
137
+
138
+
139
+ # ---------------- NOWCASTING ----------------
140
+ X_xgb = df.iloc[-1:][xgb_features]
141
+ X_ngb = df.iloc[-1:][ngb_features]
142
+
143
+ point = xgb_model.predict(X_xgb)[0]
144
+ dist = ngb_model.pred_dist(X_ngb)
145
+ low, high = dist.ppf([0.025, 0.975])
146
+
147
+ # ---------------- FORECASTING (t+1) ----------------
148
+ X_ngb_fore = df.iloc[-1:][forecast_ngb_features]
149
+
150
+ for_point = forecast_ngb_model.predict(X_ngb_fore)[0]
151
+ for_dist = forecast_ngb_model.pred_dist(X_ngb_fore)
152
+ for_low, for_high = for_dist.ppf([0.025, 0.975])
153
+
154
+ return point, low, high, for_point, for_low, for_high
155
+
156
+
157
+
158
+ ################### GRADIO INTERFACE
159
+ import gradio as gr
160
+ import pandas as pd
161
+
162
+ ################### HELPER FUNCTION TO RETURN TABLE ###################
163
+ def predict_volatility_for_table(date):
164
+ """
165
+ Returns a DataFrame with Nowcast and Forecast predictions for the given date.
166
+ """
167
+ # Run your existing predict_volatility function
168
+ point, low, high, for_point, for_low, for_high = predict_volatility(date)
169
+
170
+ # Create a DataFrame to display nicely
171
+ data = {
172
+ "Type": ["Nowcast (t)", "Forecast (t+1)"],
173
+ "Volatility": [point, for_point],
174
+ "Low 95% CI": [low, for_low],
175
+ "High 95% CI": [high, for_high]
176
+ }
177
+
178
+ df = pd.DataFrame(data)
179
+
180
+ # Round values for better readability
181
+ df[["Volatility", "Low 95% CI", "High 95% CI"]] = df[["Volatility", "Low 95% CI", "High 95% CI"]].round(4)
182
+
183
+ return df
184
+
185
+ ################### GRADIO WRAPPER ###################
186
+ def gradio_predict(date):
187
+ """
188
+ Wrapper for Gradio. Returns a DataFrame for display.
189
+ """
190
+ # Validate date format
191
+ try:
192
+ pd.to_datetime(date)
193
+ except:
194
+ return pd.DataFrame({"Error": ["❌ Invalid date format. Use YYYY-MM-DD."]})
195
+
196
+ # Attempt to predict
197
+ try:
198
+ df = predict_volatility_for_table(date)
199
+ return df
200
+ except Exception as e:
201
+ return pd.DataFrame({"Error": [f"⚠️ Error while computing prediction:\n{str(e)}"]})
202
+
203
+ ################### GRADIO INTERFACE ###################
204
+ demo = gr.Interface(
205
+ fn=gradio_predict,
206
+ inputs=gr.Textbox(label="📆 Date (YYYY-MM-DD)"),
207
+ outputs=gr.Dataframe(label="Volatility Predictions", headers=["Type", "🎯 Volatility", "Low 95% CI", "High 95% CI"]),
208
+ title="BTC Volatility Predictor",
209
+ description=(
210
+ "Enter a date to get predicted BTC volatility and 95% confidence intervals.\n"
211
+ "Nowcast = today's volatility, Forecast = next day's volatility."
212
+ )
213
+ )
214
+
215
+ if __name__ == "__main__":
216
+ demo.launch()