Browse Source

WR指标

liruiqiang
liruiqiang 1 week ago
parent
commit
829913e5ec
  1. 7
      src/main/java/com/deepchart/controller/IndexController.java
  2. 2
      src/main/java/com/deepchart/service/IndexService.java
  3. 5
      src/main/java/com/deepchart/service/impl/IndexServiceImpl.java
  4. 231
      src/main/java/com/deepchart/utils/WRUtil.java

7
src/main/java/com/deepchart/controller/IndexController.java

@ -46,4 +46,11 @@ public class IndexController {
String result = indexService.expma(list); String result = indexService.expma(list);
return Result.success("success", result); return Result.success("success", result);
} }
@PostMapping("/wr")
public Result wr(@RequestBody StockInfo stock) {
List<StockDailyData> list = indexService.getStockData(stock);
String result = indexService.wr(list);
return Result.success("success", result);
}
} }

2
src/main/java/com/deepchart/service/IndexService.java

@ -15,4 +15,6 @@ public interface IndexService {
String cci(List<StockDailyData> list); String cci(List<StockDailyData> list);
String expma(List<StockDailyData> list); String expma(List<StockDailyData> list);
String wr(List<StockDailyData> list);
} }

5
src/main/java/com/deepchart/service/impl/IndexServiceImpl.java

@ -39,4 +39,9 @@ public class IndexServiceImpl implements IndexService {
public String expma(List<StockDailyData> list) { public String expma(List<StockDailyData> list) {
return EXPMAUtil.analyzeEXPMA(list,12,40); return EXPMAUtil.analyzeEXPMA(list,12,40);
} }
@Override
public String wr(List<StockDailyData> list) {
return WRUtil.analyzeWR(list);
}
} }

231
src/main/java/com/deepchart/utils/WRUtil.java

@ -0,0 +1,231 @@
package com.deepchart.utils;
import com.deepchart.entity.StockDailyData;
import java.time.LocalDate;
import java.util.*;
public class WRUtil {
/**
* 对40日股票数据进行WR威廉指标分析
*
* @param stockData 按时间升序排列的40天股票日线数据最早在前
* @return WR分析报告文本
*/
public static String analyzeWR(List<StockDailyData> stockData) {
if (stockData == null || stockData.size() < 14) {
return "❌ 数据不足:WR分析至少需要14天K线数据。";
}
int n = stockData.size();
// 常用双周期快线 N=6慢线 N=10
List<Double> wrFast = calculateWr(stockData, 6);
List<Double> wrSlow = calculateWr(stockData, 10);
List<Double> prices = new ArrayList<>();
for (var d : stockData) prices.add(d.getClosePrice());
LocalDate latestDate = stockData.get(n - 1).getDate();
double latestPrice = prices.get(n - 1);
double currentFast = wrFast.get(n - 1);
double currentSlow = wrSlow.get(n - 1);
StringBuilder report = new StringBuilder();
report.append("📊 威廉指标(WR)分析报告(截至 ").append(latestDate).append(")\n");
report.append(" • 当前收盘价: ").append(String.format("%.2f", latestPrice)).append("\n");
report.append(" • WR(6): ").append(String.format("%.1f", currentFast)).append("\n");
report.append(" • WR(10): ").append(String.format("%.1f", currentSlow)).append("\n\n");
// 区域判断
boolean fastInOversold = currentFast >= 80;
boolean slowInOversold = currentSlow >= 80;
boolean fastInOverbought = currentFast <= 20;
boolean slowInOverbought = currentSlow <= 20;
if (fastInOversold || slowInOversold) {
report.append("📉 市场状态:【超卖区域】\n");
report.append(" → 价格接近近期低点,短期下跌动能可能衰竭。\n");
} else if (fastInOverbought || slowInOverbought) {
report.append("📈 市场状态:【超买区域】\n");
report.append(" → 价格接近近期高点,短期上涨动能可能衰竭。\n");
} else {
report.append("⚖️ 市场状态:【常态波动区(20~80)】\n");
report.append(" → 无极端情绪信号,WR参考价值有限。\n");
}
// 双线交叉信号仅当有足够历史数据
String crossSignal = detectCross(wrFast, wrSlow, n);
if (!crossSignal.isEmpty()) {
report.append("\n").append(crossSignal);
}
// 背离检测
boolean topDiv = detectTopDivergence(prices, wrFast, n);
boolean bottomDiv = detectBottomDivergence(prices, wrFast, n);
if (topDiv) {
report.append("\n🚨 顶背离信号!\n");
report.append(" → 股价创新高,但WR强势减弱,上涨动能不足。\n");
report.append(" → 强烈建议减仓或止盈。\n");
}
if (bottomDiv) {
report.append("\n💎 底背离信号!\n");
report.append(" → 股价创新低,但WR弱势减弱,下跌动能衰竭。\n");
report.append(" → 可视为短线抄底机会。\n");
}
if (!topDiv && !bottomDiv) {
report.append("\n🔍 背离状态:无显著背离\n");
}
// 综合操作建议
report.append("\n📌 操作建议:\n");
if (bottomDiv || (fastInOversold && currentFast < wrFast.get(n - 2))) {
report.append(" • WR从超卖区回升,结合底背离,可考虑短线买入。\n");
} else if (topDiv || (fastInOverbought && currentFast > wrFast.get(n - 2))) {
report.append(" • WR从超买区回落,结合顶背离,建议逢高减仓。\n");
} else if (currentFast > 50 && currentSlow > 50) {
report.append(" • WR处于中性偏弱区域,观望为主。\n");
} else if (currentFast < 50 && currentSlow < 50) {
report.append(" • WR处于中性偏强区域,持股待涨。\n");
} else {
report.append(" • 市场方向不明,建议结合趋势指标(如EXPMA)综合判断。\n");
}
report.append("\n❗ 注意:WR在单边行情中易钝化(长期超买/超卖),震荡市中效果最佳。");
return report.toString();
}
// 计算WR(N)
private static List<Double> calculateWr(List<StockDailyData> data, int period) {
List<Double> wr = new ArrayList<>(Collections.nCopies(data.size(), 100.0));
if (data.size() < period) return wr;
for (int i = period - 1; i < data.size(); i++) {
double highest = Double.MIN_VALUE;
double lowest = Double.MAX_VALUE;
// 找过去period天含当日的最高价和最低价
for (int j = i - period + 1; j <= i; j++) {
highest = Math.max(highest, data.get(j).getHighPrice());
lowest = Math.min(lowest, data.get(j).getLowPrice());
}
double close = data.get(i).getClosePrice();
if (highest == lowest) {
wr.set(i, 0.0); // 防止除零
} else {
double value = (highest - close) / (highest - lowest) * 100.0;
wr.set(i, Math.max(0.0, Math.min(100.0, value))); // 限制在[0,100]
}
}
return wr;
}
// 检测双线交叉仅看最近3天
private static String detectCross(List<Double> fast, List<Double> slow, int n) {
if (n < 3) return "";
double f0 = fast.get(n - 1), f1 = fast.get(n - 2);
double s0 = slow.get(n - 1), s1 = slow.get(n - 2);
// 金叉快线上穿慢线且在超卖区附近
if (f1 <= s1 && f0 > s0 && f0 >= 70) {
return "✨ 买入信号:WR(6)在超卖区上穿WR(10),形成金叉,短期反弹可期。\n";
}
// 死叉快线下穿慢线且在超买区附近
if (f1 >= s1 && f0 < s0 && f0 <= 30) {
return "⚠️ 卖出信号:WR(6)在超买区下穿WR(10),形成死叉,短期回调风险加大。\n";
}
return "";
}
// 顶背离价格新高WR高点降低WR是反向指标高点=弱势
private static boolean detectTopDivergence(List<Double> prices, List<Double> wr, int n) {
if (n < 20) return false;
// 找最近一个价格高点局部最大
int recentHighIdx = findRecentPeak(prices, n, 5);
if (recentHighIdx == -1 || recentHighIdx < 10) return false;
// 找上一个价格高点
int prevHighIdx = findPreviousPeak(prices, recentHighIdx - 5, recentHighIdx - 15);
if (prevHighIdx == -1) return false;
double recentPriceHigh = prices.get(recentHighIdx);
double prevPriceHigh = prices.get(prevHighIdx);
double recentWrHigh = wr.get(recentHighIdx); // WR高值 = 弱势
double prevWrHigh = wr.get(prevHighIdx);
// 价格新高 + WR高点更低即弱势更弱 顶背离
return (recentPriceHigh > prevPriceHigh) && (recentWrHigh < prevWrHigh);
}
// 底背离价格新低WR低点抬高WR低值=强势
private static boolean detectBottomDivergence(List<Double> prices, List<Double> wr, int n) {
if (n < 20) return false;
int recentLowIdx = findRecentTrough(prices, n, 5);
if (recentLowIdx == -1 || recentLowIdx < 10) return false;
int prevLowIdx = findPreviousTrough(prices, recentLowIdx - 5, recentLowIdx - 15);
if (prevLowIdx == -1) return false;
double recentPriceLow = prices.get(recentLowIdx);
double prevPriceLow = prices.get(prevLowIdx);
double recentWrLow = wr.get(recentLowIdx); // WR低值 = 强势
double prevWrLow = wr.get(prevLowIdx);
// 价格新低 + WR低点更高即强势更强 底背离
return (recentPriceLow < prevPriceLow) && (recentWrLow > prevWrLow);
}
// 辅助找最近局部高点
private static int findRecentPeak(List<Double> prices, int n, int window) {
for (int i = n - 1; i >= n - window && i >= 2; i--) {
if (prices.get(i) >= prices.get(i - 1) && prices.get(i) >= prices.get(i + 1)) {
return i;
}
}
return -1;
}
private static int findPreviousPeak(List<Double> prices, int end, int start) {
start = Math.max(0, start);
end = Math.min(end, prices.size() - 1);
double max = -1;
int idx = -1;
for (int i = start; i <= end; i++) {
if (prices.get(i) > max) {
max = prices.get(i);
idx = i;
}
}
return idx;
}
private static int findRecentTrough(List<Double> prices, int n, int window) {
for (int i = n - 2; i >= n - window && i >= 2; i--) {
if (prices.get(i) <= prices.get(i - 1) && prices.get(i) <= prices.get(i + 1)) {
return i;
}
}
return -1;
}
private static int findPreviousTrough(List<Double> prices, int end, int start) {
start = Math.max(0, start);
end = Math.min(end, prices.size() - 1);
double min = Double.MAX_VALUE;
int idx = -1;
for (int i = start; i <= end; i++) {
if (prices.get(i) < min) {
min = prices.get(i);
idx = i;
}
}
return idx;
}
}
Loading…
Cancel
Save