增加选股策略

This commit is contained in:
oficcejo
2025-12-12 19:46:38 +08:00
parent 595278100f
commit ad5865d3b4
25 changed files with 4818 additions and 20 deletions
+293
View File
@@ -0,0 +1,293 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
低价擒牛策略监控UI模块
"""
import streamlit as st
import pandas as pd
from datetime import datetime
from low_price_bull_monitor import low_price_bull_monitor
from low_price_bull_service import low_price_bull_service
def display_monitor_panel():
"""显示策略监控面板"""
st.markdown("## 📊 策略监控中心")
st.markdown("---")
# 服务状态
status = low_price_bull_service.get_status()
col1, col2, col3, col4 = st.columns(4)
with col1:
service_status = "🟢 运行中" if status['running'] else "🔴 已停止"
st.metric("服务状态", service_status)
with col2:
st.metric("监控股票", f"{status['monitored_count']}")
with col3:
st.metric("待处理提醒", f"{status['pending_alerts']}")
with col4:
st.metric("扫描间隔", f"{status['scan_interval']}")
st.markdown("---")
# 服务控制
col_start, col_stop, col_config = st.columns(3)
with col_start:
if st.button("▶️ 启动监控服务", type="primary", disabled=status['running']):
if low_price_bull_service.start():
st.success("✅ 监控服务已启动")
st.rerun()
else:
st.error("❌ 启动失败")
with col_stop:
if st.button("⏸️ 停止监控服务", type="secondary", disabled=not status['running']):
if low_price_bull_service.stop():
st.success("✅ 监控服务已停止")
st.rerun()
else:
st.error("❌ 停止失败")
with col_config:
with st.popover("⚙️ 监控配置"):
st.markdown("**扫描间隔**")
new_interval = st.number_input(
"扫描间隔(秒)",
min_value=10,
max_value=600,
value=status['scan_interval'],
step=10,
label_visibility="collapsed"
)
if st.button("保存配置"):
low_price_bull_service.scan_interval = new_interval
st.success("✅ 配置已保存")
st.markdown("---")
# 标签页
tab1, tab2, tab3 = st.tabs(["📋 监控列表", "🔔 卖出提醒", "📜 历史记录"])
with tab1:
display_monitored_stocks()
with tab2:
display_sell_alerts()
with tab3:
display_alert_history()
def display_monitored_stocks():
"""显示监控中的股票列表"""
stocks = low_price_bull_monitor.get_monitored_stocks()
if not stocks:
st.info("暂无监控中的股票")
return
st.markdown(f"### 📋 监控列表(共{len(stocks)}只)")
# 转换为DataFrame
df = pd.DataFrame(stocks)
# 显示表格
display_df = df[['stock_code', 'stock_name', 'buy_price', 'buy_date', 'holding_days', 'add_time']].copy()
display_df.columns = ['股票代码', '股票名称', '买入价格', '买入日期', '持有天数', '加入时间']
st.dataframe(display_df, width='content', height=400)
# 批量移除
st.markdown("---")
st.markdown("### 🗑️ 批量管理")
selected_codes = st.multiselect(
"选择要移除的股票",
options=[f"{s['stock_code']} {s['stock_name']}" for s in stocks],
format_func=lambda x: x
)
if selected_codes and st.button("🗑️ 移除选中股票", type="secondary"):
for item in selected_codes:
code = item.split()[0]
success, msg = low_price_bull_monitor.remove_stock(code, "手动移除")
if success:
st.success(msg)
else:
st.error(msg)
st.rerun()
def display_sell_alerts():
"""显示待处理的卖出提醒"""
alerts = low_price_bull_monitor.get_pending_alerts()
if not alerts:
st.info("暂无待处理的卖出提醒")
return
st.markdown(f"### 🔔 卖出提醒(共{len(alerts)}条)")
for alert in alerts:
with st.expander(
f"🔔 {alert['stock_code']} {alert['stock_name']} - {alert['alert_reason']}",
expanded=True
):
col1, col2 = st.columns(2)
with col1:
st.markdown("#### 📊 提醒信息")
st.markdown(f"**股票代码**: {alert['stock_code']}")
st.markdown(f"**股票名称**: {alert['stock_name']}")
st.markdown(f"**提醒类型**: {_get_alert_type_name(alert['alert_type'])}")
st.markdown(f"**提醒原因**: {alert['alert_reason']}")
st.markdown(f"**提醒时间**: {alert['alert_time']}")
with col2:
st.markdown("#### 💰 市场数据")
# 处理当前价格(避免bytes类型错误)
current_price = alert.get('current_price')
if current_price is not None:
try:
price_val = float(current_price)
st.markdown(f"**当前价格**: {price_val:.2f}")
except (ValueError, TypeError):
st.markdown(f"**当前价格**: {current_price}")
# 处理MA5和MA20(避免bytes类型错误)
ma5 = alert.get('ma5')
ma20 = alert.get('ma20')
if ma5 is not None and ma20 is not None:
try:
ma5_val = float(ma5)
ma20_val = float(ma20)
st.markdown(f"**MA5**: {ma5_val:.2f}")
st.markdown(f"**MA20**: {ma20_val:.2f}")
st.markdown(f"**均线差**: {(ma5_val - ma20_val):.2f}")
except (ValueError, TypeError):
st.markdown(f"**MA5**: {ma5}")
st.markdown(f"**MA20**: {ma20}")
# 处理持有天数
holding_days = alert.get('holding_days')
if holding_days is not None:
try:
days_val = int(holding_days)
st.markdown(f"**持有天数**: {days_val}")
except (ValueError, TypeError):
st.markdown(f"**持有天数**: {holding_days}")
# 操作按钮
st.markdown("---")
col_action1, col_action2 = st.columns(2)
with col_action1:
if st.button(f"✅ 已处理 - {alert['stock_code']}", key=f"done_{alert['id']}"):
low_price_bull_monitor.mark_alert_sent(alert['id'])
low_price_bull_monitor.remove_stock(alert['stock_code'], "已处理提醒")
st.success(f"✅ 已标记为已处理并移除 {alert['stock_code']}")
st.rerun()
with col_action2:
if st.button(f"❌ 忽略 - {alert['stock_code']}", key=f"ignore_{alert['id']}"):
low_price_bull_monitor.mark_alert_sent(alert['id'])
st.success(f"✅ 已忽略提醒(股票保留在监控列表)")
st.rerun()
def display_alert_history():
"""显示历史提醒记录"""
alerts = low_price_bull_monitor.get_history_alerts(limit=50)
if not alerts:
st.info("暂无历史提醒记录")
return
st.markdown("### 📜 历史提醒记录")
# 转换为DataFrame
df = pd.DataFrame(alerts)
# 选择显示列
display_cols = ['stock_code', 'stock_name', 'alert_type', 'alert_reason', 'current_price', 'holding_days', 'alert_time', 'is_sent']
display_df = df[display_cols].copy()
# 重命名列
display_df.columns = ['股票代码', '股票名称', '提醒类型', '提醒原因', '当前价格', '持有天数', '提醒时间', '已发送']
# 格式化
display_df['提醒类型'] = display_df['提醒类型'].apply(_get_alert_type_name)
display_df['已发送'] = display_df['已发送'].apply(lambda x: '' if x == 1 else '')
st.dataframe(display_df, width='content', height=400)
# 清理按钮
st.markdown("---")
if st.button("🗑️ 清理30天前的记录"):
low_price_bull_monitor.clear_old_alerts(days=30)
st.success("✅ 已清理旧记录")
st.rerun()
def _get_alert_type_name(alert_type: str) -> str:
"""获取提醒类型名称"""
type_map = {
'holding_days': '持股到期',
'ma_cross': 'MA均线死叉'
}
return type_map.get(alert_type, alert_type)
def add_stock_to_monitor_button(stock_code: str, stock_name: str, price: float = None):
"""
添加股票到监控按钮(在股票详情中使用)
Args:
stock_code: 股票代码
stock_name: 股票名称
price: 买入价格
"""
# 检查是否已在监控
stocks = low_price_bull_monitor.get_monitored_stocks()
is_monitored = any(s['stock_code'] == stock_code for s in stocks)
if is_monitored:
st.info(f"{stock_code} 已在监控列表中")
if st.button(f"🗑️ 移出监控 - {stock_code}", key=f"remove_{stock_code}"):
success, msg = low_price_bull_monitor.remove_stock(stock_code, "手动移除")
if success:
st.success(msg)
st.rerun()
else:
st.error(msg)
else:
if st.button(f" 加入策略监控 - {stock_code}", type="primary", key=f"add_{stock_code}"):
if price is None:
price = 0.0 # 如果没有价格,使用0
success, msg = low_price_bull_monitor.add_stock(
stock_code=stock_code,
stock_name=stock_name,
buy_price=price,
buy_date=datetime.now().strftime("%Y-%m-%d")
)
if success:
st.success(msg)
st.info("💡 提示:请在左侧菜单进入'策略监控'查看监控状态")
st.rerun()
else:
st.error(msg)