Spaces:
Running
Running
Update update_predictions.py
Browse files- update_predictions.py +32 -147
update_predictions.py
CHANGED
@@ -28,19 +28,21 @@ Config = {
|
|
28 |
"PREDICTION_CACHE": os.path.join("/tmp", "predictions_cache"),
|
29 |
"CHART_PATH": os.path.join("/tmp", "prediction_chart.png"),
|
30 |
"HTML_PATH": os.path.join("/tmp", "index.html"),
|
31 |
-
#
|
32 |
"LAST_INFERENCED_BUSINESS_DATE": None,
|
|
|
|
|
|
|
|
|
33 |
"CACHED_RESULTS": {
|
34 |
"close_preds": None,
|
35 |
"volume_preds": None,
|
36 |
"v_close_preds": None,
|
37 |
-
"upside_prob": None,
|
38 |
-
"vol_amp_prob": None,
|
39 |
"hist_df_for_plot": None
|
40 |
}
|
41 |
}
|
42 |
|
43 |
-
#
|
44 |
Config["CHINESE_FONT_PATH"] = os.path.join(Config["REPO_PATH"], "fonts", "wqy-microhei.ttf")
|
45 |
|
46 |
# 创建必要目录
|
@@ -54,7 +56,6 @@ def get_china_time():
|
|
54 |
return datetime.now(china_tz)
|
55 |
|
56 |
|
57 |
-
# -------------------------- 新增:业务日判断函数(核心修改) --------------------------
|
58 |
def get_business_info():
|
59 |
"""
|
60 |
基于北京时间20点分界,返回当前业务信息
|
@@ -96,11 +97,10 @@ def load_local_model():
|
|
96 |
return predictor
|
97 |
|
98 |
|
99 |
-
# -------------------------- 修改:数据获取日期(基于业务日) --------------------------
|
100 |
def fetch_stock_data():
|
101 |
"""获取股票数据(基于业务日更新,中国时间),添加数据获取日志"""
|
102 |
china_now = get_china_time()
|
103 |
-
current_business_date, _ = get_business_info() #
|
104 |
end_date = current_business_date.strftime("%Y-%m-%d")
|
105 |
need_points = Config["VOL_WINDOW"] + Config["VOL_WINDOW"] # 历史数据+波动率计算窗口
|
106 |
|
@@ -184,7 +184,7 @@ def make_prediction(df, predictor):
|
|
184 |
infer_time = time.time() - begin_time
|
185 |
print(f"[{get_china_time():%Y-%m-%d %H:%M:%S}] 推理完成,耗时{infer_time:.2f}秒")
|
186 |
|
187 |
-
#
|
188 |
close_preds_volatility = close_preds_main
|
189 |
return close_preds_main, volume_preds_main, close_preds_volatility
|
190 |
|
@@ -222,41 +222,36 @@ def create_plot():
|
|
222 |
china_now = get_china_time()
|
223 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 开始生成预测图表(适配低版本matplotlib字体)")
|
224 |
|
225 |
-
#
|
226 |
hist_df_for_plot = Config["CACHED_RESULTS"]["hist_df_for_plot"]
|
227 |
close_preds = Config["CACHED_RESULTS"]["close_preds"]
|
228 |
volume_preds = Config["CACHED_RESULTS"]["volume_preds"]
|
229 |
|
230 |
-
#
|
231 |
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
|
232 |
-
# -----------------------------------------------------------------------------
|
233 |
|
234 |
-
#
|
235 |
from matplotlib.font_manager import FontProperties
|
236 |
font_path = Config["CHINESE_FONT_PATH"]
|
237 |
|
238 |
# 检查字体文件是否存在
|
239 |
if os.path.exists(font_path):
|
240 |
-
# 直接通过FontProperties指定字体文件路径(兼容低版本matplotlib)
|
241 |
chinese_font = FontProperties(fname=font_path)
|
242 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 成功加载.ttf字体:{font_path}")
|
243 |
else:
|
244 |
-
# 字体文件不存在时的 fallback 逻辑
|
245 |
chinese_font = FontProperties(family='SimHei', size=10)
|
246 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 字体文件不存在,使用系统默认字体:SimHei")
|
247 |
|
248 |
-
#
|
249 |
plt.rcParams["font.family"] = ["sans-serif"]
|
250 |
plt.rcParams["font.sans-serif"] = ["WenQuanYi Micro Hei", "SimHei", "Heiti TC"]
|
251 |
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
|
252 |
-
# -----------------------------------------------------------------------------
|
253 |
|
254 |
-
#
|
255 |
-
# 1. 价格子图
|
256 |
hist_time = hist_df_for_plot['timestamps']
|
257 |
ax1.plot(hist_time, hist_df_for_plot['close'], color='#00274C', linewidth=1.5)
|
258 |
mean_preds = close_preds.mean(axis=1)
|
259 |
-
#
|
260 |
last_hist_time = hist_time.max()
|
261 |
pred_time = pd.date_range(start=last_hist_time + pd.Timedelta(days=1), periods=Config["PRED_HORIZON"], freq='B')
|
262 |
ax1.plot(pred_time, mean_preds, color='#FF6B00', linestyle='-')
|
@@ -266,12 +261,11 @@ def create_plot():
|
|
266 |
ax1.set_title(f'{Config["STOCK_CODE"]} 上证指数概率预测(未来{Config["PRED_HORIZON"]}个交易日)',
|
267 |
fontsize=16, weight='bold', fontproperties=chinese_font)
|
268 |
ax1.set_ylabel('价格(元)', fontsize=12, fontproperties=chinese_font)
|
269 |
-
# 图例指定字体
|
270 |
ax1.legend(['上证指数(后复权)', '预测均价', '预测区间(最小-最大)'],
|
271 |
fontsize=10, prop=chinese_font)
|
272 |
ax1.grid(True, which='both', linestyle='--', linewidth=0.5)
|
273 |
|
274 |
-
#
|
275 |
ax2.bar(hist_time, hist_df_for_plot['volume']/1e8, color='#00A86B', width=0.6)
|
276 |
ax2.bar(pred_time, volume_preds.mean(axis=1)/1e8, color='#FF6B00', width=0.6)
|
277 |
ax2.set_ylabel('成交量(亿手)', fontsize=12, fontproperties=chinese_font)
|
@@ -297,110 +291,6 @@ def create_plot():
|
|
297 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 图表生成完成,保存路径:{chart_path}")
|
298 |
|
299 |
|
300 |
-
def update_html():
|
301 |
-
"""更新HTML页面,复用当前业务日缓存的指标,添加HTML更新日志"""
|
302 |
-
china_now = get_china_time()
|
303 |
-
current_business_date, _ = get_business_info()
|
304 |
-
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 开始更新HTML页面(业务日:{current_business_date})...")
|
305 |
-
|
306 |
-
# 1. 从缓存获取指标(增加空值判断,避免报错)
|
307 |
-
upside_prob = Config["CACHED_RESULTS"].get("upside_prob")
|
308 |
-
vol_amp_prob = Config["CACHED_RESULTS"].get("vol_amp_prob")
|
309 |
-
|
310 |
-
# 处理缓存为空的情况
|
311 |
-
if upside_prob is None or vol_amp_prob is None:
|
312 |
-
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 警告:缓存中未找到指标数据,无法更新HTML")
|
313 |
-
return
|
314 |
-
|
315 |
-
# 格式化指标(保留1位小数百分比)
|
316 |
-
upside_prob_str = f'{upside_prob:.1%}'
|
317 |
-
vol_amp_prob_str = f'{vol_amp_prob:.1%}'
|
318 |
-
now_cn_str = china_now.strftime('%Y-%m-%d %H:%M:%S')
|
319 |
-
|
320 |
-
# 2. 初始化HTML(不存在则创建基础模板)
|
321 |
-
html_path = Path(Config["HTML_PATH"])
|
322 |
-
src_html_path = Config["REPO_PATH"] / "templates" / "index.html"
|
323 |
-
|
324 |
-
if not html_path.exists():
|
325 |
-
html_path.parent.mkdir(parents=True, exist_ok=True)
|
326 |
-
if src_html_path.exists():
|
327 |
-
# 复制项目模板
|
328 |
-
import shutil
|
329 |
-
shutil.copy2(src_html_path, html_path)
|
330 |
-
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 从项目模板复制HTML:{src_html_path} -> {html_path}")
|
331 |
-
else:
|
332 |
-
# 创建基础中文HTML(确保指标对应的id与正则匹配)
|
333 |
-
base_html = """
|
334 |
-
<!DOCTYPE html>
|
335 |
-
<html>
|
336 |
-
<head>
|
337 |
-
<title>清华大模型Kronos上证指数预测</title>
|
338 |
-
<style>
|
339 |
-
body { max-width: 1200px; margin: 0 auto; padding: 20px; font-family: "WenQuanYi Micro Hei", Arial; }
|
340 |
-
.metric { margin: 20px 0; padding: 10px; background: #f5f5f5; border-radius: 5px; }
|
341 |
-
.metric-value { font-size: 1.2em; color: #0066cc; }
|
342 |
-
img { max-width: 100%; height: auto; }
|
343 |
-
h1 { color: #333; }
|
344 |
-
</style>
|
345 |
-
</head>
|
346 |
-
<body>
|
347 |
-
<h1>清华大学K线大模型Kronos上证指数(sh.000001)概率预测</h1>
|
348 |
-
<p>最后更新时间(中国时间):<strong id="update-time">未更新</strong></p>
|
349 |
-
<p>同 步 网 站:<strong><a href="http://15115656.top" target="_blank">火狼工具站</a></strong></p>
|
350 |
-
<div class="metric">
|
351 |
-
<p>24个交易日上涨概率:<span class="metric-value" id="upside-prob">--%</span></p>
|
352 |
-
</div>
|
353 |
-
<div class="metric">
|
354 |
-
<p>波动率放大概率:<span class="metric-value" id="vol-amp-prob">--%</span></p>
|
355 |
-
</div>
|
356 |
-
<div><img src="/prediction_chart.png" alt="上证指数预测图表"></div>
|
357 |
-
</body>
|
358 |
-
</html>
|
359 |
-
"""
|
360 |
-
with open(html_path, 'w', encoding='utf-8') as f:
|
361 |
-
f.write(base_html)
|
362 |
-
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 在/tmp创建基础HTML:{html_path}")
|
363 |
-
|
364 |
-
# 3. 读取HTML内容(确保读取成功)
|
365 |
-
try:
|
366 |
-
with open(html_path, 'r', encoding='utf-8') as f:
|
367 |
-
content = f.read()
|
368 |
-
except Exception as e:
|
369 |
-
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 读取HTML失败:{str(e)}")
|
370 |
-
return
|
371 |
-
|
372 |
-
# 4. 正则替换(关键:确保re.sub()参数完整)
|
373 |
-
# 替换更新时间
|
374 |
-
content = re.sub(
|
375 |
-
pattern=r'(<strong id="update-time">).*?(</strong>)',
|
376 |
-
repl=lambda m: f'{m.group(1)}{now_cn_str}{m.group(2)}',
|
377 |
-
string=content
|
378 |
-
)
|
379 |
-
# 替换上涨概率(id="upside-prob",与HTML模板对应)
|
380 |
-
content = re.sub(
|
381 |
-
pattern=r'(<span class="metric-value" id="upside-prob">).*?(</span>)',
|
382 |
-
repl=lambda m: f'{m.group(1)}{upside_prob_str}{m.group(2)}',
|
383 |
-
string=content
|
384 |
-
)
|
385 |
-
# 替换波动率放大概率(id="vol-amp-prob",与HTML模板对应)
|
386 |
-
content = re.sub(
|
387 |
-
pattern=r'(<span class="metric-value" id="vol-amp-prob">).*?(</span>)',
|
388 |
-
repl=lambda m: f'{m.group(1)}{vol_amp_prob_str}{m.group(2)}',
|
389 |
-
string=content
|
390 |
-
)
|
391 |
-
|
392 |
-
# 5. 写入更新后的HTML
|
393 |
-
try:
|
394 |
-
with open(html_path, 'w', encoding='utf-8') as f:
|
395 |
-
f.write(content)
|
396 |
-
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] HTML更新完成,路径:{html_path}")
|
397 |
-
# 验证替换结果(调试用)
|
398 |
-
print(f"[DEBUG] 上涨概率更新为:{upside_prob_str}")
|
399 |
-
print(f"[DEBUG] 波动率概率更新为:{vol_amp_prob_str}")
|
400 |
-
except Exception as e:
|
401 |
-
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 写入HTML失败:{str(e)}")
|
402 |
-
|
403 |
-
|
404 |
def git_commit_and_push():
|
405 |
"""Git提交(仅当Git存在时执行),添加Git操作日志"""
|
406 |
china_now = get_china_time()
|
@@ -418,7 +308,7 @@ def git_commit_and_push():
|
|
418 |
# 执行Git操作
|
419 |
try:
|
420 |
os.chdir(Config["REPO_PATH"])
|
421 |
-
# 复制图表和HTML到Git
|
422 |
chart_src = Config["CHART_PATH"]
|
423 |
chart_dst = Config["REPO_PATH"] / "prediction_chart.png"
|
424 |
html_src = Config["HTML_PATH"]
|
@@ -452,7 +342,6 @@ def git_commit_and_push():
|
|
452 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] Git权限错误:{str(e)},跳过Git操作")
|
453 |
|
454 |
|
455 |
-
# -------------------------- 修改:主任务逻辑(基于业务日判断) --------------------------
|
456 |
def main_task(model):
|
457 |
"""主任务:控制基于20点分界的业务日推理逻辑,同业务日复用缓存"""
|
458 |
china_now = get_china_time()
|
@@ -461,13 +350,11 @@ def main_task(model):
|
|
461 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 开始执行主任务")
|
462 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 当前业务日:{current_business_date}(北京时间{'20点后' if is_after_20h else '20点前'})")
|
463 |
|
464 |
-
#
|
465 |
if Config["LAST_INFERENCED_BUSINESS_DATE"] == current_business_date:
|
466 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 当前业务日({current_business_date})已完成推理,直接复用缓存结果")
|
467 |
-
#
|
468 |
create_plot()
|
469 |
-
update_html()
|
470 |
-
git_commit_and_push()
|
471 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 主任务完成(复用缓存)")
|
472 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] " + "="*60 + "\n")
|
473 |
return
|
@@ -491,24 +378,25 @@ def main_task(model):
|
|
491 |
"close_preds": close_preds,
|
492 |
"volume_preds": volume_preds,
|
493 |
"v_close_preds": v_close_preds,
|
494 |
-
"upside_prob": upside_prob,
|
495 |
-
"vol_amp_prob": vol_amp_prob,
|
496 |
"hist_df_for_plot": hist_df_for_plot
|
497 |
}
|
498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
499 |
Config["LAST_INFERENCED_BUSINESS_DATE"] = current_business_date
|
500 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 业务日({current_business_date})推理结果已缓存,同业务日后续调用将复用")
|
501 |
|
502 |
# 5. 生成图表
|
503 |
create_plot()
|
504 |
|
505 |
-
# 6.
|
506 |
-
update_html()
|
507 |
-
|
508 |
-
# 7. Git提交
|
509 |
git_commit_and_push()
|
510 |
|
511 |
-
#
|
512 |
del df_full, df_for_model, hist_df_for_metrics
|
513 |
gc.collect()
|
514 |
|
@@ -524,7 +412,6 @@ def main_task(model):
|
|
524 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] " + "="*60 + "\n")
|
525 |
|
526 |
|
527 |
-
# -------------------------- 修改:定时器逻辑(从0点改为20点触发) --------------------------
|
528 |
def run_scheduler(model):
|
529 |
"""定时器:基于北京时间20点分界触发任务,其他时间5分钟检查一次"""
|
530 |
china_tz = timezone("Asia/Shanghai")
|
@@ -534,22 +421,20 @@ def run_scheduler(model):
|
|
534 |
china_now = get_china_time()
|
535 |
current_business_date, is_after_20h = get_business_info()
|
536 |
|
537 |
-
#
|
538 |
if is_after_20h:
|
539 |
-
# 已过当天20点 → 下次执行时间为次日20点
|
540 |
next_exec_date = (china_now + timedelta(days=1)).date()
|
541 |
else:
|
542 |
-
# 未过当天20点 → 下次执行时间为当天20点
|
543 |
next_exec_date = china_now.date()
|
544 |
|
545 |
-
# 构造下次执行时间(20:00:05,留5
|
546 |
next_exec_time = datetime.combine(
|
547 |
next_exec_date,
|
548 |
datetime.strptime("20:00:05", "%H:%M:%S").time(),
|
549 |
tzinfo=china_tz
|
550 |
)
|
551 |
|
552 |
-
# 计算等待时间(秒),最小等待5
|
553 |
sleep_seconds = (next_exec_time - china_now).total_seconds()
|
554 |
sleep_seconds = max(sleep_seconds, 300)
|
555 |
|
@@ -566,7 +451,6 @@ def run_scheduler(model):
|
|
566 |
# 到达执行时间,触发主任务
|
567 |
try:
|
568 |
main_task(model)
|
569 |
-
# 无需重置业务日标记(下次判断基于新业务日)
|
570 |
except Exception as e:
|
571 |
print(f"[{get_china_time():%Y-%m-%d %H:%M:%S}] 定时器触发任务失败:{str(e)}")
|
572 |
import traceback
|
@@ -587,4 +471,5 @@ if __name__ == '__main__':
|
|
587 |
main_task(loaded_model)
|
588 |
|
589 |
# 启动定时器(中国时间每天20点执行)
|
590 |
-
run_scheduler(loaded_model)
|
|
|
|
28 |
"PREDICTION_CACHE": os.path.join("/tmp", "predictions_cache"),
|
29 |
"CHART_PATH": os.path.join("/tmp", "prediction_chart.png"),
|
30 |
"HTML_PATH": os.path.join("/tmp", "index.html"),
|
31 |
+
# 核心配置:记录最后推理业务日
|
32 |
"LAST_INFERENCED_BUSINESS_DATE": None,
|
33 |
+
# 新增:供Flask读取的预测指标
|
34 |
+
"upside_prob": None, # 上涨概率
|
35 |
+
"vol_amp_prob": None, # 波动率放大概率
|
36 |
+
"update_time": None, # 最后更新时间
|
37 |
"CACHED_RESULTS": {
|
38 |
"close_preds": None,
|
39 |
"volume_preds": None,
|
40 |
"v_close_preds": None,
|
|
|
|
|
41 |
"hist_df_for_plot": None
|
42 |
}
|
43 |
}
|
44 |
|
45 |
+
# 补充定义中文字体路径
|
46 |
Config["CHINESE_FONT_PATH"] = os.path.join(Config["REPO_PATH"], "fonts", "wqy-microhei.ttf")
|
47 |
|
48 |
# 创建必要目录
|
|
|
56 |
return datetime.now(china_tz)
|
57 |
|
58 |
|
|
|
59 |
def get_business_info():
|
60 |
"""
|
61 |
基于北京时间20点分界,返回当前业务信息
|
|
|
97 |
return predictor
|
98 |
|
99 |
|
|
|
100 |
def fetch_stock_data():
|
101 |
"""获取股票数据(基于业务日更新,中国时间),添加数据获取日志"""
|
102 |
china_now = get_china_time()
|
103 |
+
current_business_date, _ = get_business_info() # 用业务日作为数据结束日期
|
104 |
end_date = current_business_date.strftime("%Y-%m-%d")
|
105 |
need_points = Config["VOL_WINDOW"] + Config["VOL_WINDOW"] # 历史数据+波动率计算窗口
|
106 |
|
|
|
184 |
infer_time = time.time() - begin_time
|
185 |
print(f"[{get_china_time():%Y-%m-%d %H:%M:%S}] 推理完成,耗时{infer_time:.2f}秒")
|
186 |
|
187 |
+
# 波动率预测复用收盘价预测结果
|
188 |
close_preds_volatility = close_preds_main
|
189 |
return close_preds_main, volume_preds_main, close_preds_volatility
|
190 |
|
|
|
222 |
china_now = get_china_time()
|
223 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 开始生成预测图表(适配低版本matplotlib字体)")
|
224 |
|
225 |
+
# 从缓存获取数据
|
226 |
hist_df_for_plot = Config["CACHED_RESULTS"]["hist_df_for_plot"]
|
227 |
close_preds = Config["CACHED_RESULTS"]["close_preds"]
|
228 |
volume_preds = Config["CACHED_RESULTS"]["volume_preds"]
|
229 |
|
230 |
+
# 创建画布和子图
|
231 |
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
|
|
|
232 |
|
233 |
+
# 低版本matplotlib字体处理
|
234 |
from matplotlib.font_manager import FontProperties
|
235 |
font_path = Config["CHINESE_FONT_PATH"]
|
236 |
|
237 |
# 检查字体文件是否存在
|
238 |
if os.path.exists(font_path):
|
|
|
239 |
chinese_font = FontProperties(fname=font_path)
|
240 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 成功加载.ttf字体:{font_path}")
|
241 |
else:
|
|
|
242 |
chinese_font = FontProperties(family='SimHei', size=10)
|
243 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 字体文件不存在,使用系统默认字体:SimHei")
|
244 |
|
245 |
+
# 全局设置字体
|
246 |
plt.rcParams["font.family"] = ["sans-serif"]
|
247 |
plt.rcParams["font.sans-serif"] = ["WenQuanYi Micro Hei", "SimHei", "Heiti TC"]
|
248 |
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
|
|
|
249 |
|
250 |
+
# 价格子图
|
|
|
251 |
hist_time = hist_df_for_plot['timestamps']
|
252 |
ax1.plot(hist_time, hist_df_for_plot['close'], color='#00274C', linewidth=1.5)
|
253 |
mean_preds = close_preds.mean(axis=1)
|
254 |
+
# 生成预测时间序列
|
255 |
last_hist_time = hist_time.max()
|
256 |
pred_time = pd.date_range(start=last_hist_time + pd.Timedelta(days=1), periods=Config["PRED_HORIZON"], freq='B')
|
257 |
ax1.plot(pred_time, mean_preds, color='#FF6B00', linestyle='-')
|
|
|
261 |
ax1.set_title(f'{Config["STOCK_CODE"]} 上证指数概率预测(未来{Config["PRED_HORIZON"]}个交易日)',
|
262 |
fontsize=16, weight='bold', fontproperties=chinese_font)
|
263 |
ax1.set_ylabel('价格(元)', fontsize=12, fontproperties=chinese_font)
|
|
|
264 |
ax1.legend(['上证指数(后复权)', '预测均价', '预测区间(最小-最大)'],
|
265 |
fontsize=10, prop=chinese_font)
|
266 |
ax1.grid(True, which='both', linestyle='--', linewidth=0.5)
|
267 |
|
268 |
+
# 成交量子图
|
269 |
ax2.bar(hist_time, hist_df_for_plot['volume']/1e8, color='#00A86B', width=0.6)
|
270 |
ax2.bar(pred_time, volume_preds.mean(axis=1)/1e8, color='#FF6B00', width=0.6)
|
271 |
ax2.set_ylabel('成交量(亿手)', fontsize=12, fontproperties=chinese_font)
|
|
|
291 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 图表生成完成,保存路径:{chart_path}")
|
292 |
|
293 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
def git_commit_and_push():
|
295 |
"""Git提交(仅当Git存在时执行),添加Git操作日志"""
|
296 |
china_now = get_china_time()
|
|
|
308 |
# 执行Git操作
|
309 |
try:
|
310 |
os.chdir(Config["REPO_PATH"])
|
311 |
+
# 复制图表和HTML到Git跟踪目录
|
312 |
chart_src = Config["CHART_PATH"]
|
313 |
chart_dst = Config["REPO_PATH"] / "prediction_chart.png"
|
314 |
html_src = Config["HTML_PATH"]
|
|
|
342 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] Git权限错误:{str(e)},跳过Git操作")
|
343 |
|
344 |
|
|
|
345 |
def main_task(model):
|
346 |
"""主任务:控制基于20点分界的业务日推理逻辑,同业务日复用缓存"""
|
347 |
china_now = get_china_time()
|
|
|
350 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 开始执行主任务")
|
351 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 当前业务日:{current_business_date}(北京时间{'20点后' if is_after_20h else '20点前'})")
|
352 |
|
353 |
+
# 判断当前业务日是否已推理
|
354 |
if Config["LAST_INFERENCED_BUSINESS_DATE"] == current_business_date:
|
355 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 当前业务日({current_business_date})已完成推理,直接复用缓存结果")
|
356 |
+
# 复用缓存生成图表
|
357 |
create_plot()
|
|
|
|
|
358 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 主任务完成(复用缓存)")
|
359 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] " + "="*60 + "\n")
|
360 |
return
|
|
|
378 |
"close_preds": close_preds,
|
379 |
"volume_preds": volume_preds,
|
380 |
"v_close_preds": v_close_preds,
|
|
|
|
|
381 |
"hist_df_for_plot": hist_df_for_plot
|
382 |
}
|
383 |
+
|
384 |
+
# 核心修改:将指标存入Config供Flask读取
|
385 |
+
Config["upside_prob"] = round(upside_prob * 100, 1) # 转换为百分比并保留1位小数
|
386 |
+
Config["vol_amp_prob"] = round(vol_amp_prob * 100, 1)
|
387 |
+
Config["update_time"] = china_now.strftime('%Y-%m-%d %H:%M:%S')
|
388 |
+
|
389 |
+
# 标记当前业务日已推理
|
390 |
Config["LAST_INFERENCED_BUSINESS_DATE"] = current_business_date
|
391 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] 业务日({current_business_date})推理结果已缓存,同业务日后续调用将复用")
|
392 |
|
393 |
# 5. 生成图表
|
394 |
create_plot()
|
395 |
|
396 |
+
# 6. Git提交
|
|
|
|
|
|
|
397 |
git_commit_and_push()
|
398 |
|
399 |
+
# 7. 内存回收
|
400 |
del df_full, df_for_model, hist_df_for_metrics
|
401 |
gc.collect()
|
402 |
|
|
|
412 |
print(f"[{china_now:%Y-%m-%d %H:%M:%S}] " + "="*60 + "\n")
|
413 |
|
414 |
|
|
|
415 |
def run_scheduler(model):
|
416 |
"""定时器:基于北京时间20点分界触发任务,其他时间5分钟检查一次"""
|
417 |
china_tz = timezone("Asia/Shanghai")
|
|
|
421 |
china_now = get_china_time()
|
422 |
current_business_date, is_after_20h = get_business_info()
|
423 |
|
424 |
+
# 计算下次执行时间(20点触发)
|
425 |
if is_after_20h:
|
|
|
426 |
next_exec_date = (china_now + timedelta(days=1)).date()
|
427 |
else:
|
|
|
428 |
next_exec_date = china_now.date()
|
429 |
|
430 |
+
# 构造下次执行时间(20:00:05,留5秒缓冲)
|
431 |
next_exec_time = datetime.combine(
|
432 |
next_exec_date,
|
433 |
datetime.strptime("20:00:05", "%H:%M:%S").time(),
|
434 |
tzinfo=china_tz
|
435 |
)
|
436 |
|
437 |
+
# 计算等待时间(秒),最小等待5分钟
|
438 |
sleep_seconds = (next_exec_time - china_now).total_seconds()
|
439 |
sleep_seconds = max(sleep_seconds, 300)
|
440 |
|
|
|
451 |
# 到达执行时间,触发主任务
|
452 |
try:
|
453 |
main_task(model)
|
|
|
454 |
except Exception as e:
|
455 |
print(f"[{get_china_time():%Y-%m-%d %H:%M:%S}] 定时器触发任务失败:{str(e)}")
|
456 |
import traceback
|
|
|
471 |
main_task(loaded_model)
|
472 |
|
473 |
# 启动定时器(中国时间每天20点执行)
|
474 |
+
run_scheduler(loaded_model)
|
475 |
+
|