增加选股策略
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
# TDX API配置快速指南
|
||||
|
||||
## 问题说明
|
||||
|
||||
如果您看到以下警告:
|
||||
|
||||
```
|
||||
WARNING:low_price_bull_service:TDX数据源未配置,无法获取股票数据
|
||||
```
|
||||
|
||||
说明系统无法连接到TDX API服务,需要进行配置。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 步骤1:配置TDX API地址
|
||||
|
||||
在 `.env` 文件中添加以下配置:
|
||||
|
||||
```bash
|
||||
# TDX数据源API配置
|
||||
TDX_BASE_URL=http://127.0.0.1:5000
|
||||
```
|
||||
|
||||
### 步骤2:确保TDX API服务已启动
|
||||
|
||||
TDX API服务需要单独启动,请检查:
|
||||
|
||||
1. **检查服务是否运行**
|
||||
```bash
|
||||
# 在浏览器中访问
|
||||
http://127.0.0.1:5000/api/health
|
||||
|
||||
# 或使用curl命令
|
||||
curl http://127.0.0.1:5000/api/health
|
||||
```
|
||||
|
||||
2. **如果服务未启动**
|
||||
- 请启动您的TDX API服务
|
||||
- 默认端口:5000
|
||||
- 如使用其他端口,请修改.env中的URL
|
||||
|
||||
### 步骤3:测试配置
|
||||
|
||||
运行测试脚本验证配置:
|
||||
|
||||
```bash
|
||||
python test_tdx_api.py
|
||||
```
|
||||
|
||||
**期望输出**:
|
||||
|
||||
```
|
||||
============================================================
|
||||
TDX API配置测试
|
||||
============================================================
|
||||
|
||||
1. TDX API地址: http://127.0.0.1:5000
|
||||
|
||||
2. 测试健康检查接口...
|
||||
✅ 健康检查成功
|
||||
响应: OK
|
||||
|
||||
3. 测试K线数据接口...
|
||||
测试股票: 000001
|
||||
✅ K线数据获取成功
|
||||
数据条数: 250
|
||||
|
||||
最新K线数据:
|
||||
- 日期: 2024-12-12
|
||||
- 开盘: 10.50
|
||||
- 收盘: 10.60
|
||||
- 最高: 10.80
|
||||
- 最低: 10.30
|
||||
- 成交量: 1000000
|
||||
|
||||
✅ 数据量充足,可以计算MA20(需要至少20条)
|
||||
|
||||
4. 测试均线计算...
|
||||
✅ 均线计算成功
|
||||
- 收盘价: 10.60
|
||||
- MA5: 10.55
|
||||
- MA20: 10.45
|
||||
- 趋势: 🟢 MA5 > MA20 (多头)
|
||||
|
||||
============================================================
|
||||
✅ 所有测试通过!TDX API配置正常
|
||||
============================================================
|
||||
```
|
||||
|
||||
## TDX API接口说明
|
||||
|
||||
### 核心接口
|
||||
|
||||
| 接口 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| /api/health | 健康检查 | - |
|
||||
| /api/kline | K线数据 | ?code=000001&type=day |
|
||||
| /api/quote | 五档行情 | ?code=000001 |
|
||||
| /api/stock-info | 综合信息 | ?code=000001 |
|
||||
|
||||
### K线数据接口详情
|
||||
|
||||
**URL**: `/api/kline`
|
||||
|
||||
**参数**:
|
||||
- `code`: 股票代码(必填)
|
||||
- 支持格式:
|
||||
- 纯数字:`000001`、`600000`
|
||||
- 市场前缀:`SZ000001`、`SH600000`
|
||||
- 示例:000001(平安银行)
|
||||
- 示例:600000(浦发银行)
|
||||
- `type`: K线类型(必填)
|
||||
- `day`: 日K线
|
||||
- `week`: 周K线
|
||||
- `month`: 月K线
|
||||
|
||||
**代码格式说明**:
|
||||
- 系统会自动处理不同格式的股票代码
|
||||
- 如果使用`SZ`/`SH`前缀失败,会自动重试纯数字代码
|
||||
- 支持带后缀的代码(如`002259.SZ`),系统会自动去除后缀
|
||||
|
||||
**返回格式**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"date": "2024-12-12",
|
||||
"open": 10.50,
|
||||
"high": 10.80,
|
||||
"low": 10.30,
|
||||
"close": 10.60,
|
||||
"volume": 1000000
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
```bash
|
||||
# 获取平安银行日K线
|
||||
curl "http://127.0.0.1:5000/api/kline?code=000001&type=day"
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 连接失败,显示连接超时?
|
||||
|
||||
**原因**:
|
||||
- TDX API服务未启动
|
||||
- 防火墙阻止了连接
|
||||
- 端口被占用
|
||||
|
||||
**解决方案**:
|
||||
1. 检查TDX API服务是否启动
|
||||
2. 检查端口5000是否被占用
|
||||
3. 尝试使用其他端口并修改.env配置
|
||||
|
||||
### Q2: 健康检查失败,HTTP 404?
|
||||
|
||||
**原因**:
|
||||
- TDX API服务版本不支持`/api/health`接口
|
||||
- URL配置错误
|
||||
|
||||
**解决方案**:
|
||||
1. 尝试访问其他接口,如`/api/kline`
|
||||
2. 检查.env中的TDX_BASE_URL是否正确
|
||||
3. 确认服务地址和端口
|
||||
|
||||
### Q3: K线数据获取失败?
|
||||
|
||||
**原因**:
|
||||
- 股票代码不存在
|
||||
- 接口参数错误
|
||||
- 数据源未就绪
|
||||
|
||||
**解决方案**:
|
||||
1. 使用已知存在的股票代码测试(如000001)
|
||||
2. 检查参数格式是否正确
|
||||
3. 查看TDX API服务日志
|
||||
|
||||
### Q4: 数据量不足,无法计算MA20?
|
||||
|
||||
**原因**:
|
||||
- TDX API返回的K线数据少于20条
|
||||
- 新上市股票数据不足
|
||||
|
||||
**解决方案**:
|
||||
1. 这是正常情况,系统会自动跳过
|
||||
2. 等待股票积累更多交易日数据
|
||||
3. 监控服务会在下次扫描时重试
|
||||
|
||||
### Q5: 如何使用远程TDX API服务?
|
||||
|
||||
如果TDX API服务部署在其他服务器上:
|
||||
|
||||
```bash
|
||||
# .env配置
|
||||
TDX_BASE_URL=http://192.168.1.100:5000
|
||||
|
||||
# 或使用域名
|
||||
TDX_BASE_URL=http://tdx-api.example.com:5000
|
||||
```
|
||||
|
||||
### Q6: 如何修改扫描间隔?
|
||||
|
||||
在.env文件中修改:
|
||||
|
||||
```bash
|
||||
# 扫描间隔(秒)
|
||||
LOW_PRICE_BULL_SCAN_INTERVAL=60 # 默认60秒,可改为30-300秒
|
||||
```
|
||||
|
||||
或在监控面板的"⚙️ 监控配置"中动态修改。
|
||||
|
||||
## 配置检查清单
|
||||
|
||||
- [ ] ✅ .env文件中已配置TDX_BASE_URL
|
||||
- [ ] ✅ TDX API服务已启动
|
||||
- [ ] ✅ 可以访问http://127.0.0.1:5000/api/health
|
||||
- [ ] ✅ 运行test_tdx_api.py测试通过
|
||||
- [ ] ✅ 在监控面板启动监控服务
|
||||
- [ ] ✅ 配置了Webhook通知(可选)
|
||||
|
||||
## 下一步
|
||||
|
||||
配置完成后:
|
||||
|
||||
1. 进入"低价擒牛"选股板块
|
||||
2. 执行选股,查看结果
|
||||
3. 点击"➕ 加入策略监控"
|
||||
4. 进入"📊 策略监控"面板
|
||||
5. 点击"▶️ 启动监控服务"
|
||||
6. 监控服务将每60秒扫描一次
|
||||
|
||||
## 需要帮助?
|
||||
|
||||
如果仍然遇到问题,请:
|
||||
|
||||
1. 查看控制台错误日志
|
||||
2. 运行`python test_tdx_api.py`获取详细错误信息
|
||||
3. 检查TDX API服务日志
|
||||
4. 确认网络连接正常
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**更新日期**: 2024-12-12
|
||||
@@ -0,0 +1,416 @@
|
||||
'''
|
||||
三重滤网交易系统 V2.0
|
||||
by Alexander Elder (优化版)
|
||||
|
||||
核心改进:
|
||||
1. 第一重滤网:月线MACD金叉/多头(更稳定的趋势判断)
|
||||
2. 第二重滤网:日线RSI/KDJ超卖回调(放宽条件)
|
||||
3. 第三重滤网:价格企稳或突破(更灵活的入场)
|
||||
'''
|
||||
|
||||
import jqdata
|
||||
import numpy as np
|
||||
|
||||
## 初始化函数
|
||||
def initialize(context):
|
||||
set_benchmark('000300.XSHG')
|
||||
set_option('use_real_price', True)
|
||||
set_option('order_volume_ratio', 1)
|
||||
set_order_cost(OrderCost(open_tax=0, close_tax=0.001,
|
||||
open_commission=0.0003, close_commission=0.0003,
|
||||
close_today_commission=0, min_commission=5), type='stock')
|
||||
|
||||
# ========== 策略参数 ==========
|
||||
g.stocknum = 10 # 增加持仓数
|
||||
g.max_position_ratio = 0.98
|
||||
|
||||
# 第一重滤网参数(月线MACD)
|
||||
g.macd_fast = 12
|
||||
g.macd_slow = 26
|
||||
g.macd_signal = 9
|
||||
|
||||
# 第二重滤网参数(日线震荡 - 放宽)
|
||||
g.rsi_period = 14
|
||||
g.rsi_oversold = 50 # 放宽到50
|
||||
g.rsi_low = 35 # 极度超卖
|
||||
g.kdj_oversold = 40 # 放宽到40
|
||||
|
||||
# 第三重滤网参数(入场确认 - 放宽)
|
||||
g.ma_short = 5 # 5日均线
|
||||
g.ma_mid = 20 # 20日均线
|
||||
|
||||
# 止盈止损参数
|
||||
g.stop_loss = 0.06
|
||||
g.take_profit = 0.20
|
||||
g.trailing_stop = 0.08
|
||||
|
||||
# 市值筛选(亿)
|
||||
g.min_market_cap = 20
|
||||
g.max_market_cap = 1500
|
||||
|
||||
# 记录
|
||||
g.highest_profit = {}
|
||||
g.hold_days = {}
|
||||
|
||||
# 定时任务
|
||||
run_daily(morning_screen, '09:35')
|
||||
run_daily(check_positions, '14:00')
|
||||
run_daily(afternoon_trade, '14:50')
|
||||
|
||||
## 早盘筛选
|
||||
def morning_screen(context):
|
||||
g.buy_list = triple_screen_filter(context)
|
||||
log.info("【三重滤网】筛选出 %d 只股票" % len(g.buy_list))
|
||||
|
||||
## 三重滤网筛选
|
||||
def triple_screen_filter(context):
|
||||
# 基础选股
|
||||
q = query(
|
||||
valuation.code,
|
||||
valuation.market_cap
|
||||
).filter(
|
||||
valuation.market_cap.between(g.min_market_cap, g.max_market_cap)
|
||||
).order_by(
|
||||
valuation.market_cap.asc()
|
||||
).limit(500)
|
||||
|
||||
df = get_fundamentals(q)
|
||||
if df.empty:
|
||||
return []
|
||||
|
||||
stock_list = list(df['code'])
|
||||
stock_list = filter_basic(stock_list)
|
||||
|
||||
# 三重滤网筛选
|
||||
candidates = []
|
||||
for stock in stock_list[:150]:
|
||||
result = check_triple_screen(stock)
|
||||
if result['pass']:
|
||||
candidates.append((stock, result['score']))
|
||||
|
||||
# 按评分排序
|
||||
candidates.sort(key=lambda x: x[1], reverse=True)
|
||||
return [c[0] for c in candidates[:g.stocknum * 3]]
|
||||
|
||||
## 检查三重滤网信号
|
||||
def check_triple_screen(stock):
|
||||
"""
|
||||
三重滤网检查 V2.0:
|
||||
1. 第一重:月线MACD金叉/多头
|
||||
2. 第二重:日线RSI/KDJ回调
|
||||
3. 第三重:价格企稳或突破
|
||||
"""
|
||||
result = {'pass': False, 'score': 0}
|
||||
|
||||
try:
|
||||
# 获取更多日线数据(计算月线MACD需要约130天)
|
||||
df = attribute_history(stock, 150, '1d', ['close', 'high', 'low'], skip_paused=True)
|
||||
if len(df) < 130:
|
||||
return result
|
||||
|
||||
close = df['close'].values
|
||||
high = df['high'].values
|
||||
low = df['low'].values
|
||||
|
||||
# ========== 第一重滤网:月线MACD ==========
|
||||
# 模拟月线:每20天取收盘价(或用月末价格)
|
||||
monthly_close = []
|
||||
for i in range(19, len(close), 20):
|
||||
monthly_close.append(close[i])
|
||||
|
||||
if len(monthly_close) < 6:
|
||||
# 数据不足,改用日线MACD判断大趋势
|
||||
dif, dea, macd = calculate_macd(close)
|
||||
if dif[-1] <= dea[-1]: # MACD死叉
|
||||
return result
|
||||
if dif[-1] <= 0: # DIF在零轴下方
|
||||
return result
|
||||
else:
|
||||
# 计算月线MACD
|
||||
m_dif, m_dea, m_macd = calculate_macd(np.array(monthly_close))
|
||||
|
||||
# 月线MACD条件(放宽):
|
||||
# 1. MACD金叉(DIF上穿DEA)或
|
||||
# 2. MACD柱由绿变红 或
|
||||
# 3. DIF > 0 且 DIF > DEA(多头)
|
||||
macd_golden = m_dif[-1] > m_dea[-1] and m_dif[-2] <= m_dea[-2]
|
||||
macd_turn_red = m_macd[-1] > 0 and m_macd[-2] <= 0
|
||||
macd_bullish = m_dif[-1] > 0 and m_dif[-1] > m_dea[-1]
|
||||
|
||||
if not (macd_golden or macd_turn_red or macd_bullish):
|
||||
return result
|
||||
|
||||
result['score'] += 30 # 趋势分
|
||||
|
||||
# ========== 第二重滤网:日线震荡指标 ==========
|
||||
rsi = calculate_rsi(close, g.rsi_period)
|
||||
k, d, j = calculate_kdj(high, low, close)
|
||||
|
||||
# RSI条件(放宽):
|
||||
# 1. RSI < 50(相对低位)或
|
||||
# 2. RSI从低位回升
|
||||
rsi_current = rsi[-1] if len(rsi) > 0 else 50
|
||||
rsi_prev = rsi[-2] if len(rsi) > 1 else 50
|
||||
|
||||
rsi_signal = (rsi_current < g.rsi_oversold) or \
|
||||
(rsi_prev < g.rsi_low and rsi_current > rsi_prev) or \
|
||||
(rsi_current < 60 and rsi_current > rsi_prev)
|
||||
|
||||
# KDJ条件(放宽):
|
||||
# 1. K < D 后金叉 或
|
||||
# 2. J < 40 或
|
||||
# 3. K/D都在低位回升
|
||||
kdj_golden = k[-1] > d[-1] and k[-2] <= d[-2]
|
||||
kdj_oversold = j[-1] < g.kdj_oversold or k[-1] < g.kdj_oversold
|
||||
kdj_rising = k[-1] > k[-2] and d[-1] > d[-2] and k[-1] < 60
|
||||
|
||||
kdj_signal = kdj_golden or kdj_oversold or kdj_rising
|
||||
|
||||
if not (rsi_signal or kdj_signal):
|
||||
return result
|
||||
|
||||
result['score'] += 30
|
||||
|
||||
# ========== 第三重滤网:入场确认(放宽)==========
|
||||
current_price = close[-1]
|
||||
ma5 = np.mean(close[-g.ma_short:])
|
||||
ma20 = np.mean(close[-g.ma_mid:])
|
||||
|
||||
# 入场条件(满足任一):
|
||||
# 1. 价格站上5日均线
|
||||
# 2. 价格接近20日均线(±3%)
|
||||
# 3. 价格突破近5日高点
|
||||
recent_high = np.max(high[-6:-1])
|
||||
|
||||
price_above_ma5 = current_price > ma5
|
||||
price_near_ma20 = abs(current_price - ma20) / ma20 < 0.03
|
||||
price_breakout = current_price > recent_high * 0.99
|
||||
|
||||
if not (price_above_ma5 or price_near_ma20 or price_breakout):
|
||||
return result
|
||||
|
||||
result['score'] += 40
|
||||
|
||||
# 额外加分
|
||||
if rsi_current < 35:
|
||||
result['score'] += 15
|
||||
if kdj_golden:
|
||||
result['score'] += 10
|
||||
if current_price > ma5 > ma20:
|
||||
result['score'] += 10
|
||||
|
||||
result['pass'] = True
|
||||
return result
|
||||
|
||||
except:
|
||||
return result
|
||||
|
||||
## 计算MACD
|
||||
def calculate_macd(close, fast=12, slow=26, signal=9):
|
||||
ema_fast = calculate_ema(close, fast)
|
||||
ema_slow = calculate_ema(close, slow)
|
||||
dif = ema_fast - ema_slow
|
||||
dea = calculate_ema(dif, signal)
|
||||
macd = (dif - dea) * 2
|
||||
return dif, dea, macd
|
||||
|
||||
## 计算EMA
|
||||
def calculate_ema(data, period):
|
||||
ema = np.zeros(len(data))
|
||||
ema[0] = data[0]
|
||||
multiplier = 2 / (period + 1)
|
||||
for i in range(1, len(data)):
|
||||
ema[i] = (data[i] - ema[i-1]) * multiplier + ema[i-1]
|
||||
return ema
|
||||
|
||||
## 计算RSI
|
||||
def calculate_rsi(close, period=14):
|
||||
delta = np.diff(close)
|
||||
gain = np.where(delta > 0, delta, 0)
|
||||
loss = np.where(delta < 0, -delta, 0)
|
||||
|
||||
avg_gain = np.zeros(len(delta))
|
||||
avg_loss = np.zeros(len(delta))
|
||||
|
||||
avg_gain[period-1] = np.mean(gain[:period])
|
||||
avg_loss[period-1] = np.mean(loss[:period])
|
||||
|
||||
for i in range(period, len(delta)):
|
||||
avg_gain[i] = (avg_gain[i-1] * (period-1) + gain[i]) / period
|
||||
avg_loss[i] = (avg_loss[i-1] * (period-1) + loss[i]) / period
|
||||
|
||||
rs = avg_gain / (avg_loss + 1e-10)
|
||||
rsi = 100 - 100 / (1 + rs)
|
||||
return rsi
|
||||
|
||||
## 计算KDJ
|
||||
def calculate_kdj(high, low, close, n=9, m1=3, m2=3):
|
||||
length = len(close)
|
||||
rsv = np.zeros(length)
|
||||
k = np.zeros(length)
|
||||
d = np.zeros(length)
|
||||
j = np.zeros(length)
|
||||
|
||||
for i in range(n-1, length):
|
||||
hn = np.max(high[i-n+1:i+1])
|
||||
ln = np.min(low[i-n+1:i+1])
|
||||
rsv[i] = (close[i] - ln) / (hn - ln + 1e-10) * 100
|
||||
|
||||
k[n-1] = 50
|
||||
d[n-1] = 50
|
||||
|
||||
for i in range(n, length):
|
||||
k[i] = (m1-1)/m1 * k[i-1] + 1/m1 * rsv[i]
|
||||
d[i] = (m2-1)/m2 * d[i-1] + 1/m2 * k[i]
|
||||
j[i] = 3 * k[i] - 2 * d[i]
|
||||
|
||||
return k, d, j
|
||||
|
||||
## 检查持仓
|
||||
def check_positions(context):
|
||||
if len(context.portfolio.positions) == 0:
|
||||
return
|
||||
|
||||
for stock in list(context.portfolio.positions.keys()):
|
||||
position = context.portfolio.positions[stock]
|
||||
if position.closeable_amount <= 0:
|
||||
continue
|
||||
|
||||
cost = position.avg_cost
|
||||
current_price = position.price
|
||||
if cost <= 0:
|
||||
continue
|
||||
|
||||
profit_ratio = (current_price - cost) / cost
|
||||
g.hold_days[stock] = g.hold_days.get(stock, 0) + 1
|
||||
|
||||
if stock not in g.highest_profit:
|
||||
g.highest_profit[stock] = profit_ratio
|
||||
else:
|
||||
g.highest_profit[stock] = max(g.highest_profit[stock], profit_ratio)
|
||||
|
||||
highest = g.highest_profit[stock]
|
||||
|
||||
# === 止损 ===
|
||||
if profit_ratio < -g.stop_loss:
|
||||
log.info("【止损】%s 亏损 %.2f%%" % (stock, profit_ratio * 100))
|
||||
order_target(stock, 0)
|
||||
clean_stock_data(stock)
|
||||
continue
|
||||
|
||||
# === 趋势反转卖出 ===
|
||||
if check_trend_reversal(stock):
|
||||
log.info("【趋势反转】%s 周线趋势转弱" % stock)
|
||||
order_target(stock, 0)
|
||||
clean_stock_data(stock)
|
||||
continue
|
||||
|
||||
# === 移动止盈 ===
|
||||
if highest >= g.take_profit:
|
||||
drawdown = highest - profit_ratio
|
||||
allowed_drawdown = min(g.trailing_stop + highest * 0.4, 0.18)
|
||||
if drawdown >= allowed_drawdown:
|
||||
log.info("【移动止盈】%s 最高%.1f%% 回撤%.1f%%" %
|
||||
(stock, highest*100, drawdown*100))
|
||||
order_target(stock, 0)
|
||||
clean_stock_data(stock)
|
||||
continue
|
||||
|
||||
# === 分批止盈 ===
|
||||
if profit_ratio >= g.take_profit * 2:
|
||||
sell_amount = int(position.closeable_amount * 0.3 / 100) * 100
|
||||
if sell_amount >= 100:
|
||||
log.info("【止盈】%s +%.1f%%" % (stock, profit_ratio*100))
|
||||
order(stock, -sell_amount)
|
||||
|
||||
## 检查趋势反转(使用MACD)
|
||||
def check_trend_reversal(stock):
|
||||
try:
|
||||
df = attribute_history(stock, 60, '1d', ['close'], skip_paused=True)
|
||||
if len(df) < 50:
|
||||
return False
|
||||
|
||||
close = df['close'].values
|
||||
dif, dea, macd = calculate_macd(close)
|
||||
|
||||
# MACD死叉且DIF < 0
|
||||
if dif[-1] < dea[-1] and dif[-1] < 0:
|
||||
return True
|
||||
|
||||
# MACD连续3天下降且为负
|
||||
if macd[-1] < 0 and macd[-2] < 0 and macd[-3] < 0:
|
||||
if macd[-1] < macd[-2] < macd[-3]:
|
||||
return True
|
||||
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
## 清理数据
|
||||
def clean_stock_data(stock):
|
||||
for d in [g.highest_profit, g.hold_days]:
|
||||
if stock in d:
|
||||
del d[stock]
|
||||
|
||||
## 尾盘交易
|
||||
def afternoon_trade(context):
|
||||
buy_stocks(context)
|
||||
|
||||
## 买入函数
|
||||
def buy_stocks(context):
|
||||
if not hasattr(g, 'buy_list') or not g.buy_list:
|
||||
return
|
||||
|
||||
position_count = len(context.portfolio.positions)
|
||||
if position_count >= g.stocknum:
|
||||
return
|
||||
|
||||
available_cash = context.portfolio.available_cash * g.max_position_ratio
|
||||
buy_count = min(g.stocknum - position_count, len(g.buy_list))
|
||||
if buy_count <= 0 or available_cash < 10000:
|
||||
return
|
||||
|
||||
cash_per_stock = available_cash / buy_count
|
||||
|
||||
bought = 0
|
||||
for stock in g.buy_list:
|
||||
if bought >= buy_count:
|
||||
break
|
||||
if stock in context.portfolio.positions:
|
||||
continue
|
||||
|
||||
# 再次确认三重滤网信号
|
||||
result = check_triple_screen(stock)
|
||||
if result['pass']:
|
||||
order_value(stock, cash_per_stock)
|
||||
log.info("【买入】%s 评分:%.0f" % (stock, result['score']))
|
||||
g.highest_profit[stock] = 0
|
||||
g.hold_days[stock] = 0
|
||||
bought += 1
|
||||
|
||||
## 基础过滤
|
||||
def filter_basic(stock_list):
|
||||
if not stock_list:
|
||||
return []
|
||||
|
||||
current_data = get_current_data()
|
||||
filtered = []
|
||||
|
||||
for stock in stock_list:
|
||||
if current_data[stock].paused:
|
||||
continue
|
||||
if current_data[stock].is_st:
|
||||
continue
|
||||
if 'ST' in current_data[stock].name or '*' in current_data[stock].name:
|
||||
continue
|
||||
if stock.startswith('688') or stock.startswith('8') or stock.startswith('4'):
|
||||
continue
|
||||
if current_data[stock].last_price >= current_data[stock].high_limit:
|
||||
continue
|
||||
if current_data[stock].last_price <= current_data[stock].low_limit:
|
||||
continue
|
||||
|
||||
filtered.append(stock)
|
||||
|
||||
return filtered
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# 低价擒牛功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
**低价擒牛**是一个专注于筛选低价高成长股票的选股策略板块,结合量化交易策略,帮助投资者发现潜在的价值洼地。
|
||||
|
||||
## 选股策略
|
||||
|
||||
### 筛选条件
|
||||
|
||||
| 条件 | 说明 |
|
||||
|------|------|
|
||||
| 股价 | < 10元 |
|
||||
| 净利润增长率 | ≥ 100%(净利润同比增长率) |
|
||||
| 市场板块 | 深圳A股 |
|
||||
| 排除项 | ST股票、科创板、创业板 |
|
||||
| 排序方式 | 按成交额由小至大排名 |
|
||||
|
||||
### 策略理念
|
||||
|
||||
1. **低价优势**:股价低于10元,易于散户参与,具有更大的上涨空间
|
||||
2. **高成长性**:净利润增长率≥100%,说明公司业绩高速增长
|
||||
3. **流动性考虑**:优先选择成交额较小的股票,避免过度热门
|
||||
4. **风险控制**:排除ST、科创板、创业板,降低风险
|
||||
|
||||
## 量化交易策略
|
||||
|
||||
### 资金与仓位管理
|
||||
|
||||
| 参数 | 配置 | 说明 |
|
||||
|------|------|------|
|
||||
| 初始资金 | 100万元 | 策略启动资金 |
|
||||
| 持股周期 | 5天 | 单只股票最长持有时间 |
|
||||
| 交易仓位 | 满仓 | 使用所有可用资金 |
|
||||
| 个股最大持仓 | 40% | 单只股票最多占用40%资金 |
|
||||
| 账户最大持股数 | 4只 | 同时最多持有4只股票 |
|
||||
| 单日最大买入数 | 2只 | 每天最多买入2只新股票 |
|
||||
|
||||
### 买卖择时规则
|
||||
|
||||
#### 买入时机
|
||||
- **时间点**:开盘买入
|
||||
- **优先级**:按成交额由小到大排序
|
||||
- **执行条件**:
|
||||
- 未达到最大持股数(4只)
|
||||
- 当日买入数量未达上限(2只)
|
||||
- 有足够的可用资金
|
||||
|
||||
#### 卖出时机
|
||||
满足以下**任一**条件即卖出:
|
||||
1. **技术信号**:MA5日线下穿MA20日线
|
||||
2. **时间到期**:持股满5天
|
||||
|
||||
### 策略优势
|
||||
|
||||
1. ✅ **严格的仓位控制**:避免单一股票风险过大
|
||||
2. ✅ **轮动机制**:卖出后释放资金,继续买入新标的
|
||||
3. ✅ **纪律性强**:明确的买卖规则,避免情绪化交易
|
||||
4. ✅ **风险分散**:最多持有4只股票,分散风险
|
||||
|
||||
## 使用步骤
|
||||
|
||||
### 1. 进入功能
|
||||
在左侧菜单栏点击:**🎯 选股板块** → **🐂 低价擒牛**
|
||||
|
||||
### 2. 设置参数
|
||||
- **筛选数量**:选择展示的股票数量(默认5只)
|
||||
|
||||
### 3. 开始选股
|
||||
点击 **🚀 开始低价擒牛选股** 按钮
|
||||
|
||||
系统将:
|
||||
- 调用问财接口获取符合条件的股票
|
||||
- 按成交额排序
|
||||
- 展示前N只股票
|
||||
|
||||
### 4. 查看结果
|
||||
|
||||
#### 股票列表
|
||||
- 展示每只股票的基本信息
|
||||
- 包含股价、净利润增长率、成交额等关键指标
|
||||
- 可下载完整数据CSV文件
|
||||
|
||||
#### 钉钉通知
|
||||
如果配置了Webhook,系统会自动发送钉钉消息,包含:
|
||||
- 筛选策略说明
|
||||
- 精选股票列表
|
||||
- 关键财务指标
|
||||
|
||||
### 5. 量化交易模拟
|
||||
|
||||
#### 策略模拟
|
||||
点击 **🎮 开始策略模拟** 可以查看:
|
||||
- 模拟买入信号
|
||||
- 当前持仓情况
|
||||
- 账户资金状态
|
||||
|
||||
#### 实盘交易(可选)
|
||||
如需使用MiniQMT进行实盘交易:
|
||||
1. 配置环境变量(详见环境配置)
|
||||
2. 点击 **🔗 连接MiniQMT实盘**
|
||||
3. 系统将自动执行买卖操作
|
||||
|
||||
## 配置说明
|
||||
|
||||
### Webhook通知配置
|
||||
|
||||
在 `.env` 文件中配置:
|
||||
|
||||
```bash
|
||||
# Webhook通知配置
|
||||
WEBHOOK_ENABLED=true
|
||||
WEBHOOK_URL=your_dingtalk_webhook_url
|
||||
WEBHOOK_TYPE=dingtalk
|
||||
WEBHOOK_KEYWORD=aiagents通知
|
||||
```
|
||||
|
||||
### MiniQMT交易配置
|
||||
|
||||
在 `.env` 文件中配置:
|
||||
|
||||
```bash
|
||||
# MiniQMT配置
|
||||
MINIQMT_ENABLED=true
|
||||
MINIQMT_ACCOUNT_ID=your_account_id
|
||||
```
|
||||
|
||||
## 风险提示
|
||||
|
||||
⚠️ **重要提醒**:
|
||||
|
||||
1. **历史业绩不代表未来收益**
|
||||
- 净利润高增长可能不可持续
|
||||
- 低价股波动性较大
|
||||
|
||||
2. **交易成本**
|
||||
- 实际交易存在滑点
|
||||
- 需要考虑手续费和印花税
|
||||
|
||||
3. **市场风险**
|
||||
- 深圳A股市场波动较大
|
||||
- 小盘股流动性风险
|
||||
|
||||
4. **技术风险**
|
||||
- MA均线信号可能滞后
|
||||
- 5天持股周期可能错过更好的卖点
|
||||
|
||||
5. **策略局限**
|
||||
- 本策略为机械化交易策略
|
||||
- 无法应对突发事件(如重大利空)
|
||||
|
||||
## 策略改进建议
|
||||
|
||||
### 可优化方向
|
||||
|
||||
1. **动态仓位管理**
|
||||
- 根据市场情绪调整仓位
|
||||
- 引入风险评分机制
|
||||
|
||||
2. **止损机制**
|
||||
- 添加固定比例止损(如-5%)
|
||||
- 避免单只股票亏损过大
|
||||
|
||||
3. **择时优化**
|
||||
- 结合更多技术指标(MACD、RSI等)
|
||||
- 考虑大盘走势
|
||||
|
||||
4. **基本面过滤**
|
||||
- 添加财务指标筛选
|
||||
- 排除财务造假风险股
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么只选深圳A股?
|
||||
A: 深圳A股集中了大量中小盘股票,符合"低价高成长"的特征。上海主板以大盘蓝筹为主,不太符合本策略定位。
|
||||
|
||||
### Q2: 成交额排序的意义是什么?
|
||||
A: 成交额小的股票往往还未被市场充分发现,具有更大的潜在空间。但也要注意流动性风险。
|
||||
|
||||
### Q3: 持股5天的依据是什么?
|
||||
A: 5天是一个交易周(一周)的时间,可以让股票有足够的时间展现其趋势,同时避免长期持有的风险。
|
||||
|
||||
### Q4: 能否调整策略参数?
|
||||
A: 当前版本参数固定,未来版本将支持自定义参数配置。
|
||||
|
||||
### Q5: 模拟交易和实盘有什么区别?
|
||||
A: 模拟交易不考虑滑点、手续费等实际成本,仅供策略验证。实盘交易需要接入MiniQMT,会有实际的资金变动。
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心模块
|
||||
|
||||
1. **low_price_bull_selector.py**
|
||||
- 使用pywencai获取数据
|
||||
- 数据清洗和格式化
|
||||
|
||||
2. **low_price_bull_strategy.py**
|
||||
- 量化交易策略实现
|
||||
- 仓位管理和买卖信号
|
||||
|
||||
3. **low_price_bull_ui.py**
|
||||
- Streamlit界面展示
|
||||
- 用户交互逻辑
|
||||
|
||||
### 数据流程
|
||||
|
||||
```
|
||||
用户输入参数
|
||||
↓
|
||||
调用pywencai接口
|
||||
↓
|
||||
获取符合条件的股票
|
||||
↓
|
||||
按成交额排序
|
||||
↓
|
||||
展示股票列表
|
||||
↓
|
||||
发送钉钉通知(可选)
|
||||
↓
|
||||
执行量化策略(可选)
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0 (2024-12-12)
|
||||
- ✅ 初始版本发布
|
||||
- ✅ 实现基础选股功能
|
||||
- ✅ 集成钉钉通知
|
||||
- ✅ 量化策略模拟
|
||||
- ✅ MiniQMT接口支持
|
||||
|
||||
## 联系与反馈
|
||||
|
||||
如有问题或建议,请通过以下方式反馈:
|
||||
- GitHub Issues
|
||||
- 系统内置反馈功能
|
||||
|
||||
---
|
||||
|
||||
**免责声明**:本功能仅供学习和研究使用,不构成任何投资建议。股市有风险,投资需谨慎。
|
||||
@@ -0,0 +1,260 @@
|
||||
# 低价擒牛选股板块 - 实现总结
|
||||
|
||||
## 实现日期
|
||||
2024-12-12
|
||||
|
||||
## 功能概述
|
||||
在左侧菜单栏"选股板块"下新增"低价擒牛"选股策略,支持低价高成长股票筛选和量化交易模拟。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 选股策略
|
||||
- ✅ 股价 < 10元
|
||||
- ✅ 净利润增长率 ≥ 100%
|
||||
- ✅ 非ST、非科创板、非创业板
|
||||
- ✅ 深圳A股
|
||||
- ✅ 按成交额由小至大排名
|
||||
- ✅ 筛选前5名(可配置3-10只)
|
||||
|
||||
### 2. 钉钉通知
|
||||
- ✅ 读取.env中的Webhook配置
|
||||
- ✅ 自动发送选股结果到钉钉群
|
||||
- ✅ 包含股票代码、价格、净利增长率等信息
|
||||
|
||||
### 3. 量化交易策略
|
||||
- ✅ 初始资金:100万元
|
||||
- ✅ 持股周期:5天
|
||||
- ✅ 仓位控制:满仓
|
||||
- ✅ 个股最大持仓:40%
|
||||
- ✅ 最大持股数:4只
|
||||
- ✅ 单日最大买入:2只
|
||||
- ✅ 买入时机:开盘买入
|
||||
- ✅ 卖出信号:MA5下穿MA20 或 持股满5天
|
||||
|
||||
### 4. 交易模拟
|
||||
- ✅ 支持策略模拟(无需配置)
|
||||
- ✅ 支持MiniQMT实盘对接(需配置)
|
||||
- ✅ 显示买入信号、持仓、账户状态
|
||||
|
||||
## 新增文件
|
||||
|
||||
### Python模块
|
||||
1. **low_price_bull_selector.py** (158行)
|
||||
- 使用pywencai获取符合条件的股票
|
||||
- 数据清洗和格式化
|
||||
- 股票代码列表生成
|
||||
|
||||
2. **low_price_bull_strategy.py** (278行)
|
||||
- 量化交易策略实现
|
||||
- 买卖信号判断
|
||||
- 仓位管理
|
||||
- 交易历史记录
|
||||
|
||||
3. **low_price_bull_ui.py** (382行)
|
||||
- Streamlit界面展示
|
||||
- 用户交互逻辑
|
||||
- 钉钉通知集成
|
||||
- 策略模拟展示
|
||||
|
||||
### 文档
|
||||
1. **docs/低价擒牛功能说明.md** (241行)
|
||||
- 完整功能文档
|
||||
- 策略详解
|
||||
- 配置说明
|
||||
- 风险提示
|
||||
|
||||
2. **docs/低价擒牛快速开始.md** (197行)
|
||||
- 快速上手指南
|
||||
- 典型使用场景
|
||||
- 常见问题解答
|
||||
|
||||
## 修改文件
|
||||
|
||||
### app.py
|
||||
修改位置:第293-380行(菜单导航区域)
|
||||
|
||||
**修改内容**:
|
||||
1. 在"选股板块"下添加"🐂 低价擒牛"按钮
|
||||
2. 更新所有按钮的状态清除列表,增加`show_low_price_bull`
|
||||
3. 添加路由检查,显示低价擒牛界面
|
||||
|
||||
**代码变更**:
|
||||
```python
|
||||
# 添加导航按钮
|
||||
if st.button("🐂 低价擒牛", width='stretch', key="nav_low_price_bull", help="低价高成长股票筛选策略"):
|
||||
st.session_state.show_low_price_bull = True
|
||||
# 清除其他页面标志
|
||||
|
||||
# 添加路由
|
||||
if 'show_low_price_bull' in st.session_state and st.session_state.show_low_price_bull:
|
||||
from low_price_bull_ui import display_low_price_bull
|
||||
display_low_price_bull()
|
||||
return
|
||||
```
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 数据获取
|
||||
- 使用pywencai模块调用问财接口
|
||||
- 查询语句:`"股价<10元,净利润增长率(净利润同比增长率)≥100%,非st,非科创板,非创业板,深圳A股,成交额由小至大排名"`
|
||||
- 智能解析返回结果,兼容DataFrame/dict/list格式
|
||||
|
||||
### 通知集成
|
||||
- 复用notification_service模块
|
||||
- 支持钉钉/飞书Webhook
|
||||
- 自动包含关键词"aiagents通知"
|
||||
- Markdown格式消息展示
|
||||
|
||||
### 策略实现
|
||||
- 基于面向对象设计
|
||||
- 严格的仓位管理
|
||||
- 清晰的买卖信号
|
||||
- 完整的交易记录
|
||||
|
||||
### UI展示
|
||||
- Streamlit expander组件展示详情
|
||||
- DataFrame展示完整数据
|
||||
- Metric组件展示关键指标
|
||||
- CSV下载功能
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
### Webhook通知(可选)
|
||||
```bash
|
||||
WEBHOOK_ENABLED=true
|
||||
WEBHOOK_URL=your_dingtalk_webhook_url
|
||||
WEBHOOK_TYPE=dingtalk
|
||||
WEBHOOK_KEYWORD=aiagents通知
|
||||
```
|
||||
|
||||
### MiniQMT实盘(可选)
|
||||
```bash
|
||||
MINIQMT_ENABLED=true
|
||||
MINIQMT_ACCOUNT_ID=your_account_id
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. **启动系统**
|
||||
```bash
|
||||
streamlit run app.py
|
||||
```
|
||||
|
||||
2. **进入功能**
|
||||
- 左侧菜单 → 🎯 选股板块 → 🐂 低价擒牛
|
||||
|
||||
3. **开始选股**
|
||||
- 调整筛选数量
|
||||
- 点击"🚀 开始低价擒牛选股"
|
||||
|
||||
4. **查看结果**
|
||||
- 股票列表
|
||||
- 统计信息
|
||||
- 钉钉通知(如已配置)
|
||||
|
||||
5. **策略模拟**(可选)
|
||||
- 点击"🎮 开始策略模拟"
|
||||
- 查看买入信号和持仓
|
||||
|
||||
6. **实盘交易**(可选)
|
||||
- 配置MiniQMT
|
||||
- 点击"🔗 连接MiniQMT实盘"
|
||||
|
||||
## 特色亮点
|
||||
|
||||
### 1. 智能筛选
|
||||
- 多维度条件过滤
|
||||
- 成交额排序优先低热度股票
|
||||
- 自动排除风险股(ST、科创板)
|
||||
|
||||
### 2. 完整策略
|
||||
- 清晰的买卖规则
|
||||
- 严格的风险控制
|
||||
- 可复现的交易逻辑
|
||||
|
||||
### 3. 即时通知
|
||||
- 自动发送钉钉消息
|
||||
- 无需人工查看
|
||||
- 移动端即时接收
|
||||
|
||||
### 4. 灵活配置
|
||||
- 筛选数量可调
|
||||
- 策略参数可改
|
||||
- 支持模拟和实盘
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 使用限制
|
||||
1. ⚠️ 问财接口有频率限制,建议每次间隔30秒以上
|
||||
2. ⚠️ 净利润增长率≥100%的股票可能较少
|
||||
3. ⚠️ 策略模拟不包含手续费和滑点
|
||||
|
||||
### 风险提示
|
||||
1. ⚠️ 本功能仅供学习研究使用
|
||||
2. ⚠️ 不构成任何投资建议
|
||||
3. ⚠️ 股市有风险,投资需谨慎
|
||||
4. ⚠️ 历史业绩不代表未来收益
|
||||
|
||||
### 技术限制
|
||||
1. ⚠️ 需要稳定的网络连接
|
||||
2. ⚠️ pywencai模块需要正确安装
|
||||
3. ⚠️ MiniQMT需要额外配置
|
||||
|
||||
## 后续优化方向
|
||||
|
||||
### 短期优化
|
||||
- [ ] 添加历史选股记录
|
||||
- [ ] 支持自定义筛选条件
|
||||
- [ ] 增加更多技术指标
|
||||
|
||||
### 中期优化
|
||||
- [ ] 回测功能
|
||||
- [ ] 策略参数优化
|
||||
- [ ] 多策略对比
|
||||
|
||||
### 长期优化
|
||||
- [ ] AI选股建议
|
||||
- [ ] 实时监控
|
||||
- [ ] 自动化交易
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 功能测试
|
||||
1. ✅ 测试选股功能是否正常
|
||||
2. ✅ 验证数据格式是否正确
|
||||
3. ✅ 检查钉钉通知是否发送
|
||||
4. ✅ 确认策略模拟逻辑
|
||||
|
||||
### 边界测试
|
||||
1. ✅ 筛选不到股票时的处理
|
||||
2. ✅ 网络异常时的错误处理
|
||||
3. ✅ Webhook未配置时的提示
|
||||
|
||||
### 性能测试
|
||||
1. ✅ 数据获取速度
|
||||
2. ✅ UI渲染性能
|
||||
3. ✅ 并发访问支持
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [低价擒牛功能说明](./docs/低价擒牛功能说明.md)
|
||||
- [低价擒牛快速开始](./docs/低价擒牛快速开始.md)
|
||||
- [Webhook通知配置指南](./docs/Webhook通知配置指南.md)
|
||||
- [MiniQMT集成指南](./docs/MINIQMT_INTEGRATION_GUIDE.md)
|
||||
|
||||
## 总结
|
||||
|
||||
本次实现完整地添加了"低价擒牛"选股板块,包含:
|
||||
- ✅ 完整的选股策略
|
||||
- ✅ 钉钉通知集成
|
||||
- ✅ 量化交易策略
|
||||
- ✅ 策略模拟功能
|
||||
- ✅ 详细的文档说明
|
||||
|
||||
所有功能均已集成到现有系统中,与主力选股等其他板块保持一致的交互体验。
|
||||
|
||||
---
|
||||
|
||||
**实现完成时间**:2024-12-12
|
||||
**版本**:v1.0
|
||||
**状态**:✅ 已完成并可使用
|
||||
@@ -0,0 +1,196 @@
|
||||
# 低价擒牛 - 快速开始指南
|
||||
|
||||
## 🚀 5分钟上手
|
||||
|
||||
### 第一步:进入功能
|
||||
|
||||
1. 启动系统:`streamlit run app.py`
|
||||
2. 在左侧菜单栏找到 **🎯 选股板块**
|
||||
3. 点击 **🐂 低价擒牛**
|
||||
|
||||
### 第二步:开始选股
|
||||
|
||||
1. 调整筛选数量(默认5只,可调整3-10只)
|
||||
2. 点击 **🚀 开始低价擒牛选股**
|
||||
3. 等待30-60秒,系统自动获取数据
|
||||
|
||||
### 第三步:查看结果
|
||||
|
||||
系统会展示:
|
||||
- ✅ 筛选数量和统计信息
|
||||
- ✅ 精选股票详细列表
|
||||
- ✅ 完整数据表格(可下载CSV)
|
||||
|
||||
### 第四步:接收通知(可选)
|
||||
|
||||
如果配置了钉钉Webhook,会自动收到消息通知
|
||||
|
||||
## 📊 选股策略一览
|
||||
|
||||
| 条件 | 值 |
|
||||
|------|-----|
|
||||
| 股价 | < 10元 |
|
||||
| 净利润增长率 | ≥ 100% |
|
||||
| 市场 | 深圳A股 |
|
||||
| 排除 | ST、科创板、创业板 |
|
||||
| 排序 | 成交额由小到大 |
|
||||
|
||||
## 🎯 量化策略参数
|
||||
|
||||
| 参数 | 值 |
|
||||
|------|-----|
|
||||
| 初始资金 | 100万元 |
|
||||
| 持股周期 | 5天 |
|
||||
| 最大持股数 | 4只 |
|
||||
| 个股最大仓位 | 40% |
|
||||
| 单日最大买入 | 2只 |
|
||||
| 买入时机 | 开盘 |
|
||||
| 卖出信号 | MA5下穿MA20 或 持股5天 |
|
||||
|
||||
## 🔧 配置Webhook通知(可选)
|
||||
|
||||
### 1. 获取钉钉机器人Webhook URL
|
||||
|
||||
1. 打开钉钉群 → 群设置 → 智能群助手
|
||||
2. 添加机器人 → 自定义
|
||||
3. 添加安全设置 → 自定义关键词:`aiagents通知`
|
||||
4. 复制Webhook URL
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
编辑项目根目录的 `.env` 文件:
|
||||
|
||||
```bash
|
||||
# Webhook通知配置
|
||||
WEBHOOK_ENABLED=true
|
||||
WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token=xxxxx
|
||||
WEBHOOK_TYPE=dingtalk
|
||||
WEBHOOK_KEYWORD=aiagents通知
|
||||
```
|
||||
|
||||
### 3. 重启系统
|
||||
|
||||
配置完成后重启Streamlit即可
|
||||
|
||||
## 🎮 使用量化策略模拟
|
||||
|
||||
### 策略模拟(无需配置)
|
||||
|
||||
1. 完成选股后,在页面底部找到 **量化交易策略模拟**
|
||||
2. 点击 **🎮 开始策略模拟**
|
||||
3. 系统会展示:
|
||||
- 模拟买入信号
|
||||
- 当前持仓情况
|
||||
- 账户资金状态
|
||||
|
||||
### MiniQMT实盘(需要配置)
|
||||
|
||||
**前提条件**:
|
||||
- 已安装MiniQMT客户端
|
||||
- 已开通量化交易账户
|
||||
|
||||
**配置步骤**:
|
||||
1. 编辑 `.env` 文件:
|
||||
```bash
|
||||
MINIQMT_ENABLED=true
|
||||
MINIQMT_ACCOUNT_ID=your_account_id
|
||||
```
|
||||
2. 重启系统
|
||||
3. 点击 **🔗 连接MiniQMT实盘**
|
||||
|
||||
## 📋 典型使用场景
|
||||
|
||||
### 场景1:每日早盘选股
|
||||
|
||||
**时间**:每天早上9:15-9:25
|
||||
|
||||
**操作**:
|
||||
1. 打开低价擒牛功能
|
||||
2. 开始选股,获取最新数据
|
||||
3. 查看筛选结果
|
||||
4. 开盘后根据买入信号下单
|
||||
|
||||
### 场景2:周末策略回测
|
||||
|
||||
**时间**:周末
|
||||
|
||||
**操作**:
|
||||
1. 选股获取数据
|
||||
2. 导出CSV文件
|
||||
3. 使用Excel或Python进行历史数据分析
|
||||
4. 优化策略参数
|
||||
|
||||
### 场景3:自动通知监控
|
||||
|
||||
**时间**:工作日定时
|
||||
|
||||
**操作**:
|
||||
1. 配置Webhook
|
||||
2. 设置定时任务(cron或Task Scheduler)
|
||||
3. 每日自动选股并推送钉钉消息
|
||||
|
||||
## 💡 使用技巧
|
||||
|
||||
### 技巧1:成交额排序的意义
|
||||
- 成交额小 = 市场关注度低 = 可能被低估
|
||||
- 优先买入排名靠前的股票(成交额最小)
|
||||
|
||||
### 技巧2:持股周期管理
|
||||
- 5天是一个交易周期
|
||||
- 可在第3-4天关注卖出信号
|
||||
- 避免持股到期临近时盲目持有
|
||||
|
||||
### 技巧3:仓位控制
|
||||
- 建议新手先用小资金测试
|
||||
- 逐步增加至策略设定的资金量
|
||||
- 严格执行单股40%的仓位限制
|
||||
|
||||
### 技巧4:结合大盘走势
|
||||
- 大盘上涨时:可适当激进
|
||||
- 大盘下跌时:减少买入或观望
|
||||
- 策略不能替代市场判断
|
||||
|
||||
## ⚠️ 常见问题
|
||||
|
||||
### Q: 为什么有时候筛选不到股票?
|
||||
A: 可能原因:
|
||||
- 市场环境不符合条件(如整体估值偏高)
|
||||
- 净利润增长率≥100%的股票较少
|
||||
- 建议适当放宽筛选条件
|
||||
|
||||
### Q: 钉钉通知没收到?
|
||||
A: 检查清单:
|
||||
- ✅ WEBHOOK_ENABLED 设置为 true
|
||||
- ✅ WEBHOOK_URL 正确
|
||||
- ✅ 钉钉机器人关键词包含"aiagents通知"
|
||||
- ✅ 系统已重启
|
||||
|
||||
### Q: 策略模拟和实盘有什么区别?
|
||||
A: 主要区别:
|
||||
- 模拟:不实际下单,无手续费
|
||||
- 实盘:真实下单,有滑点和手续费
|
||||
- 建议先用模拟验证策略
|
||||
|
||||
### Q: 如何调整策略参数?
|
||||
A: 当前版本参数固定在代码中,如需调整:
|
||||
1. 编辑 `low_price_bull_strategy.py`
|
||||
2. 修改 `__init__` 方法中的参数
|
||||
3. 重启系统
|
||||
|
||||
## 📚 进阶阅读
|
||||
|
||||
- [低价擒牛功能说明](./低价擒牛功能说明.md) - 完整功能文档
|
||||
- [Webhook配置指南](./Webhook通知配置指南.md) - 详细配置步骤
|
||||
- [MiniQMT集成指南](./MINIQMT_INTEGRATION_GUIDE.md) - 量化交易配置
|
||||
|
||||
## 🎯 下一步
|
||||
|
||||
1. ✅ 熟悉基础选股功能
|
||||
2. ✅ 配置钉钉通知
|
||||
3. ✅ 使用策略模拟
|
||||
4. ✅ 尝试MiniQMT实盘(谨慎)
|
||||
5. ✅ 根据实际情况优化策略
|
||||
|
||||
---
|
||||
|
||||
**祝您投资顺利!** 🚀📈💰
|
||||
@@ -0,0 +1,365 @@
|
||||
# 低价擒牛策略监控功能实现总结
|
||||
|
||||
## 实现日期
|
||||
2024-12-12
|
||||
|
||||
## 功能概述
|
||||
为"低价擒牛"选股板块添加策略监控功能,实现自动监控持仓股票并在满足卖出条件时发送提醒。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 策略监控
|
||||
- ✅ 加入监控:在选出的股票列表中点击"加入策略监控"按钮
|
||||
- ✅ 自动扫描:每分钟扫描1次(可配置)
|
||||
- ✅ 持股天数监控:持股满5天第6天开盘提醒卖出
|
||||
- ✅ 均线监控:MA5下穿MA20提醒卖出
|
||||
- ✅ 自动移除:提醒卖出后自动移出监控列表
|
||||
|
||||
### 2. 数据源集成
|
||||
- ✅ TDX数据源:读取股票K线数据
|
||||
- ✅ 均线计算:自动计算MA5和MA20
|
||||
- ✅ 实时价格:获取最新股票价格
|
||||
|
||||
### 3. 通知系统
|
||||
- ✅ 钉钉通知:自动发送卖出提醒到钉钉群
|
||||
- ✅ 面板提醒:在监控面板显示待处理提醒
|
||||
- ✅ 历史记录:保存所有提醒记录
|
||||
|
||||
## 新增文件
|
||||
|
||||
### 核心模块(3个Python文件)
|
||||
|
||||
1. **low_price_bull_monitor.py** (363行)
|
||||
- 监控数据库管理
|
||||
- 股票添加/移除
|
||||
- 提醒生成和管理
|
||||
- 持有天数更新
|
||||
|
||||
2. **low_price_bull_service.py** (310行)
|
||||
- 后台监控服务
|
||||
- 定时扫描循环
|
||||
- TDX数据获取
|
||||
- MA均线检测
|
||||
- 钉钉通知发送
|
||||
|
||||
3. **low_price_bull_monitor_ui.py** (273行)
|
||||
- 监控面板UI
|
||||
- 服务控制(启动/停止)
|
||||
- 监控列表展示
|
||||
- 卖出提醒展示
|
||||
- 历史记录查询
|
||||
|
||||
### 文档(2个Markdown文件)
|
||||
|
||||
1. **低价擒牛策略监控配置说明.md**
|
||||
- 环境变量配置
|
||||
- TDX数据源配置
|
||||
- 使用流程说明
|
||||
- 常见问题解答
|
||||
|
||||
2. **低价擒牛策略监控功能实现总结.md**
|
||||
- 功能概述
|
||||
- 技术实现
|
||||
- 使用说明
|
||||
|
||||
## 修改文件
|
||||
|
||||
### low_price_bull_ui.py
|
||||
- ✅ 添加导入语句
|
||||
- ✅ 添加"策略监控"按钮(右上角)
|
||||
- ✅ 在股票详情中添加"加入策略监控"按钮
|
||||
- ✅ 集成监控面板切换
|
||||
- ✅ 更新策略说明
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 表1: monitored_stocks(监控列表)
|
||||
```sql
|
||||
CREATE TABLE monitored_stocks (
|
||||
id INTEGER PRIMARY KEY,
|
||||
stock_code TEXT NOT NULL, -- 股票代码
|
||||
stock_name TEXT NOT NULL, -- 股票名称
|
||||
buy_price REAL NOT NULL, -- 买入价格
|
||||
buy_date TEXT NOT NULL, -- 买入日期
|
||||
holding_days INTEGER DEFAULT 0, -- 持有天数
|
||||
status TEXT DEFAULT 'holding', -- 状态(holding/removed)
|
||||
add_time TEXT NOT NULL, -- 加入时间
|
||||
remove_time TEXT, -- 移除时间
|
||||
remove_reason TEXT, -- 移除原因
|
||||
UNIQUE(stock_code, status)
|
||||
)
|
||||
```
|
||||
|
||||
### 表2: sell_alerts(卖出提醒)
|
||||
```sql
|
||||
CREATE TABLE sell_alerts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
stock_code TEXT NOT NULL, -- 股票代码
|
||||
stock_name TEXT NOT NULL, -- 股票名称
|
||||
alert_type TEXT NOT NULL, -- 提醒类型(holding_days/ma_cross)
|
||||
alert_reason TEXT NOT NULL, -- 提醒原因
|
||||
current_price REAL, -- 当前价格
|
||||
ma5 REAL, -- MA5值
|
||||
ma20 REAL, -- MA20值
|
||||
holding_days INTEGER, -- 持有天数
|
||||
alert_time TEXT NOT NULL, -- 提醒时间
|
||||
is_sent INTEGER DEFAULT 0 -- 是否已发送
|
||||
)
|
||||
```
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 监控服务架构
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ LowPriceBullService │
|
||||
│ (后台监控服务) │
|
||||
└──────────────┬──────────────────────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ 监控循环 │
|
||||
│ (每60秒) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────────▼─────────────┐
|
||||
│ 扫描监控列表 │
|
||||
│ - 更新持有天数 │
|
||||
│ - 检查卖出条件 │
|
||||
└──────────┬──────────────┘
|
||||
│
|
||||
┌──────────▼────────────────┐
|
||||
│ 检测卖出信号 │
|
||||
│ 1. 持股天数≥5 │
|
||||
│ 2. MA5 < MA20 │
|
||||
└──────────┬─────────────────┘
|
||||
│
|
||||
┌──────────▼──────────────┐
|
||||
│ 生成卖出提醒 │
|
||||
│ - 保存到数据库 │
|
||||
│ - 发送钉钉通知 │
|
||||
│ - 自动移除股票 │
|
||||
└───────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. TDX数据获取流程
|
||||
```python
|
||||
def _get_stock_data(stock_code):
|
||||
# 1. 获取K线数据(30天)
|
||||
df = get_stock_kline_data(stock_code, period='daily', count=30)
|
||||
|
||||
# 2. 计算均线
|
||||
df['MA5'] = df['close'].rolling(window=5).mean()
|
||||
df['MA20'] = df['close'].rolling(window=20).mean()
|
||||
|
||||
# 3. 返回最新数据
|
||||
latest = df.iloc[-1]
|
||||
return latest['close'], latest['MA5'], latest['MA20']
|
||||
```
|
||||
|
||||
### 3. 卖出信号检测
|
||||
```python
|
||||
def _check_stock(stock):
|
||||
# 检查1: 持股天数
|
||||
if holding_days >= 5:
|
||||
add_sell_alert(type='holding_days')
|
||||
return
|
||||
|
||||
# 检查2: MA5下穿MA20
|
||||
current_price, ma5, ma20 = _get_stock_data(stock_code)
|
||||
if ma5 < ma20:
|
||||
add_sell_alert(type='ma_cross')
|
||||
```
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
### 新增配置项
|
||||
```bash
|
||||
# 低价擒牛策略监控
|
||||
LOW_PRICE_BULL_SCAN_INTERVAL=60 # 扫描间隔(秒)
|
||||
LOW_PRICE_BULL_HOLDING_DAYS=5 # 持股天数限制
|
||||
```
|
||||
|
||||
### 依赖的配置
|
||||
```bash
|
||||
# Webhook通知(建议配置)
|
||||
WEBHOOK_ENABLED=true
|
||||
WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token=xxxxx
|
||||
WEBHOOK_TYPE=dingtalk
|
||||
WEBHOOK_KEYWORD=aiagents通知
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 1. 启动流程
|
||||
```
|
||||
1. 低价擒牛选股
|
||||
↓
|
||||
2. 查看股票列表
|
||||
↓
|
||||
3. 点击"➕ 加入策略监控"
|
||||
↓
|
||||
4. 点击右上角"📊 策略监控"
|
||||
↓
|
||||
5. 点击"▶️ 启动监控服务"
|
||||
↓
|
||||
6. 监控服务开始运行
|
||||
```
|
||||
|
||||
### 2. 监控流程
|
||||
```
|
||||
监控服务 → 每60秒扫描 → 检测条件 → 生成提醒 → 发送通知 → 自动移除
|
||||
```
|
||||
|
||||
### 3. 提醒流程
|
||||
```
|
||||
满足卖出条件
|
||||
↓
|
||||
生成卖出提醒
|
||||
↓
|
||||
┌─────────┴─────────┐
|
||||
│ │
|
||||
发送钉钉通知 显示在面板
|
||||
│ │
|
||||
└─────────┬─────────┘
|
||||
↓
|
||||
自动移出监控列表
|
||||
↓
|
||||
记录到历史
|
||||
```
|
||||
|
||||
## 功能特点
|
||||
|
||||
### 1. 自动化监控
|
||||
- ✅ 无需人工盯盘
|
||||
- ✅ 定时自动扫描
|
||||
- ✅ 智能卖出提醒
|
||||
|
||||
### 2. 双重条件
|
||||
- ✅ 时间条件:持股5天
|
||||
- ✅ 技术条件:MA5下穿MA20
|
||||
- ✅ 满足任一即提醒
|
||||
|
||||
### 3. 灵活配置
|
||||
- ✅ 可调整扫描间隔
|
||||
- ✅ 可调整持股天数
|
||||
- ✅ 可启动/停止服务
|
||||
|
||||
### 4. 完整记录
|
||||
- ✅ 监控历史
|
||||
- ✅ 提醒记录
|
||||
- ✅ 操作日志
|
||||
|
||||
## 监控面板功能
|
||||
|
||||
### 📋 监控列表
|
||||
- 显示所有监控中的股票
|
||||
- 实时更新持有天数
|
||||
- 支持批量移除
|
||||
- 显示买入价格和日期
|
||||
|
||||
### 🔔 卖出提醒
|
||||
- 显示待处理提醒
|
||||
- 查看MA5/MA20数据
|
||||
- 操作选项:
|
||||
- ✅ 已处理(移除股票)
|
||||
- ❌ 忽略(保留股票)
|
||||
|
||||
### 📜 历史记录
|
||||
- 查看历史提醒
|
||||
- 筛选已发送/未发送
|
||||
- 清理旧记录
|
||||
|
||||
### ⚙️ 服务控制
|
||||
- ▶️ 启动服务
|
||||
- ⏸️ 停止服务
|
||||
- 配置扫描间隔
|
||||
- 查看服务状态
|
||||
|
||||
## 钉钉通知格式
|
||||
|
||||
```markdown
|
||||
### aiagents通知 - 低价擒牛卖出提醒
|
||||
|
||||
**股票代码**: 002259
|
||||
|
||||
**股票名称**: 升达林业
|
||||
|
||||
**提醒类型**: MA均线死叉
|
||||
|
||||
**提醒原因**: MA5下穿MA20,技术信号卖出
|
||||
|
||||
**当前价格**: 8.52元
|
||||
|
||||
**MA5**: 8.45
|
||||
|
||||
**MA20**: 8.67
|
||||
|
||||
**持有天数**: 3天
|
||||
|
||||
**提醒时间**: 2024-12-12 14:30:00
|
||||
|
||||
---
|
||||
|
||||
**建议**: 开盘时卖出该股票
|
||||
|
||||
_此消息由AI股票分析系统自动发送_
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 服务运行
|
||||
- ⚠️ 监控服务需要手动启动
|
||||
- ⚠️ 关闭浏览器后服务停止
|
||||
- ⚠️ 建议在服务器上运行
|
||||
|
||||
### 2. 数据源
|
||||
- ⚠️ 需要配置TDX数据源
|
||||
- ⚠️ 建议使用pytdx(无需配置)
|
||||
- ⚠️ K线数据可能有延迟
|
||||
|
||||
### 3. 扫描频率
|
||||
- ⚠️ 默认60秒扫描1次
|
||||
- ⚠️ 不建议低于30秒
|
||||
- ⚠️ 避免API限流
|
||||
|
||||
### 4. 持股天数
|
||||
- ⚠️ 按自然日计算
|
||||
- ⚠️ 不区分交易日
|
||||
- ⚠️ 建议预留余地
|
||||
|
||||
## 后续优化方向
|
||||
|
||||
### 短期
|
||||
- [ ] 支持后台常驻服务
|
||||
- [ ] 增加更多技术指标
|
||||
- [ ] 支持自定义卖出条件
|
||||
|
||||
### 中期
|
||||
- [ ] 集成MiniQMT自动交易
|
||||
- [ ] 支持止损止盈设置
|
||||
- [ ] 增加回测功能
|
||||
|
||||
### 长期
|
||||
- [ ] AI智能卖出建议
|
||||
- [ ] 多策略组合监控
|
||||
- [ ] 移动端APP推送
|
||||
|
||||
## 总结
|
||||
|
||||
本次实现为"低价擒牛"选股板块添加了完整的策略监控功能,包括:
|
||||
|
||||
- ✅ **3个核心模块**:监控管理、后台服务、UI界面
|
||||
- ✅ **完整的数据库设计**:监控列表、提醒记录
|
||||
- ✅ **自动化监控**:定时扫描、智能提醒、自动移除
|
||||
- ✅ **双重卖出条件**:持股天数 + MA均线
|
||||
- ✅ **钉钉通知集成**:实时接收卖出提醒
|
||||
- ✅ **灵活的配置**:可调整扫描间隔和持股天数
|
||||
- ✅ **完善的文档**:配置说明、使用指南
|
||||
|
||||
整个功能已集成到系统中,用户可以立即使用!
|
||||
|
||||
---
|
||||
|
||||
**实现完成时间**:2024-12-12
|
||||
**版本**:v1.0
|
||||
**状态**:✅ 已完成并可使用
|
||||
@@ -0,0 +1,239 @@
|
||||
# 低价擒牛策略监控配置说明
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
在 `.env` 文件中添加以下配置:
|
||||
|
||||
```bash
|
||||
# 低价擒牛策略监控配置
|
||||
LOW_PRICE_BULL_SCAN_INTERVAL=60 # 扫描间隔(秒),默认60秒
|
||||
LOW_PRICE_BULL_HOLDING_DAYS=5 # 持股天数限制,默认5天
|
||||
|
||||
# TDX数据源API配置(必须)
|
||||
TDX_BASE_URL=http://127.0.0.1:5000 # TDX API服务地址
|
||||
```
|
||||
|
||||
## TDX数据源配置
|
||||
|
||||
策略监控需要使用TDX数据API获取股票K线数据和均线指标。
|
||||
|
||||
### 配置方法
|
||||
|
||||
在 `.env` 文件中添加TDX API URL:
|
||||
|
||||
```bash
|
||||
# TDX数据源API配置
|
||||
TDX_BASE_URL=http://127.0.0.1:5000 # TDX API服务地址
|
||||
```
|
||||
|
||||
### API接口说明
|
||||
|
||||
#### 核心接口
|
||||
|
||||
| 接口 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| /api/quote | 五档行情 | ?code=000001 |
|
||||
| /api/kline | K线数据 | ?code=000001&type=day |
|
||||
| /api/minute | 分时数据 | ?code=000001 |
|
||||
| /api/trade | 分时成交 | ?code=000001 |
|
||||
| /api/search | 搜索股票 | ?keyword=平安 |
|
||||
| /api/stock-info | 综合信息 | ?code=000001 |
|
||||
|
||||
#### K线数据接口
|
||||
|
||||
策略监控使用的接口:**`/api/kline`**
|
||||
|
||||
**请求参数**:
|
||||
- `code`: 股票代码(例如:000001)
|
||||
- `type`: K线类型(day=日K线)
|
||||
|
||||
**返回格式**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"date": "2024-12-12",
|
||||
"open": 10.50,
|
||||
"high": 10.80,
|
||||
"low": 10.30,
|
||||
"close": 10.60,
|
||||
"volume": 1000000
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
```bash
|
||||
curl "http://127.0.0.1:5000/api/kline?code=000001&type=day"
|
||||
```
|
||||
|
||||
### 启动TDX API服务
|
||||
|
||||
如果您还没有TDX API服务,需要先启动服务:
|
||||
|
||||
1. 确保已安装相关依赖
|
||||
2. 启动TDX API服务器(默认端口5000)
|
||||
3. 在.env中配置服务地址
|
||||
|
||||
### 数据说明
|
||||
|
||||
- **更新频率**:K线数据实时更新
|
||||
- **数据量**:策略需要至少20天的K线数据
|
||||
- **均线计算**:系统自动计算MA5和MA20
|
||||
|
||||
## Webhook通知配置
|
||||
|
||||
建议配置钉钉Webhook以接收卖出提醒:
|
||||
|
||||
```bash
|
||||
# Webhook通知配置
|
||||
WEBHOOK_ENABLED=true
|
||||
WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token=xxxxx
|
||||
WEBHOOK_TYPE=dingtalk
|
||||
WEBHOOK_KEYWORD=aiagents通知
|
||||
```
|
||||
|
||||
## 功能说明
|
||||
|
||||
### 1. 加入策略监控
|
||||
|
||||
在"低价擒牛"选股结果中,点击任意股票详情中的"➕ 加入策略监控"按钮。
|
||||
|
||||
### 2. 监控条件
|
||||
|
||||
#### 条件1:持股天数到期
|
||||
- 持股满5天(可配置)
|
||||
- 第6天开盘时发送卖出提醒
|
||||
- 自动从监控列表移除
|
||||
|
||||
#### 条件2:MA5下穿MA20
|
||||
- 每分钟扫描1次(可配置)
|
||||
- 检测MA5是否小于MA20
|
||||
- 满足条件时发送卖出提醒
|
||||
- 自动从监控列表移除
|
||||
|
||||
### 3. 卖出提醒
|
||||
|
||||
提醒会通过以下方式发送:
|
||||
- 钉钉Webhook通知(需配置)
|
||||
- 监控面板中的"卖出提醒"标签页
|
||||
|
||||
### 4. 自动移除
|
||||
|
||||
当满足卖出条件时:
|
||||
1. 生成卖出提醒
|
||||
2. 发送钉钉通知(如已配置)
|
||||
3. 自动从监控列表移除
|
||||
4. 记录到历史提醒
|
||||
|
||||
## 使用流程
|
||||
|
||||
```
|
||||
1. 低价擒牛选股
|
||||
↓
|
||||
2. 查看选股结果
|
||||
↓
|
||||
3. 点击"➕ 加入策略监控"
|
||||
↓
|
||||
4. 进入"📊 策略监控"面板
|
||||
↓
|
||||
5. 启动监控服务
|
||||
↓
|
||||
6. 等待卖出提醒
|
||||
↓
|
||||
7. 收到提醒后卖出
|
||||
```
|
||||
|
||||
## 监控面板功能
|
||||
|
||||
### 服务控制
|
||||
- ▶️ 启动监控服务
|
||||
- ⏸️ 停止监控服务
|
||||
- ⚙️ 配置扫描间隔
|
||||
|
||||
### 监控列表
|
||||
- 查看所有监控中的股票
|
||||
- 显示持有天数
|
||||
- 手动移除股票
|
||||
|
||||
### 卖出提醒
|
||||
- 查看待处理的提醒
|
||||
- 查看MA5/MA20数据
|
||||
- 标记已处理/忽略
|
||||
|
||||
### 历史记录
|
||||
- 查看历史提醒
|
||||
- 清理旧记录
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. ⚠️ **监控服务需要手动启动**
|
||||
- 首次使用需在监控面板启动服务
|
||||
- 服务启动后会持续运行
|
||||
- 关闭浏览器后服务会停止
|
||||
|
||||
2. ⚠️ **TDX数据源**
|
||||
- 必须配置TDX_BASE_URL
|
||||
- 确俜TDX API服务已启动
|
||||
- 使用`/api/kline`接口获取日K线数据
|
||||
- 数据需要至少20天才能计算MA20
|
||||
|
||||
3. ⚠️ **扫描间隔**
|
||||
- 默认60秒扫描1次
|
||||
- 可在监控面板调整
|
||||
- 建议不要低于30秒
|
||||
|
||||
4. ⚠️ **持股天数计算**
|
||||
- 按自然日计算
|
||||
- 不区分交易日/非交易日
|
||||
- 建议留有余地
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 监控服务无法启动?
|
||||
A: 检查:
|
||||
- Python环境是否正常
|
||||
- 依赖包是否安装完整
|
||||
- 查看控制台错误日志
|
||||
|
||||
### Q2: 没有收到卖出提醒?
|
||||
A: 检查:
|
||||
- Webhook是否配置正确
|
||||
- 监控服务是否在运行
|
||||
- TDX API服务是否启动
|
||||
- 访问`http://127.0.0.1:5000/api/health`检查API状态
|
||||
- 查看控制台日志是否有错误信息
|
||||
|
||||
### Q3: 如何修改扫描间隔?
|
||||
A: 两种方式:
|
||||
- 在监控面板的"⚙️ 监控配置"中修改
|
||||
- 在.env文件中修改`LOW_PRICE_BULL_SCAN_INTERVAL`
|
||||
|
||||
### Q4: 持股天数如何调整?
|
||||
A: 在.env文件中修改:
|
||||
```bash
|
||||
LOW_PRICE_BULL_HOLDING_DAYS=7 # 改为7天
|
||||
```
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 数据库
|
||||
- SQLite本地数据库
|
||||
- 文件:`low_price_bull_monitor.db`
|
||||
- 包含监控列表和提醒记录
|
||||
|
||||
### 后台服务
|
||||
- Python线程实现
|
||||
- 定时扫描循环
|
||||
- 异常自动恢复
|
||||
|
||||
### 数据获取
|
||||
- 使用TDX API
|
||||
- 调用`/api/kline`接口
|
||||
- 获取日K线数据
|
||||
- 计算MA5/MA20均线
|
||||
|
||||
---
|
||||
|
||||
**版本**: v1.0
|
||||
**更新日期**: 2024-12-12
|
||||
@@ -0,0 +1,460 @@
|
||||
'''
|
||||
均线回归策略 V3.0 进攻版
|
||||
优化重点:提高Beta,增强进攻性,减少过度保守
|
||||
|
||||
核心改进:
|
||||
1. 放宽市场择时,只在极端熊市降仓
|
||||
2. 增加持仓数量,提高资金利用率
|
||||
3. 放宽买入条件,增加交易机会
|
||||
4. 优化止盈,让利润奔跑
|
||||
5. 增加突破买入模式
|
||||
'''
|
||||
|
||||
import jqdata
|
||||
import numpy as np
|
||||
|
||||
## 初始化函数
|
||||
def initialize(context):
|
||||
# 设定沪深300作为基准
|
||||
set_benchmark('000300.XSHG')
|
||||
# 开启动态复权模式
|
||||
set_option('use_real_price', True)
|
||||
# 设定成交量比例
|
||||
set_option('order_volume_ratio', 1)
|
||||
# 设置交易手续费
|
||||
set_order_cost(OrderCost(open_tax=0, close_tax=0.001,
|
||||
open_commission=0.0003, close_commission=0.0003,
|
||||
close_today_commission=0, min_commission=5), type='stock')
|
||||
|
||||
# ========== 策略参数(进攻版)==========
|
||||
g.stocknum = 8 # 持仓数量增加到8只
|
||||
g.max_position_ratio = 0.98 # 最大仓位98%
|
||||
|
||||
# 均线参数
|
||||
g.ma_short = 20 # 短期均线
|
||||
g.ma_mid = 60 # 中期均线
|
||||
g.ma_long = 120 # 长期均线
|
||||
|
||||
# 回调买入参数(放宽条件)
|
||||
g.pullback_ratio = 0.035 # 放宽到3.5%
|
||||
g.break_tolerance = 0.015 # 允许跌破1.5%
|
||||
|
||||
# 止盈止损参数(让利润奔跑)
|
||||
g.base_take_profit = 0.20 # 止盈提高到20%
|
||||
g.base_stop_loss = 0.06 # 止损放宽到6%
|
||||
g.trailing_stop = 0.08 # 回撤容忍8%
|
||||
|
||||
# 市值筛选(亿)- 扩大范围
|
||||
g.min_market_cap = 20
|
||||
g.max_market_cap = 1000
|
||||
|
||||
# 行业分散参数
|
||||
g.max_industry_stocks = 3 # 同行业最多3只
|
||||
|
||||
# 市场状态
|
||||
g.market_state = 'NORMAL'
|
||||
g.position_ratio = 1.0
|
||||
|
||||
# 记录
|
||||
g.highest_profit = {}
|
||||
g.stock_volatility = {}
|
||||
g.hold_days = {} # 持仓天数
|
||||
|
||||
# 定时任务
|
||||
run_daily(market_analysis, '09:31')
|
||||
run_daily(morning_check, '09:35')
|
||||
run_daily(check_positions, '14:00') # 只检查一次
|
||||
run_daily(afternoon_trade, '14:50')
|
||||
|
||||
## 市场环境分析(放宽版)
|
||||
def market_analysis(context):
|
||||
"""
|
||||
只在极端情况下降低仓位,大部分时间保持高仓位
|
||||
"""
|
||||
index_code = '000300.XSHG'
|
||||
|
||||
try:
|
||||
df = attribute_history(index_code, 130, '1d', ['close'], skip_paused=True)
|
||||
if len(df) < 120:
|
||||
g.market_state = 'NORMAL'
|
||||
g.position_ratio = 1.0
|
||||
return
|
||||
|
||||
close = df['close'].values
|
||||
current = close[-1]
|
||||
|
||||
ma20 = np.mean(close[-20:])
|
||||
ma60 = np.mean(close[-60:])
|
||||
ma120 = np.mean(close[-120:])
|
||||
|
||||
change_5d = (close[-1] - close[-6]) / close[-6]
|
||||
change_10d = (close[-1] - close[-11]) / close[-11]
|
||||
|
||||
# 只在极端熊市才降仓(条件更严格)
|
||||
if current < ma120 and ma20 < ma60 < ma120 and change_10d < -0.08:
|
||||
# 极端熊市:指数跌破120日均线,均线空头,10日跌幅超8%
|
||||
g.market_state = 'BEAR'
|
||||
g.position_ratio = 0.5
|
||||
log.info("【市场】极端熊市,仓位50%%")
|
||||
elif change_5d < -0.08:
|
||||
# 短期暴跌超8%:暂时减仓
|
||||
g.market_state = 'BEAR'
|
||||
g.position_ratio = 0.6
|
||||
log.info("【市场】短期暴跌,仓位60%%")
|
||||
elif current > ma20 and ma20 > ma60:
|
||||
# 趋势向上:满仓
|
||||
g.market_state = 'BULL'
|
||||
g.position_ratio = 1.0
|
||||
log.info("【市场】趋势向上,满仓")
|
||||
else:
|
||||
# 默认高仓位运行
|
||||
g.market_state = 'NORMAL'
|
||||
g.position_ratio = 0.9
|
||||
log.info("【市场】正常状态,仓位90%%")
|
||||
|
||||
except Exception as e:
|
||||
g.market_state = 'NORMAL'
|
||||
g.position_ratio = 0.9
|
||||
|
||||
## 早盘检查
|
||||
def morning_check(context):
|
||||
g.buy_list = check_stocks(context)
|
||||
log.info("【选股】%d 只,市场: %s,仓位: %.0f%%" %
|
||||
(len(g.buy_list), g.market_state, g.position_ratio * 100))
|
||||
|
||||
## 检查持仓 - 优化版(让利润奔跑)
|
||||
def check_positions(context):
|
||||
if len(context.portfolio.positions) == 0:
|
||||
return
|
||||
|
||||
for stock in list(context.portfolio.positions.keys()):
|
||||
position = context.portfolio.positions[stock]
|
||||
if position.closeable_amount <= 0:
|
||||
continue
|
||||
|
||||
cost = position.avg_cost
|
||||
current_price = position.price
|
||||
if cost <= 0:
|
||||
continue
|
||||
|
||||
profit_ratio = (current_price - cost) / cost
|
||||
|
||||
# 更新持仓天数
|
||||
g.hold_days[stock] = g.hold_days.get(stock, 0) + 1
|
||||
|
||||
# 更新最高盈利
|
||||
if stock not in g.highest_profit:
|
||||
g.highest_profit[stock] = profit_ratio
|
||||
else:
|
||||
g.highest_profit[stock] = max(g.highest_profit[stock], profit_ratio)
|
||||
|
||||
highest = g.highest_profit[stock]
|
||||
hold_days = g.hold_days.get(stock, 0)
|
||||
|
||||
# 动态止损(根据波动率和持仓时间)
|
||||
volatility = g.stock_volatility.get(stock, 0.025)
|
||||
# 持仓时间越长,止损越宽松
|
||||
time_factor = min(1 + hold_days * 0.01, 1.5)
|
||||
dynamic_stop_loss = min(g.base_stop_loss * time_factor, 0.12)
|
||||
|
||||
# === 止损逻辑 ===
|
||||
if profit_ratio < -dynamic_stop_loss:
|
||||
log.info("【止损】%s 亏损 %.2f%%" % (stock, profit_ratio * 100))
|
||||
order_target(stock, 0)
|
||||
clean_stock_data(stock)
|
||||
continue
|
||||
|
||||
# === 均线破位(只在亏损时卖)===
|
||||
if profit_ratio < 0 and check_sell_signal(stock):
|
||||
log.info("【均线破位】%s 破位且亏损,卖出" % stock)
|
||||
order_target(stock, 0)
|
||||
clean_stock_data(stock)
|
||||
continue
|
||||
|
||||
# === 移动止盈(让利润奔跑)===
|
||||
if highest >= g.base_take_profit:
|
||||
drawdown = highest - profit_ratio
|
||||
# 盈利越多,允许回撤越大(最多允许回撤15%)
|
||||
allowed_drawdown = min(g.trailing_stop + highest * 0.4, 0.18)
|
||||
if drawdown >= allowed_drawdown:
|
||||
log.info("【移动止盈】%s 最高 %.2f%%,回撤 %.2f%%" %
|
||||
(stock, highest * 100, drawdown * 100))
|
||||
order_target(stock, 0)
|
||||
clean_stock_data(stock)
|
||||
continue
|
||||
|
||||
# === 分批止盈(保守止盈,只卖小部分)===
|
||||
if profit_ratio >= g.base_take_profit * 2:
|
||||
# 盈利超40%,卖出30%锁定利润
|
||||
sell_amount = int(position.closeable_amount * 0.3 / 100) * 100
|
||||
if sell_amount >= 100:
|
||||
log.info("【大幅止盈】%s 盈利 %.2f%%,卖出30%%" % (stock, profit_ratio * 100))
|
||||
order(stock, -sell_amount)
|
||||
|
||||
## 清理股票数据
|
||||
def clean_stock_data(stock):
|
||||
for d in [g.highest_profit, g.stock_volatility, g.hold_days]:
|
||||
if stock in d:
|
||||
del d[stock]
|
||||
|
||||
## 尾盘交易
|
||||
def afternoon_trade(context):
|
||||
buy_stocks(context)
|
||||
|
||||
## 买入函数(积极版)
|
||||
def buy_stocks(context):
|
||||
if not hasattr(g, 'buy_list') or not g.buy_list:
|
||||
return
|
||||
|
||||
position_count = len(context.portfolio.positions)
|
||||
adjusted_stocknum = int(g.stocknum * g.position_ratio)
|
||||
if adjusted_stocknum <= 0 or position_count >= adjusted_stocknum:
|
||||
return
|
||||
|
||||
# 计算资金分配
|
||||
available_cash = context.portfolio.available_cash * g.max_position_ratio
|
||||
buy_count = min(adjusted_stocknum - position_count, len(g.buy_list))
|
||||
if buy_count <= 0 or available_cash < 10000:
|
||||
return
|
||||
|
||||
cash_per_stock = available_cash / buy_count
|
||||
held_industries = get_held_industries(context)
|
||||
|
||||
bought = 0
|
||||
for stock in g.buy_list:
|
||||
if bought >= buy_count:
|
||||
break
|
||||
if stock in context.portfolio.positions:
|
||||
continue
|
||||
|
||||
# 行业分散
|
||||
stock_industry = get_stock_industry(stock)
|
||||
if stock_industry and held_industries.get(stock_industry, 0) >= g.max_industry_stocks:
|
||||
continue
|
||||
|
||||
# 简化买入条件检查
|
||||
if check_buy_signal(stock):
|
||||
order_value(stock, cash_per_stock)
|
||||
log.info("【买入】%s" % stock)
|
||||
g.highest_profit[stock] = 0
|
||||
g.stock_volatility[stock] = 0.025
|
||||
g.hold_days[stock] = 0
|
||||
if stock_industry:
|
||||
held_industries[stock_industry] = held_industries.get(stock_industry, 0) + 1
|
||||
bought += 1
|
||||
|
||||
## 获取已持仓的行业分布
|
||||
def get_held_industries(context):
|
||||
industries = {}
|
||||
for stock in context.portfolio.positions.keys():
|
||||
ind = get_stock_industry(stock)
|
||||
if ind:
|
||||
industries[ind] = industries.get(ind, 0) + 1
|
||||
return industries
|
||||
|
||||
## 获取股票所属行业
|
||||
def get_stock_industry(stock):
|
||||
try:
|
||||
ind_dict = get_industry(stock)
|
||||
if ind_dict and stock in ind_dict:
|
||||
# 获取申万一级行业
|
||||
for ind_code, ind_info in ind_dict[stock].items():
|
||||
if ind_code.startswith('sw_l1'):
|
||||
return ind_info.get('industry_name', None)
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
## 选股函数(进攻版)
|
||||
def check_stocks(context):
|
||||
# 第一步:基础筛选(放宽条件)
|
||||
q = query(
|
||||
valuation.code,
|
||||
valuation.market_cap
|
||||
).filter(
|
||||
valuation.market_cap.between(g.min_market_cap, g.max_market_cap)
|
||||
).order_by(
|
||||
valuation.market_cap.asc()
|
||||
).limit(500) # 扩大候选池
|
||||
|
||||
df = get_fundamentals(q)
|
||||
if df.empty:
|
||||
return []
|
||||
|
||||
stock_list = list(df['code'])
|
||||
stock_list = filter_basic(stock_list)
|
||||
|
||||
# 均线信号筛选
|
||||
candidates = []
|
||||
for stock in stock_list[:200]: # 检查前200只
|
||||
if check_buy_signal(stock):
|
||||
score = calculate_trend_score(stock)
|
||||
candidates.append((stock, score))
|
||||
|
||||
# 按评分排序
|
||||
candidates.sort(key=lambda x: x[1], reverse=True)
|
||||
buy_list = [c[0] for c in candidates[:g.stocknum * 3]]
|
||||
|
||||
return buy_list
|
||||
|
||||
## 计算趋势强度评分
|
||||
def calculate_trend_score(stock):
|
||||
"""
|
||||
评分因素:
|
||||
1. 均线发散程度
|
||||
2. 价格距离MA20的位置
|
||||
3. 成交量配合
|
||||
"""
|
||||
try:
|
||||
df = attribute_history(stock, 130, '1d', ['close', 'volume'], skip_paused=True)
|
||||
if len(df) < 120:
|
||||
return 0
|
||||
|
||||
close = df['close'].values
|
||||
volume = df['volume'].values
|
||||
|
||||
ma20 = np.mean(close[-20:])
|
||||
ma60 = np.mean(close[-60:])
|
||||
ma120 = np.mean(close[-120:])
|
||||
current = close[-1]
|
||||
|
||||
score = 0
|
||||
|
||||
# 均线发散程度(20分)
|
||||
spread = (ma20 - ma120) / ma120
|
||||
score += min(spread * 100, 20)
|
||||
|
||||
# 价格位置(30分)- 越接近MA20越好
|
||||
distance = abs(current - ma20) / ma20
|
||||
score += max(0, 30 - distance * 500)
|
||||
|
||||
# 成交量(20分)- 回调缩量为佳
|
||||
vol_ma5 = np.mean(volume[-5:])
|
||||
vol_ma20 = np.mean(volume[-20:])
|
||||
if vol_ma5 < vol_ma20 * 0.8:
|
||||
score += 20 # 缩量回调
|
||||
elif vol_ma5 < vol_ma20:
|
||||
score += 10
|
||||
|
||||
# 趋势持续性(30分)
|
||||
ma20_slope = (ma20 - np.mean(close[-25:-5])) / np.mean(close[-25:-5])
|
||||
if ma20_slope > 0:
|
||||
score += min(ma20_slope * 300, 30)
|
||||
|
||||
return score
|
||||
|
||||
except:
|
||||
return 0
|
||||
|
||||
## 基础过滤
|
||||
def filter_basic(stock_list):
|
||||
if not stock_list:
|
||||
return []
|
||||
|
||||
current_data = get_current_data()
|
||||
filtered = []
|
||||
|
||||
for stock in stock_list:
|
||||
# 过滤停牌
|
||||
if current_data[stock].paused:
|
||||
continue
|
||||
# 过滤ST
|
||||
if current_data[stock].is_st:
|
||||
continue
|
||||
if 'ST' in current_data[stock].name or '*' in current_data[stock].name:
|
||||
continue
|
||||
# 过滤科创板、北交所
|
||||
if stock.startswith('688') or stock.startswith('8') or stock.startswith('4'):
|
||||
continue
|
||||
# 过滤涨跌停
|
||||
if current_data[stock].last_price >= current_data[stock].high_limit:
|
||||
continue
|
||||
if current_data[stock].last_price <= current_data[stock].low_limit:
|
||||
continue
|
||||
|
||||
filtered.append(stock)
|
||||
|
||||
return filtered
|
||||
|
||||
## 检查买入信号(放宽版)
|
||||
def check_buy_signal(stock):
|
||||
"""
|
||||
放宽买入条件,增加交易机会
|
||||
"""
|
||||
try:
|
||||
df = attribute_history(stock, g.ma_long + 10, '1d', ['close'], skip_paused=True)
|
||||
if len(df) < g.ma_long:
|
||||
return False
|
||||
|
||||
close = df['close'].values
|
||||
current_price = close[-1]
|
||||
|
||||
# 计算均线
|
||||
ma20 = np.mean(close[-g.ma_short:])
|
||||
ma60 = np.mean(close[-g.ma_mid:])
|
||||
ma120 = np.mean(close[-g.ma_long:])
|
||||
|
||||
# 条件1:均线多头排列(核心条件)
|
||||
if not (ma20 > ma60 > ma120):
|
||||
return False
|
||||
|
||||
# 条件2:MA20向上
|
||||
ma20_5d_ago = np.mean(close[-g.ma_short-5:-5])
|
||||
if ma20 <= ma20_5d_ago:
|
||||
return False
|
||||
|
||||
# 条件3:价格在MA20附近(放宽范围)
|
||||
distance = (current_price - ma20) / ma20
|
||||
# 允许在MA20上方5%以内,或跌破2%以内
|
||||
if distance > 0.05 or distance < -0.02:
|
||||
return False
|
||||
|
||||
# 条件4:价格不能离MA60太远
|
||||
if current_price > ma60 * 1.30:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except:
|
||||
return False
|
||||
|
||||
## 检查卖出信号(均线破位)
|
||||
def check_sell_signal(stock):
|
||||
"""
|
||||
卖出条件:
|
||||
1. 价格跌破MA60
|
||||
2. MA20下穿MA60(死叉)
|
||||
3. 价格跌破MA20且持续3天
|
||||
"""
|
||||
try:
|
||||
df = attribute_history(stock, g.ma_mid + 5, '1d', ['close'], skip_paused=True)
|
||||
if len(df) < g.ma_mid:
|
||||
return False
|
||||
|
||||
close = df['close'].values
|
||||
current_price = close[-1]
|
||||
|
||||
ma20 = np.mean(close[-g.ma_short:])
|
||||
ma60 = np.mean(close[-g.ma_mid:])
|
||||
|
||||
# 价格跌破MA60超过2%
|
||||
if current_price < ma60 * 0.98:
|
||||
return True
|
||||
|
||||
# MA20下穿MA60(死叉)
|
||||
ma20_yesterday = np.mean(close[-g.ma_short-1:-1])
|
||||
ma60_yesterday = np.mean(close[-g.ma_mid-1:-1])
|
||||
if ma20_yesterday > ma60_yesterday and ma20 < ma60:
|
||||
return True
|
||||
|
||||
# 连续3天收盘在MA20下方
|
||||
ma20_3d = [np.mean(close[-g.ma_short-i:-i]) if i > 0 else ma20 for i in range(3)]
|
||||
below_ma20_count = sum(1 for i in range(3) if close[-1-i] < ma20_3d[i] * 0.99)
|
||||
if below_ma20_count >= 3:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except:
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user