kapr commited on
Commit
5024af9
·
verified ·
1 Parent(s): 5e5c049

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +250 -0
app.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import requests
4
+ from prophet import Prophet
5
+ import logging
6
+
7
+ logging.basicConfig(level=logging.INFO)
8
+
9
+ ########################################
10
+ # OKX endpoints & utility
11
+ ########################################
12
+
13
+ # 1) GET symbols (spot tickers)
14
+ OKX_TICKERS_ENDPOINT = "https://www.okx.com/api/v5/market/tickers?instType=SPOT"
15
+
16
+ # 2) GET historical candles for a symbol
17
+ # e.g. https://www.okx.com/api/v5/market/candles?instId=BTC-USDT&bar=1H&limit=100
18
+ OKX_CANDLE_ENDPOINT = "https://www.okx.com/api/v5/market/candles"
19
+
20
+ # You can extend or modify this to match more of OKX's `bar` intervals
21
+ TIMEFRAME_MAPPING = {
22
+ "1m": "1m",
23
+ "5m": "5m",
24
+ "15m": "15m",
25
+ "30m": "30m",
26
+ "1h": "1H",
27
+ "2h": "2H",
28
+ "4h": "4H",
29
+ "6h": "6H",
30
+ "12h": "12H",
31
+ "1d": "1D",
32
+ "1w": "1W", # OKX supports 1W, etc.
33
+ }
34
+
35
+ def fetch_okx_symbols():
36
+ """
37
+ Fetch the list of symbols (instId) from OKX Spot tickers.
38
+ """
39
+ logging.info("Fetching symbols from OKX Spot tickers...")
40
+ try:
41
+ resp = requests.get(OKX_TICKERS_ENDPOINT, timeout=30)
42
+ resp.raise_for_status()
43
+ json_data = resp.json()
44
+
45
+ if json_data.get("code") != "0":
46
+ logging.error(f"Non-zero code returned: {json_data}")
47
+ return ["Error: Could not fetch OKX symbols"]
48
+
49
+ data = json_data.get("data", [])
50
+ # Example item in data: { "instId": "ETH-USDT", "instType": "SPOT", ... }
51
+ symbols = [item["instId"] for item in data if item.get("instType") == "SPOT"]
52
+ if not symbols:
53
+ logging.warning("No spot symbols found.")
54
+ return ["Error: No spot symbols found."]
55
+
56
+ logging.info(f"Fetched {len(symbols)} OKX spot symbols.")
57
+ return sorted(symbols)
58
+
59
+ except Exception as e:
60
+ logging.error(f"Error fetching OKX symbols: {e}")
61
+ return [f"Error: {str(e)}"]
62
+
63
+ def fetch_okx_candles(symbol, timeframe="1H", limit=100):
64
+ """
65
+ Fetch historical candle data for a symbol from OKX.
66
+ timeframe must match OKX's `bar` (e.g. "1H", "4H", "1D").
67
+ Returns (DataFrame, error_message) or (DataFrame, "").
68
+ """
69
+ logging.info(f"Fetching {limit} candles for {symbol} @ {timeframe} from OKX...")
70
+ params = {
71
+ "instId": symbol,
72
+ "bar": timeframe,
73
+ "limit": limit
74
+ }
75
+
76
+ try:
77
+ resp = requests.get(OKX_CANDLE_ENDPOINT, params=params, timeout=30)
78
+ resp.raise_for_status()
79
+ json_data = resp.json()
80
+
81
+ if json_data.get("code") != "0":
82
+ msg = f"OKX returned code={json_data.get('code')}, msg={json_data.get('msg')}"
83
+ logging.error(msg)
84
+ return pd.DataFrame(), msg
85
+
86
+ # Data looks like: ["1673684400000", "20923.7", "20952.5", "20881.3", "20945.8", "927.879", "19412314.5671"]
87
+ # Let's parse columns: [0] ts, [1] open, [2] high, [3] low, [4] close, [5] volume, [6] ??? quoteVol
88
+ items = json_data.get("data", [])
89
+ if not items:
90
+ warning_msg = f"No candle data returned for {symbol}."
91
+ logging.warning(warning_msg)
92
+ return pd.DataFrame(), warning_msg
93
+
94
+ # items is a list of lists, each is a candle. Reverse if needed to go old->new:
95
+ # OKX returns the most recent data first, so we invert it for chronological order
96
+ items.reverse()
97
+
98
+ df = pd.DataFrame(items, columns=[
99
+ "timestamp", "open", "high", "low", "close", "volume", "quoteVolume"
100
+ ])
101
+ df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
102
+ df[["open", "high", "low", "close", "volume", "quoteVolume"]] = df[
103
+ ["open", "high", "low", "close", "volume", "quoteVolume"]
104
+ ].astype(float)
105
+
106
+ logging.info(f"Fetched {len(df)} rows for {symbol}.")
107
+ return df, ""
108
+ except Exception as e:
109
+ err_msg = f"Error fetching candles for {symbol}: {e}"
110
+ logging.error(err_msg)
111
+ return pd.DataFrame(), err_msg
112
+
113
+ ########################################
114
+ # Prophet pipeline
115
+ ########################################
116
+
117
+ def prepare_data_for_prophet(df):
118
+ """
119
+ Convert the DataFrame to a Prophet-compatible format.
120
+ """
121
+ if df.empty:
122
+ logging.warning("Empty DataFrame, cannot prepare data for Prophet.")
123
+ return pd.DataFrame(columns=["ds", "y"])
124
+
125
+ df_prophet = df.rename(columns={"timestamp": "ds", "close": "y"})
126
+ return df_prophet[["ds", "y"]]
127
+
128
+ def prophet_forecast(df_prophet, periods=10, freq="H"):
129
+ """
130
+ Train a Prophet model and forecast.
131
+ """
132
+ if df_prophet.empty:
133
+ logging.warning("Prophet input is empty, no forecast can be generated.")
134
+ return pd.DataFrame(), "No data to forecast."
135
+
136
+ try:
137
+ model = Prophet()
138
+ model.fit(df_prophet)
139
+ future = model.make_future_dataframe(periods=periods, freq=freq)
140
+ forecast = model.predict(future)
141
+ return forecast, ""
142
+ except Exception as e:
143
+ logging.error(f"Forecast error: {e}")
144
+ return pd.DataFrame(), f"Forecast error: {e}"
145
+
146
+ def prophet_wrapper(df_prophet, forecast_steps, freq):
147
+ """
148
+ Do the forecast, then slice out the new/future rows.
149
+ """
150
+ if len(df_prophet) < 10:
151
+ return pd.DataFrame(), "Not enough data for forecasting (need >=10 rows)."
152
+
153
+ full_forecast, err = prophet_forecast(df_prophet, forecast_steps, freq)
154
+ if err:
155
+ return pd.DataFrame(), err
156
+
157
+ # Only keep the newly generated future portion
158
+ future_only = full_forecast.iloc[len(df_prophet):, ["ds", "yhat", "yhat_lower", "yhat_upper"]]
159
+ return future_only, ""
160
+
161
+ ########################################
162
+ # Main Gradio logic
163
+ ########################################
164
+
165
+ def predict(symbol, timeframe, forecast_steps):
166
+ """
167
+ Orchestrate candle fetch + prophet forecast.
168
+ """
169
+ # Convert user timeframe to OKX bar param
170
+ okx_bar = TIMEFRAME_MAPPING.get(timeframe, "1H")
171
+
172
+ # Let’s fetch 500 candles
173
+ df_raw, err = fetch_okx_candles(symbol, timeframe=okx_bar, limit=500)
174
+ if err:
175
+ return pd.DataFrame(), err
176
+
177
+ df_prophet = prepare_data_for_prophet(df_raw)
178
+ # We guess frequency from timeframe. If timeframe is "1h", we'll do freq="H" in Prophet, etc.
179
+ # We'll do a simple mapping here:
180
+ freq = "H" if "h" in timeframe.lower() else "D" # e.g. "1h" -> "H", "1d" -> "D"
181
+
182
+ future_df, err2 = prophet_wrapper(df_prophet, forecast_steps, freq)
183
+ if err2:
184
+ return pd.DataFrame(), err2
185
+
186
+ return future_df, ""
187
+
188
+ def display_forecast(symbol, timeframe, forecast_steps):
189
+ """
190
+ For the Gradio UI, returns forecast or error message.
191
+ """
192
+ logging.info(f"User requested: symbol={symbol}, timeframe={timeframe}, steps={forecast_steps}")
193
+ forecast_df, error = predict(symbol, timeframe, forecast_steps)
194
+ if error:
195
+ return f"Error: {error}"
196
+ return forecast_df
197
+
198
+ def main():
199
+ # Fetch OKX symbols
200
+ symbols = fetch_okx_symbols()
201
+ if not symbols or "Error" in symbols[0]:
202
+ symbols = ["No symbols available"]
203
+
204
+ with gr.Blocks() as demo:
205
+ gr.Markdown("# OKX Price Forecasting with Prophet")
206
+ gr.Markdown(
207
+ "This app uses OKX's spot market candles to predict future price movements. "
208
+ "Select a symbol and timeframe, specify forecast steps, then click 'Generate Forecast'. "
209
+ "No proxies or special access required."
210
+ )
211
+
212
+ symbol_dd = gr.Dropdown(
213
+ label="Symbol",
214
+ choices=symbols,
215
+ value=symbols[0] if symbols else None
216
+ )
217
+ timeframe_dd = gr.Dropdown(
218
+ label="Timeframe",
219
+ choices=["1m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d", "1w"],
220
+ value="1h"
221
+ )
222
+ steps_slider = gr.Slider(
223
+ label="Forecast Steps (hours/days depending on timeframe)",
224
+ minimum=1,
225
+ maximum=100,
226
+ value=10
227
+ )
228
+ forecast_btn = gr.Button("Generate Forecast")
229
+
230
+ output_df = gr.Dataframe(
231
+ label="Future Forecast Only",
232
+ headers=["ds", "yhat", "yhat_lower", "yhat_upper"]
233
+ )
234
+
235
+ forecast_btn.click(
236
+ fn=display_forecast,
237
+ inputs=[symbol_dd, timeframe_dd, steps_slider],
238
+ outputs=output_df
239
+ )
240
+
241
+ gr.Markdown(
242
+ "Looking for more automation? Check out this "
243
+ "[crypto trading bot](https://www.gunbot.com)."
244
+ )
245
+
246
+ return demo
247
+
248
+ if __name__ == "__main__":
249
+ app = main()
250
+ app.launch()