Smart-Trader-EA commited on
Commit
8b49703
·
1 Parent(s): c6ef3b3

更新完整应用代码

Browse files
Files changed (1) hide show
  1. app.py +303 -95
app.py CHANGED
@@ -3,12 +3,19 @@ import pandas as pd
3
  import plotly.graph_objects as go
4
  from prophet import Prophet
5
  import os
 
 
6
 
 
 
 
 
7
 
 
8
  DATA_DIR = "data"
9
  available_tickers = {}
10
 
11
-
12
  if os.path.exists(DATA_DIR):
13
  for filename in os.listdir(DATA_DIR):
14
  if filename.endswith(".csv"):
@@ -16,140 +23,341 @@ if os.path.exists(DATA_DIR):
16
  file_path = os.path.join(DATA_DIR, filename)
17
  try:
18
  # 尝试不同编码读取
19
- for encoding in ['utf-8', 'gbk', 'latin1']:
 
 
 
20
  try:
21
  df = pd.read_csv(file_path, encoding=encoding)
 
22
  break
23
- except:
24
  continue
25
 
26
- # 识别日期列
27
- date_cols = [col for col in df.columns if 'date' in col.lower() or 'time' in col.lower()]
28
- if date_cols:
29
- df[date_cols[0]] = pd.to_datetime(df[date_cols[0]])
30
- df.set_index(date_cols[0], inplace=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  available_tickers[ticker_name] = {
33
  "file": file_path,
34
  "data": df
35
  }
36
- print(f"成功加载: {ticker_name}")
37
  except Exception as e:
38
- print(f"加载失败 {filename}: {str(e)}")
39
  else:
40
- print(f"警告: 数据目录不存在 - {DATA_DIR}")
41
 
42
  def analyze_stock(ticker):
43
- if not any(available_tickers):
44
- return "❌ 错误: 没有找到任何数据文件。请检查data/目录", None, None
45
-
46
-
47
- ticker_upper = ticker.upper()
48
- matched_ticker = None
49
-
50
-
51
- if ticker_upper in available_tickers:
52
- matched_ticker = ticker_upper
53
- else:
54
-
55
- for name in available_tickers.keys():
56
- if ticker_upper in name or name in ticker_upper:
57
- matched_ticker = name
58
- break
59
-
60
- if not matched_ticker:
61
- return f"❌ 未找到匹配的数据: {ticker}\n可用数据: {', '.join(available_tickers.keys())}", None, None
62
-
63
  try:
64
- hist = available_tickers[matched_ticker]["data"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
-
 
 
 
 
 
 
 
67
  required_cols = ['Open', 'High', 'Low', 'Close']
68
- if not all(col in hist.columns for col in required_cols):
69
- return f"❌ 数据格式错误: 缺少必要列。请确保CSV包含: {', '.join(required_cols)}", None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
-
72
- fig = go.Figure(data=[go.Candlestick(x=hist.index,
73
- open=hist['Open'],
74
- high=hist['High'],
75
- low=hist['Low'],
76
- close=hist['Close'])])
77
- fig.update_layout(title=f"{matched_ticker} 股票K线图 (本地数据)", xaxis_title="日期", yaxis_title="价格")
78
-
79
-
80
- df = hist[['Close']].reset_index()
81
- df.columns = ['ds', 'y']
82
- model = Prophet(daily_seasonality=True, yearly_seasonality=True)
83
- model.fit(df)
84
- future = model.make_future_dataframe(periods=30) # 30天预测
85
- forecast = model.predict(future)
86
-
87
- fig2 = go.Figure()
88
- fig2.add_trace(go.Scatter(x=df['ds'], y=df['y'], mode='lines', name='历史价格'))
89
- fig2.add_trace(go.Scatter(x=forecast['ds'], y=forecast['yhat'], mode='lines', name='预测价格'))
90
- fig2.update_layout(title=f"{matched_ticker} 30天价格预测", xaxis_title="日期", yaxis_title="价格")
91
-
92
-
93
- hist['MA20'] = hist['Close'].rolling(20).mean()
94
- hist['MA50'] = hist['Close'].rolling(50).mean()
95
  current_price = hist['Close'].iloc[-1]
96
- ma20 = hist['MA20'].iloc[-1]
97
- ma50 = hist['MA50'].iloc[-1]
98
-
99
-
100
- if current_price > ma20 > ma50:
101
- signal = "📈 强烈看涨 (黄金交叉)"
102
- elif current_price > ma20:
103
- signal = "📈 看涨"
104
- elif current_price < ma20 < ma50:
105
- signal = "📉 强烈看跌 (死亡交叉)"
106
- else:
107
- signal = "🔄 震荡"
 
 
 
 
 
 
 
108
 
109
  result_text = (
110
- f"📊 {matched_ticker} 分析结果\n"
111
- f"💰 当前价格: ${current_price:.2f}\n"
112
- f"📈 20日均线: ${ma20:.2f}\n"
113
- f"📉 50日均线: ${ma50:.2f}\n"
114
- f"🎯 信号: {signal}\n"
115
- f"💾 数据来源: 本地文件 ({len(hist)} 条记录)"
 
 
116
  )
117
 
118
  return result_text, fig, fig2
119
 
120
  except Exception as e:
121
- return f"❌ 分析错误: {str(e)}", None, None
 
 
122
 
123
  def list_available_data():
 
124
  if not available_tickers:
125
- return "暂无可用数据文件"
126
- return "可用数据: " + ", ".join(available_tickers.keys())
 
 
 
 
 
 
127
 
128
- with gr.Blocks(title="股票AI分析 (本地数据版)") as demo:
129
- gr.Markdown("# 📈 股票AI分析系统 (本地历史数据版)")
130
- gr.Markdown(" 优势: 无需网络,数据稳定,适合中长期分析")
 
131
 
132
- data_status = gr.Textbox(label="数据状态", value=list_available_data(), interactive=False)
 
 
 
133
 
 
134
  with gr.Row():
135
- ticker_input = gr.Textbox(label="股票代码/名称", value="EURUSD")
136
- analyze_btn = gr.Button("分析", variant="primary")
 
 
 
137
 
138
- signal_output = gr.Textbox(label="分析结果")
139
  with gr.Row():
140
- kchart = gr.Plot(label="K线图")
141
- pred_chart = gr.Plot(label="价格预测")
 
 
 
 
 
 
 
 
142
 
143
- gr.Markdown("### 使用指南:\n"
144
- "1. 输入货币对名称例如: EURUSD\n"
145
- "2. 系统自动从本地数据加载\n"
146
- "3. 查看技术分析和30天预测\n"
147
- "4. 定期更新data/目录中的CSV文件")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
 
149
  analyze_btn.click(
150
  fn=analyze_stock,
151
  inputs=ticker_input,
152
  outputs=[signal_output, kchart, pred_chart]
153
  )
154
 
155
- demo.launch()
 
 
 
 
 
 
 
3
  import plotly.graph_objects as go
4
  from prophet import Prophet
5
  import os
6
+ import numpy as np
7
+ from datetime import datetime
8
 
9
+ # 设置环境变量(优化M1芯片性能)
10
+ os.environ["OMP_NUM_THREADS"] = "1"
11
+ os.environ["OPENBLAS_NUM_THREADS"] = "1"
12
+ os.environ["MKL_NUM_THREADS"] = "1"
13
 
14
+ # 预加载所有数据文件
15
  DATA_DIR = "data"
16
  available_tickers = {}
17
 
18
+ # 检查data目录是否存在
19
  if os.path.exists(DATA_DIR):
20
  for filename in os.listdir(DATA_DIR):
21
  if filename.endswith(".csv"):
 
23
  file_path = os.path.join(DATA_DIR, filename)
24
  try:
25
  # 尝试不同编码读取
26
+ encodings = ['utf-8', 'gbk', 'latin1', 'ISO-8859-1']
27
+ df = None
28
+
29
+ for encoding in encodings:
30
  try:
31
  df = pd.read_csv(file_path, encoding=encoding)
32
+ print(f"成功使用 {encoding} 编码加载 {filename}")
33
  break
34
+ except UnicodeDecodeError:
35
  continue
36
 
37
+ if df is None:
38
+ raise Exception("无法解码文件")
39
+
40
+ # 自动检测日期列
41
+ date_col = None
42
+ for col in df.columns:
43
+ if 'date' in col.lower() or 'time' in col.lower():
44
+ date_col = col
45
+ break
46
+
47
+ # 如果没有找到日期列,尝试第一列
48
+ if date_col is None and len(df.columns) > 0:
49
+ date_col = df.columns[0]
50
+
51
+ # 转换日期列
52
+ if date_col:
53
+ df[date_col] = pd.to_datetime(df[date_col], errors='coerce')
54
+ df = df.dropna(subset=[date_col])
55
+ df.set_index(date_col, inplace=True)
56
+
57
+ # 检查必需的列
58
+ required_columns = ['Open', 'High', 'Low', 'Close']
59
+ found_columns = [col for col in required_columns if col in df.columns]
60
+
61
+ if len(found_columns) < 3: # 至少需要3列
62
+ print(f"警告: {filename} 缺少必要列,将跳过")
63
+ continue
64
 
65
  available_tickers[ticker_name] = {
66
  "file": file_path,
67
  "data": df
68
  }
69
+ print(f"成功加载: {ticker_name} ({len(df)} 条记录)")
70
  except Exception as e:
71
+ print(f"加载失败 {filename}: {str(e)}")
72
  else:
73
+ print(f"⚠️ 警告: 数据目录不存在 - {DATA_DIR}")
74
 
75
  def analyze_stock(ticker):
76
+ """分析股票/外汇数据"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  try:
78
+ # 检查是否有可用数据
79
+ if not available_tickers:
80
+ return "❌ 错误: 没有找到任何数据文件。请检查data/目录", None, None
81
+
82
+ # 模糊匹配股票代码
83
+ ticker_upper = ticker.upper()
84
+ matched_ticker = None
85
+
86
+ # 尝试精确匹配
87
+ if ticker_upper in available_tickers:
88
+ matched_ticker = ticker_upper
89
+ else:
90
+ # 尝试部分匹配
91
+ for name in available_tickers.keys():
92
+ if ticker_upper in name or name in ticker_upper:
93
+ matched_ticker = name
94
+ break
95
 
96
+ if not matched_ticker:
97
+ return (f"❌ 未找到匹配的数据: {ticker}\n"
98
+ f"可用数据: {', '.join(available_tickers.keys())}"), None, None
99
+
100
+ # 获取数据
101
+ hist = available_tickers[matched_ticker]["data"].copy()
102
+
103
+ # 确保必要的列存在
104
  required_cols = ['Open', 'High', 'Low', 'Close']
105
+ missing_cols = [col for col in required_cols if col not in hist.columns]
106
+
107
+ if missing_cols:
108
+ # 尝试查找相似列名
109
+ col_mapping = {}
110
+ for col in missing_cols:
111
+ for existing_col in hist.columns:
112
+ if col.lower() in existing_col.lower() or existing_col.lower() in col.lower():
113
+ col_mapping[existing_col] = col
114
+
115
+ if col_mapping:
116
+ hist.rename(columns=col_mapping, inplace=True)
117
+ missing_cols = [col for col in required_cols if col not in hist.columns]
118
+
119
+ if missing_cols:
120
+ return (f"❌ 数据格式错误: 缺少必要列: {', '.join(missing_cols)}\n"
121
+ f"可用列: {', '.join(hist.columns)}"), None, None
122
+
123
+ # 创建K线图
124
+ fig = go.Figure(data=[go.Candlestick(
125
+ x=hist.index,
126
+ open=hist['Open'],
127
+ high=hist['High'],
128
+ low=hist['Low'],
129
+ close=hist['Close'],
130
+ name='价格'
131
+ )])
132
+
133
+ # 添加移动平均线
134
+ if len(hist) >= 20:
135
+ hist['MA20'] = hist['Close'].rolling(window=20, min_periods=1).mean()
136
+ fig.add_trace(go.Scatter(
137
+ x=hist.index,
138
+ y=hist['MA20'],
139
+ mode='lines',
140
+ name='20日均线',
141
+ line=dict(color='blue', width=1.5)
142
+ ))
143
+
144
+ if len(hist) >= 50:
145
+ hist['MA50'] = hist['Close'].rolling(window=50, min_periods=1).mean()
146
+ fig.add_trace(go.Scatter(
147
+ x=hist.index,
148
+ y=hist['MA50'],
149
+ mode='lines',
150
+ name='50日均线',
151
+ line=dict(color='orange', width=1.5)
152
+ ))
153
+
154
+ fig.update_layout(
155
+ title=f"{matched_ticker} 价格走势 (本地数据)",
156
+ xaxis_title="日期",
157
+ yaxis_title="价格",
158
+ template="plotly_white",
159
+ hovermode="x unified",
160
+ height=500
161
+ )
162
+
163
+ # 预测 - 使用Prophet (修复版本)
164
+ try:
165
+ # 准备数据
166
+ df = hist[['Close']].reset_index()
167
+ df.columns = ['ds', 'y']
168
+
169
+ # 移除NaN值
170
+ df = df.dropna()
171
+
172
+ # 确保有足够数据
173
+ if len(df) < 30:
174
+ raise ValueError("数据点不足,无法进行预测")
175
+
176
+ # 创建并拟合模型 (修复: 移除stan_backend)
177
+ model = Prophet(
178
+ daily_seasonality=True,
179
+ yearly_seasonality=True,
180
+ interval_width=0.95,
181
+ uncertainty_samples=1000
182
+ )
183
+
184
+ model.fit(df)
185
+
186
+ # 创建未来数据框 (30天预测)
187
+ future = model.make_future_dataframe(periods=30, freq='D')
188
+ forecast = model.predict(future)
189
+
190
+ # 预测图表
191
+ fig2 = go.Figure()
192
+
193
+ # 历史数据
194
+ fig2.add_trace(go.Scatter(
195
+ x=df['ds'],
196
+ y=df['y'],
197
+ mode='lines',
198
+ name='历史价格',
199
+ line=dict(color='blue', width=2)
200
+ ))
201
+
202
+ # 预测数据
203
+ fig2.add_trace(go.Scatter(
204
+ x=forecast['ds'],
205
+ y=forecast['yhat'],
206
+ mode='lines',
207
+ name='预测价格',
208
+ line=dict(color='red', width=2, dash='dash')
209
+ ))
210
+
211
+ # 预测区间
212
+ fig2.add_trace(go.Scatter(
213
+ x=forecast['ds'].tolist() + forecast['ds'][::-1].tolist(),
214
+ y=forecast['yhat_upper'].tolist() + forecast['yhat_lower'][::-1].tolist(),
215
+ fill='toself',
216
+ fillcolor='rgba(255,0,0,0.1)',
217
+ line=dict(color='rgba(255,255,255,0)'),
218
+ name='置信区间'
219
+ ))
220
+
221
+ fig2.update_layout(
222
+ title=f"{matched_ticker} 30天价格预测",
223
+ xaxis_title="日期",
224
+ yaxis_title="价格",
225
+ template="plotly_white",
226
+ hovermode="x unified",
227
+ height=500
228
+ )
229
+
230
+ except Exception as e:
231
+ print(f"预测错误: {str(e)}")
232
+ fig2 = None
233
+ forecast_error = str(e)
234
+
235
+ # 技术指标计算
236
+ if 'MA20' not in hist.columns and len(hist) >= 20:
237
+ hist['MA20'] = hist['Close'].rolling(20).mean()
238
+
239
+ if 'MA50' not in hist.columns and len(hist) >= 50:
240
+ hist['MA50'] = hist['Close'].rolling(50).mean()
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  current_price = hist['Close'].iloc[-1]
243
+ ma20 = hist['MA20'].iloc[-1] if 'MA20' in hist.columns else None
244
+ ma50 = hist['MA50'].iloc[-1] if 'MA50' in hist.columns else None
245
+
246
+ # 信号判断
247
+ signal = "🔍 数据不足,无法判断信号"
248
+ if ma20 is not None:
249
+ if current_price > ma20:
250
+ signal = "📈 看涨 (价格 > 20日均线)"
251
+ else:
252
+ signal = "📉 看跌 (价格 < 20日均线)"
253
+
254
+ if ma20 is not None and ma50 is not None:
255
+ if current_price > ma20 > ma50:
256
+ signal = "🚀 强烈看涨 (黄金交叉)"
257
+ elif current_price < ma20 < ma50:
258
+ signal = "💣 强烈看跌 (死亡交叉)"
259
+
260
+ # 计算回报率
261
+ period_return = (current_price / hist['Close'].iloc[0] - 1) * 100
262
 
263
  result_text = (
264
+ f"📊 {matched_ticker} 分析报告\n"
265
+ f"💰 当前价格: {current_price:.5f}\n"
266
+ f"📈 20日均线: {ma20:.5f}\n" if ma20 is not None else ""
267
+ f"📉 50日均线: {ma50:.5f}\n" if ma50 is not None else ""
268
+ f"🎯 交易信号: {signal}\n"
269
+ f"📊 总回报率: {period_return:.2f}%\n"
270
+ f"💾 数据记录: {len(hist)} 条\n"
271
+ f"🕒 更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}"
272
  )
273
 
274
  return result_text, fig, fig2
275
 
276
  except Exception as e:
277
+ error_msg = f"❌ 分析错误: {str(e)}"
278
+ print(error_msg)
279
+ return error_msg, None, None
280
 
281
  def list_available_data():
282
+ """列出可用数据"""
283
  if not available_tickers:
284
+ return "⚠️ 未找到数据文件。请将CSV文件放入data/目录"
285
+ return "可用数据: " + ", ".join(available_tickers.keys())
286
+
287
+ def get_app_version():
288
+ """获取应用版本信息"""
289
+ return f"📈 外汇/股票AI分析系统 v2.1\n" \
290
+ f"🕒 最后更新: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n" \
291
+ f"📊 可用数据集: {len(available_tickers)}"
292
 
293
+ # 创建Gradio界面
294
+ with gr.Blocks(title="外汇AI分析系统", theme=gr.themes.Soft()) as demo:
295
+ gr.Markdown("# 📈 外汇/股票AI分析系统 (本地数据版)")
296
+ gr.Markdown("✅ 优势: 无需网络,数据稳定,隐私安全,适合中长期分析")
297
 
298
+ # 状态信息
299
+ with gr.Row():
300
+ version_info = gr.Textbox(label="系统信息", value=get_app_version(), interactive=False)
301
+ data_status = gr.Textbox(label="数据状态", value=list_available_data(), interactive=False)
302
 
303
+ # 分析区域
304
  with gr.Row():
305
+ ticker_input = gr.Textbox(label="数据名称", value="EURUSD", placeholder="输入数据文件名,如: EURUSD, AAPL")
306
+ analyze_btn = gr.Button("分析", variant="primary", size="lg")
307
+
308
+ # 结果区域
309
+ signal_output = gr.Textbox(label="分析结果", lines=6)
310
 
 
311
  with gr.Row():
312
+ kchart = gr.Plot(label="价格走势与技术指标")
313
+ pred_chart = gr.Plot(label="30天价格预测")
314
+
315
+ # 使用指南
316
+ gr.Markdown("""
317
+ ### 📋 使用指南
318
+ 1. **输入数据名称**:使用数据文件名(不含.csv后缀),例如 `EURUSD`
319
+ 2. **点击"分析"**:系统将加载本地数据并生成分析报告
320
+ 3. **查看结果**:包括技术指标、交易信号和价格预测
321
+ 4. **数据更新**:上传新的CSV文件到data/目录,重新部署应用
322
 
323
+ ### 🔍 数据格式要求
324
+ CSV文件应包含以下列不区分大小写
325
+ - 日期列 (Date/Time)
326
+ - 开盘价 (Open)
327
+ - 最高价 (High)
328
+ - 最低价 (Low)
329
+ - 收盘价 (Close)
330
+
331
+ ### 💡 提示
332
+ - 首次加载可能需要1-2分钟
333
+ - 数据越多,预测越准确(建议至少6个月数据)
334
+ - 支持多种金融产品:外汇、股票、加密货币
335
+ """)
336
+
337
+ # 示例按钮
338
+ with gr.Row():
339
+ gr.Examples(
340
+ examples=[
341
+ ["EURUSD"],
342
+ ["AAPL"],
343
+ ["BTCUSD"]
344
+ ],
345
+ inputs=ticker_input,
346
+ label="常用数据示例",
347
+ examples_per_page=3
348
+ )
349
 
350
+ # 分析按钮点击事件
351
  analyze_btn.click(
352
  fn=analyze_stock,
353
  inputs=ticker_input,
354
  outputs=[signal_output, kchart, pred_chart]
355
  )
356
 
357
+ # 启动应用
358
+ if __name__ == "__main__":
359
+ demo.launch(
360
+ server_name="0.0.0.0",
361
+ server_port=7860,
362
+ share=False
363
+ )