| 2025-12-25 | Administrator | ![]() |
| 2025-12-25 | Administrator | ![]() |
| 2025-12-25 | Administrator | ![]() |
| MacdMaStrategy_Analysis.md | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACDTest.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java | ●●●●● patch | view | raw | blame | history |
MacdMaStrategy_Analysis.md
New file @@ -0,0 +1,224 @@ # MacdMaStrategy.java 类逻辑梳理 ## 1. 类概述 MacdMaStrategy.java是一个专为ETH合约设计的MACD+MA复合交易策略实现类,结合了移动平均线(MA)的趋势判断能力和MACD指标的动量分析能力,通过RSI和波动率指标进行风险控制和信号过滤。 ## 2. 核心组成部分 ### 2.1 技术指标实例 类中初始化了以下技术指标: ```java // MACD指标:用于判断价格动量和趋势变化 private final MACD macd = new MACD(); // 30日移动平均线:ETH波动较大,使用更短的周期捕捉趋势变化 private final MovingAverage ma30 = new MovingAverage(30); // 100日移动平均线:ETH作为高波动资产,长期趋势判断使用更短周期 private final MovingAverage ma100 = new MovingAverage(100); // RSI指标(10周期):ETH波动快,使用更短周期提高响应速度 private final RSI rsi = new RSI(10); // 波动率指标(15周期):ETH波动频繁,使用更短周期捕捉市场变化 private final Volatility volatility = new Volatility(15); ``` ### 2.2 状态变量 ```java // 最新价格:用于策略判断 private BigDecimal lastPrice; // 当前市场趋势:牛市/熊市 private TrendDirection trend; ``` ### 2.3 枚举类型 定义了两个内部枚举类型用于状态表示: ```java enum PositionStatus { FLAT, LONG, SHORT } // 持仓状态:空仓/多头/空头 enum TrendDirection { BULLISH, BEARISH } // 趋势方向:牛市/熊市 ``` ## 3. 策略执行流程 ### 3.1 主执行入口 ```java public void execute(List<BigDecimal> prices) { // 验证输入数据完整性:策略需要至少200个价格数据点 if (prices.size() < 200) { throw new IllegalArgumentException("至少需要200个价格数据点才能运行该策略"); } updateIndicators(prices); // 更新所有技术指标 getTrend(); // 确定当前市场趋势 } ``` ### 3.2 指标更新 ```java private void updateIndicators(List<BigDecimal> prices) { // 先计算波动率指标,因为MACD需要用它来动态调整周期 volatility.calculate(prices); // 计算MACD指标并传入波动率参数,实现动态周期调整 macd.calculate(prices, volatility.getValue()); // 计算移动平均线指标 ma30.calculate(prices); // 计算30日移动平均线 ma100.calculate(prices); // 计算100日移动平均线 // 计算RSI指标 rsi.calculate(prices); // 更新最新价格 lastPrice = prices.get(prices.size()-1); } ``` ### 3.3 趋势判断 ```java public TrendDirection getTrend() { return determineTrend(); } private TrendDirection determineTrend() { BigDecimal currentMa100 = ma100.getMa(); // 获取当前100日移动平均线 // 根据最新价格与100日MA的关系判断趋势:价格高于100日MA为牛市,否则为熊市 return lastPrice.compareTo(currentMa100) > 0 ? TrendDirection.BULLISH : TrendDirection.BEARISH; } ``` ## 4. 交易信号生成 ### 4.1 入场信号检查 ```java public TradeRequestParam getOrderParamOpen(TradeRequestParam tradeRequestParam) { return checkEntrySignal(tradeRequestParam); } private TradeRequestParam checkEntrySignal(TradeRequestParam tradeRequestParam) { String poSide = null; BigDecimal currentMa30 = ma30.getMa(); BigDecimal currentDif = macd.getDif(); BigDecimal currentDea = macd.getDea(); BigDecimal macdBar = macd.getMacdBar(); BigDecimal currentRsi = rsi.getRsi(); BigDecimal currentVolatility = volatility.getValue(); if (trend == TrendDirection.BULLISH) { // 牛市入场条件:MACD多头信号、MACD柱状图为正、价格在30日MA之上、RSI合理、波动率适中 boolean macdBull = currentDif.compareTo(currentDea) > 0; boolean macdBarPositive = macdBar.compareTo(BigDecimal.ZERO) > 0; BigDecimal priceMaDiff = lastPrice.subtract(currentMa30); boolean priceAboveMAWithStrength = priceMaDiff.compareTo(currentMa30.multiply(new BigDecimal("0.005"))) > 0; boolean rsiInRange = currentRsi.compareTo(new BigDecimal(40)) > 0 && currentRsi.compareTo(new BigDecimal(65)) < 0; boolean volatilityModerate = currentVolatility.compareTo(new BigDecimal("0.005")) > 0 && currentVolatility.compareTo(new BigDecimal("0.05")) < 0; if (macdBull && macdBarPositive && priceAboveMAWithStrength && rsiInRange && volatilityModerate) { poSide = CoinEnums.POSSIDE_LONG.getCode(); } } else if (trend == TrendDirection.BEARISH) { // 熊市入场条件:MACD空头信号、MACD柱状图为负、价格在30日MA之下、RSI合理、波动率适中 boolean macdBear = currentDif.compareTo(currentDea) < 0; boolean macdBarNegative = macdBar.compareTo(BigDecimal.ZERO) < 0; BigDecimal priceMaDiff = currentMa30.subtract(lastPrice); boolean priceBelowMAWithStrength = priceMaDiff.compareTo(currentMa30.multiply(new BigDecimal("0.005"))) > 0; boolean rsiInRange = currentRsi.compareTo(new BigDecimal(35)) > 0 && currentRsi.compareTo(new BigDecimal(60)) < 0; boolean volatilityModerate = currentVolatility.compareTo(new BigDecimal("0.005")) > 0 && currentVolatility.compareTo(new BigDecimal("0.05")) < 0; if (macdBear && macdBarNegative && priceBelowMAWithStrength && rsiInRange && volatilityModerate) { poSide = CoinEnums.POSSIDE_SHORT.getCode(); } } tradeRequestParam.setPosSide(poSide); return tradeRequestParam; } ``` ### 4.2 离场信号检查 ```java public TradeRequestParam getOrderParamClose(TradeRequestParam tradeRequestParam) { return checkExitSignal(tradeRequestParam); } private TradeRequestParam checkExitSignal(TradeRequestParam tradeRequestParam) { BigDecimal currentMa30 = ma30.getMa(); BigDecimal currentDif = macd.getDif(); BigDecimal currentDea = macd.getDea(); String posSide = tradeRequestParam.getPosSide(); if (posSide == CoinEnums.POSSIDE_LONG.getCode()) { // 多头持仓离场条件:MACD转为空头信号且价格跌破30日MA boolean macdExit = currentDif.compareTo(currentDea) < 0; boolean priceExit = lastPrice.compareTo(currentMa30) < 0; if (macdExit && priceExit) { tradeRequestParam.setSide(CoinEnums.SIDE_SELL.getCode()); } } else if (posSide == CoinEnums.POSSIDE_SHORT.getCode()) { // 空头持仓离场条件:MACD转为多头信号且价格突破30日MA boolean macdExit = currentDif.compareTo(currentDea) > 0; boolean priceExit = lastPrice.compareTo(currentMa30) > 0; if (macdExit && priceExit) { tradeRequestParam.setSide(CoinEnums.SIDE_BUY.getCode()); } } return tradeRequestParam; } ``` ## 5. 交易过滤机制 ### 5.1 交易跳过检查 ```java public boolean getSkip() { return shouldSkipTrade(); } private boolean shouldSkipTrade() { // 波动率过滤:当波动率小于1%时,市场活跃度不足,跳过交易 if (volatility.getValue().compareTo(new BigDecimal("0.01")) < 0) { return true; } // RSI极端值过滤: // 1. 牛市中RSI>65表示超买,避免追高 // 2. 熊市中RSI<35表示超卖,避免追空 if (trend == TrendDirection.BULLISH && rsi.getRsi().compareTo(new BigDecimal(65)) > 0) { return true; } if (trend == TrendDirection.BEARISH && rsi.getRsi().compareTo(new BigDecimal(35)) < 0) { return true; } return false; } ``` ## 6. 策略核心特点 1. **动态参数调整**:MACD周期根据市场波动率自动调整,适应ETH高波动特性 2. **多重指标验证**:结合MACD、MA、RSI和波动率指标进行综合判断 3. **严格的风险控制**:通过波动率过滤和RSI极端值过滤降低交易风险 4. **精确的入场时机**:要求价格在MA之上/之下有一定偏离,避免假突破 5. **明确的离场条件**:MACD信号反转且价格突破MA时离场,确保及时止盈止损 ## 7. 代码优化建议 1. **完善日志记录**:在关键决策点添加日志记录,便于策略调试和分析 2. **增加止盈止损机制**:当前代码缺少明确的止盈止损价格设置和判断 3. **仓位管理**:建议添加基于风险的仓位计算逻辑 4. **策略参数外部化**:将策略参数(如周期、阈值)配置为外部参数,便于调整 5. **结果返回优化**:execute方法可以返回策略执行结果,方便调用方获取交易信号 ## 8. 总结 MacdMaStrategy.java实现了一个完整的MACD+MA复合交易策略,专为ETH合约设计。该策略通过结合多种技术指标,实现了趋势判断、入场时机选择和风险控制的功能。策略执行流程清晰,从指标更新到趋势判断,再到交易信号生成和过滤,形成了一个完整的交易决策系统。 该策略的核心优势在于动态参数调整和多重指标验证,能够较好地适应ETH高波动的市场特性,同时通过严格的过滤机制降低交易风险。 src/main/java/com/xcong/excoin/modules/okxNewPrice/OkxKlineWebSocketClient.java
@@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSONObject; import com.xcong.excoin.modules.okxNewPrice.celue.CaoZuoService; import com.xcong.excoin.modules.okxNewPrice.indicator.TradingStrategy; import com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy.MacdMaStrategy; import com.xcong.excoin.modules.okxNewPrice.okxWs.*; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums; @@ -256,10 +257,156 @@ log.debug("收到pong响应"); cancelPongTimeout(); } else { processPushData(response); // processPushData(response); processPushDataV2(response); } } catch (Exception e) { log.error("处理WebSocket消息失败: {}", message, e); } } /** * 解析并处理价格推送数据。 * 将最新的标记价格存入 Redis 并触发后续业务逻辑比较处理。 * 当价格变化时,调用CaoZuoService的caoZuo方法,触发所有账号的量化操作 * * @param response 包含价格数据的 JSON 对象 */ private void processPushDataV2(JSONObject response) { try { /** * { * "arg": { * "channel": "candle1D", * "instId": "BTC-USDT" * }, * "data": [ * [ * "1629993600000", * "42500", * "48199.9", * "41006.1", * "41006.1", * "3587.41204591", * "166741046.22583129", * "166741046.22583129", * "0" * ] * ] * } */ JSONObject arg = response.getJSONObject("arg"); if (arg == null) { log.warn("{}: 无效的推送数据,缺少 'arg' 字段", response); return; } String channel = arg.getString("channel"); if (channel == null) { log.warn("{}: 无效的推送数据,缺少 'channel' 字段", response); return; } String instId = arg.getString("instId"); if (instId == null) { log.warn("{}: 无效的推送数据,缺少 'instId' 字段", response); return; } if (CHANNEL.equals(channel) && CoinEnums.HE_YUE.getCode().equals(instId)) { JSONArray dataArray = response.getJSONArray("data"); if (dataArray == null || dataArray.isEmpty()) { log.warn("K线频道数据为空"); return; } JSONArray data = dataArray.getJSONArray(0); BigDecimal openPx = new BigDecimal(data.getString(1)); BigDecimal highPx = new BigDecimal(data.getString(2)); BigDecimal lowPx = new BigDecimal(data.getString(3)); BigDecimal closePx = new BigDecimal(data.getString(4)); BigDecimal vol = new BigDecimal(data.getString(5)); /** * K线状态 * 0:K线未完结 * 1:K线已完结 */ String confirm = data.getString(8); if ("1".equals(confirm)){ //调用策略 // 创建策略实例 MacdMaStrategy strategy = new MacdMaStrategy(); // 生成100个15分钟价格数据点 List<Kline> kline15MinuteData = getKlineDataByInstIdAndBar(instId, "1D"); List<BigDecimal> fiveMinPrices = kline15MinuteData.stream() .map(Kline::getC) .collect(Collectors.toList()); try { // 执行策略 strategy.execute(fiveMinPrices); boolean skip = strategy.getSkip(); if (skip){ log.info("跳过"); return; } System.out.println("策略初始化成功!"); } catch (Exception e) { System.err.println("策略初始化失败:" + e.getMessage()); e.printStackTrace(); } Collection<OkxQuantWebSocketClient> allClients = clientManager.getAllClients(); //如果为空,则直接返回 if (allClients.isEmpty()) { return; } // 获取所有OkxQuantWebSocketClient实例 for (OkxQuantWebSocketClient client : clientManager.getAllClients()) { String accountName = client.getAccountName(); if (accountName != null) { TradeRequestParam tradeRequestParam = new TradeRequestParam(); tradeRequestParam = strategy.getOrderParamOpen(tradeRequestParam); String posSide = tradeRequestParam.getPosSide(); String side = tradeRequestParam.getSide(); BigDecimal posHold = PositionsWs.getAccountMap(PositionsWs.initAccountName(accountName, posSide)).get("pos"); if (posHold != null && posHold.compareTo(BigDecimal.ZERO) > 0){ tradeRequestParam = strategy.getOrderParamClose(tradeRequestParam); } String currentPrice = String.valueOf(closePx); tradeRequestParam = caoZuoService.caoZuoStrategy(accountName, currentPrice, posSide); String clOrdId = WsParamBuild.getOrderNum(side); tradeRequestParam.setClOrdId(clOrdId); String sz = null; if (posSide == CoinEnums.POSSIDE_LONG.getCode()){ if (side == CoinEnums.SIDE_BUY.getCode()){ sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name()); }else if (side == CoinEnums.SIDE_SELL.getCode()){ BigDecimal pos = PositionsWs.getAccountMap(PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_LONG.getCode())).get("pos"); if (BigDecimal.ZERO.compareTo( pos) >= 0) { tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue()); } sz = String.valueOf( pos); } }else if (posSide == CoinEnums.POSSIDE_SHORT.getCode()){ if (side == CoinEnums.SIDE_BUY.getCode()){ BigDecimal pos = PositionsWs.getAccountMap(PositionsWs.initAccountName(accountName, CoinEnums.POSSIDE_SHORT.getCode())).get("pos"); if (BigDecimal.ZERO.compareTo( pos) >= 0) { tradeRequestParam.setTradeType(OrderParamEnums.TRADE_NO.getValue()); } sz = String.valueOf( pos); }else if (side == CoinEnums.SIDE_SELL.getCode()){ sz = InstrumentsWs.getAccountMap(accountName).get(CoinEnums.BUY_CNT_INIT.name()); } } tradeRequestParam.setSz(sz); TradeOrderWs.orderEvent(client.getWebSocketClient(), tradeRequestParam); } } } } } catch (Exception e) { log.error("处理 K线频道推送数据失败", e); } } @@ -498,7 +645,7 @@ LinkedHashMap<String, Object> requestParam = new LinkedHashMap<>(); requestParam.put("instId", instId); requestParam.put("bar", bar); requestParam.put("limit", "100"); requestParam.put("limit", "200"); String result = ExchangeLoginService.getInstance(ExchangeInfoEnum.OKX_UAT.name()).lineHistory(requestParam); log.info("加载OKX-KLINE,{}", result); src/main/java/com/xcong/excoin/modules/okxNewPrice/celue/CaoZuoServiceImpl.java
@@ -43,6 +43,7 @@ public TradeRequestParam caoZuoStrategy(String accountName, String markPx, String posSide) { TradeRequestParam tradeRequestParam = new TradeRequestParam(); tradeRequestParam.setAccountName(accountName); tradeRequestParam.setMarkPx(markPx); tradeRequestParam.setInstId(CoinEnums.HE_YUE.getCode()); tradeRequestParam.setTdMode(CoinEnums.CROSS.getCode()); tradeRequestParam.setPosSide(posSide); src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/IndicatorBase.java
@@ -6,31 +6,31 @@ import java.util.List; /** * 技术指标基础类,提供通用计算方法 * Technical indicators base class, provides common calculation methods * * 指标组合策略: * 1. 趋势判断(MA/AdvancedMA/MACD):判断价格的整体走势方向 * 2. 动量确认(RSI/KDJ):确认当前趋势的强度和可持续性 * 3. 波动参考(BOLL):确定价格的合理波动范围和突破时机 * Indicator combination strategy: * 1. Trend judgment (MA/AdvancedMA/MACD): Determine the overall trend direction of prices * 2. Momentum confirmation (RSI/KDJ): Confirm the strength and sustainability of the current trend * 3. Volatility reference (BOLL): Determine reasonable price volatility range and breakthrough timing * * 多空方向选择逻辑: * - 多头信号:MA多头排列 + MACD金叉 + RSI(30-70) + BOLL价格在上轨与中轨之间 * - 空头信号:MA空头排列 + MACD死叉 + RSI(30-70) + BOLL价格在下轨与中轨之间 * - 震荡信号:AdvancedMA三线粘合 + RSI(40-60) + BOLL带宽收窄 * Long/Short direction selection logic: * - Long signal: MA bullish arrangement + MACD golden cross + RSI(30-70) + BOLL price between upper and middle band * - Short signal: MA bearish arrangement + MACD death cross + RSI(30-70) + BOLL price between lower and middle band * - Consolidation signal: AdvancedMA three-line convergence + RSI(40-60) + BOLL bandwidth narrowing * * 开仓和平仓策略: * - 开多:MA金叉 + MACD金叉 + KDJ金叉 + RSI(30-70) + 价格突破BOLL中轨 * - 开空:MA死叉 + MACD死叉 + KDJ死叉 + RSI(30-70) + 价格跌破BOLL中轨 * - 平多:MA死叉 + MACD死叉 + RSI超买(>70) + 价格跌破BOLL中轨 * - 平空:MA金叉 + MACD金叉 + RSI超卖(<30) + 价格突破BOLL中轨 * Open and close position strategies: * - Open long: MA golden cross + MACD golden cross + KDJ golden cross + RSI(30-70) + price breaks through BOLL middle band * - Open short: MA death cross + MACD death cross + KDJ death cross + RSI(30-70) + price breaks below BOLL middle band * - Close long: MA death cross + MACD death cross + RSI overbought(>70) + price breaks below BOLL middle band * - Close short: MA golden cross + MACD golden cross + RSI oversold(<30) + price breaks through BOLL middle band */ public abstract class IndicatorBase { /** * 计算移动平均值 * @param prices 价格列表 * @param period 周期 * @return 移动平均值 * Calculate moving average * @param prices Price list * @param period Period * @return Moving average value */ protected BigDecimal calculateMA(List<BigDecimal> prices, int period) { if (prices == null || prices.size() < period) { @@ -44,11 +44,11 @@ } /** * 计算指数移动平均值 * @param prices 价格列表 * @param period 周期 * @param prevEMA 前一个EMA值 * @return 指数移动平均值 * Calculate exponential moving average * @param prices Price list * @param period Period * @param prevEMA Previous EMA value * @return Exponential moving average value */ protected BigDecimal calculateEMA(List<BigDecimal> prices, int period, BigDecimal prevEMA) { if (prices == null || prices.size() == 0) { @@ -63,10 +63,10 @@ } /** * 计算标准差 * @param prices 价格列表 * @param period 周期 * @return 标准差 * Calculate standard deviation * @param prices Price list * @param period Period * @return Standard deviation */ protected BigDecimal calculateStdDev(List<BigDecimal> prices, int period) { if (prices == null || prices.size() < period) { @@ -83,9 +83,9 @@ } /** * 计算平方根(简化实现) * @param value 输入值 * @return 平方根 * Calculate square root (simplified implementation) * @param value Input value * @return Square root */ protected BigDecimal sqrt(BigDecimal value) { if (value.compareTo(BigDecimal.ZERO) < 0) { @@ -95,10 +95,10 @@ } /** * 获取最近的价格数据 * @param prices 所有价格数据 * @param period 周期 * @return 最近period个价格数据 * Get recent price data * @param prices All price data * @param period Period * @return Recent period price data */ protected List<BigDecimal> getRecentPrices(List<BigDecimal> prices, int period) { if (prices == null || prices.size() == 0) { @@ -109,12 +109,12 @@ } /** * 计算ATR(Average True Range) * @param high 最高价列表 * @param low 最低价列表 * @param close 收盘价列表 * @param period 周期 * @return ATR值 * Calculate ATR (Average True Range) * @param high High price list * @param low Low price list * @param close Close price list * @param period Period * @return ATR value */ protected BigDecimal calculateATR(List<BigDecimal> high, List<BigDecimal> low, List<BigDecimal> close, int period) { if (high == null || low == null || close == null || @@ -128,16 +128,16 @@ trList.add(trueRange); } // 使用简单移动平均计算ATR // Use simple moving average to calculate ATR return calculateMA(trList, Math.min(period, trList.size())); } /** * 计算真实波幅(True Range) * @param high 当前最高价 * @param low 当前最低价 * @param prevClose 前收盘价 * @return 真实波幅 * Calculate True Range * @param high Current high price * @param low Current low price * @param prevClose Previous close price * @return True range */ protected BigDecimal calculateTrueRange(BigDecimal high, BigDecimal low, BigDecimal prevClose) { BigDecimal h1 = high.subtract(low); @@ -147,10 +147,10 @@ } /** * 计算标准化波动率(基于ATR) * @param close 收盘价列表 * @param atr ATR值 * @return 标准化波动率(百分比) * Calculate normalized volatility (based on ATR) * @param close Close price list * @param atr ATR value * @return Normalized volatility (percentage) */ protected BigDecimal normalizeVolatility(List<BigDecimal> close, BigDecimal atr) { if (close == null || close.size() == 0 || atr.compareTo(BigDecimal.ZERO) == 0) { src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACD.java
@@ -2,7 +2,6 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.math.RoundingMode; @@ -12,37 +11,26 @@ /** * MACD (Moving Average Convergence Divergence) 指标实现 * 计算逻辑: * 1. DIF = EMA(12) - EMA(26) * 2. DEA = EMA(DIF, 9) * 3. MACD柱状图 = (DIF - DEA) * 2 * 1. 快线DIFF = EMA(Close, 12) - EMA(Close, 26) * 2. 慢线DEA = EMA(DIFF, 9) * 3. MACD柱状图 = (DIFF - DEA) * macdBarsMultiplier * * 作用: * 1. 识别趋势方向和动能变化 * 2. DIF上穿DEA形成金叉,提示买入信号 * 3. DIF下穿DEA形成死叉,提示卖出信号 * 4. MACD柱状图由负转正,提示多头力量增强 * 5. MACD柱状图由正转负,提示空头力量增强 * 核心概念: * 1. DIFF是EMA12与EMA26之间的距离,反映短期与长期趋势的差异 * 2. 当DIFF > 0表示EMA12在EMA26上方,代表多头趋势 * 3. 当DIFF < 0表示EMA12在EMA26下方,代表空头趋势 * 4. DEA是DIFF的EMA平滑线,用于过滤DIFF的波动 * 5. MACD柱状图通过放大倍数展示DIFF与DEA之间的关系,便于观察趋势变化 * * 价格参数类型: * - 参数名称:prices * - 参数类型:List<BigDecimal> * - 参数说明:需要至少2个价格数据点用于计算,数据越多计算越准确 * 多空判断: * - 多头机会:DIFF在0轴上且MACD柱状图向上,股价同步上涨 * - 空头机会:DIFF在0轴下且MACD柱状图向下,股价同步下跌 * * 推荐时间粒度及优缺点: * 1. 1分钟(1m): * - 优点:反应迅速,适合超短线交易 * - 缺点:MACD柱状图波动剧烈,信号频繁 * 2. 5分钟(5m): * - 优点:平衡了反应速度和信号可靠性 * - 缺点:仍有一定噪音 * 3. 15分钟(15m): * - 优点:适合日内交易,信号较为可靠 * - 缺点:反应速度较慢 * 4. 1小时(1h)及以上: * - 优点:趋势信号明确,MACD柱状图变化稳定 * - 缺点:反应滞后,不适合短线交易 * 信号过滤: * - 可通过设置macdBarsSmoothingPeriod启用MACD柱状图平滑处理 * - 平滑公式:MACD柱状图 = MA((DIFF - DEA) * macdBarsMultiplier, macdBarsSmoothingPeriod) * - 作用:消除柱状图杂讯,使信号更加清晰 */ @Slf4j @Getter @Setter public class MACD extends IndicatorBase { @@ -51,24 +39,43 @@ public static final int DEFAULT_FAST_PERIOD = 12; public static final int DEFAULT_SLOW_PERIOD = 26; public static final int DEFAULT_SIGNAL_PERIOD = 9; // 默认MACD柱状图放大倍数 public static final int DEFAULT_MACDBARS_MULTIPLIER = 2; // 默认MACD柱状图平滑周期(0表示不平滑) public static final int DEFAULT_MACDBARS_SMOOTHING_PERIOD = 0; // 动态周期参数 // 周期参数 private int fastPeriod; private int slowPeriod; private int signalPeriod; // MACD柱状图参数 private int macdBarsMultiplier; // MACD柱状图放大倍数 private int macdBarsSmoothingPeriod; // MACD柱状图平滑周期(0表示不平滑) // MACD计算结果 private BigDecimal dif = BigDecimal.ZERO; private BigDecimal dea = BigDecimal.ZERO; private BigDecimal macdBar = BigDecimal.ZERO; // 历史值缓存 private BigDecimal prevFastEMA = null; private BigDecimal prevSlowEMA = null; private BigDecimal prevDea = null; private List<BigDecimal> difHistory = new ArrayList<>(); // 保存历史DIF值,用于计算DEA private List<BigDecimal> rawMacdBarHistory = new ArrayList<>(); // 保存原始MACD柱状图值,用于平滑处理 // 最大保存的历史值数量 private static final int MAX_HISTORY_SIZE = 50; // 构造函数使用默认周期 // 构造函数使用默认参数 public MACD() { this.fastPeriod = DEFAULT_FAST_PERIOD; this.slowPeriod = DEFAULT_SLOW_PERIOD; this.signalPeriod = DEFAULT_SIGNAL_PERIOD; this.macdBarsMultiplier = DEFAULT_MACDBARS_MULTIPLIER; this.macdBarsSmoothingPeriod = DEFAULT_MACDBARS_SMOOTHING_PERIOD; } /** @@ -85,7 +92,7 @@ * @param volatility 标准化波动率(百分比),用于动态调整周期 */ public void calculate(List<BigDecimal> prices, BigDecimal volatility) { if (prices == null || prices.size() < 2) { if (prices == null || prices.isEmpty()) { return; } @@ -94,26 +101,81 @@ adjustPeriodsByVolatility(volatility); } // 计算快速EMA // 计算快速EMA (12日) prevFastEMA = calculateEMA(prices, fastPeriod, prevFastEMA); // 计算慢速EMA // 计算慢速EMA (26日) prevSlowEMA = calculateEMA(prices, slowPeriod, prevSlowEMA); // 计算DIF // 计算DIF = EMA(12) - EMA(26) dif = prevFastEMA.subtract(prevSlowEMA).setScale(8, RoundingMode.HALF_UP); // 计算DEA List<BigDecimal> difList = new ArrayList<>(); difList.add(dif); prevDea = calculateEMA(difList, signalPeriod, prevDea); // 将新的DIF值添加到历史记录 difHistory.add(dif); // 保持历史记录在合理范围内 if (difHistory.size() > MAX_HISTORY_SIZE) { difHistory.remove(0); } // 计算DEA = EMA(DIFF, 9) calculateDEA(); // 计算原始MACD柱状图值 = (DIF - DEA) * 放大倍数 BigDecimal rawMacdBar = dif.subtract(dea).multiply(new BigDecimal(macdBarsMultiplier)).setScale(8, RoundingMode.HALF_UP); // 将原始MACD柱状图值添加到历史记录 rawMacdBarHistory.add(rawMacdBar); // 保持历史记录在合理范围内 if (rawMacdBarHistory.size() > MAX_HISTORY_SIZE) { rawMacdBarHistory.remove(0); } // 如果启用了平滑处理,则计算平滑后的MACD柱状图 if (macdBarsSmoothingPeriod > 0) { macdBar = smoothMacdBars().setScale(8, RoundingMode.HALF_UP); } else { macdBar = rawMacdBar; } } /** * 计算DEA指标 */ private void calculateDEA() { int difCount = difHistory.size(); // 如果没有足够的DIF历史值,无法计算有效的DEA if (difCount == 0) { dea = BigDecimal.ZERO; prevDea = null; return; } // 计算DEA = EMA(DIFF, signalPeriod) // 使用所有DIF历史值来计算初始EMA,然后使用最新值更新 prevDea = calculateEMA(difHistory, signalPeriod, prevDea); dea = prevDea.setScale(8, RoundingMode.HALF_UP); } /** * 平滑MACD柱状图 * @return 平滑后的MACD柱状图值 */ private BigDecimal smoothMacdBars() { int historyCount = rawMacdBarHistory.size(); // 计算MACD柱状图 macdBar = dif.subtract(dea).multiply(new BigDecimal(2)).setScale(8, RoundingMode.HALF_UP); // 如果没有足够的历史数据,返回最新的原始值 if (historyCount < macdBarsSmoothingPeriod) { return rawMacdBarHistory.get(historyCount - 1); } log.info("MACD计算结果 - DIF: {}, DEA: {}, MACD柱状图: {}, 参数: fast={}, slow={}, signal={}", dif, dea, macdBar, fastPeriod, slowPeriod, signalPeriod); // 使用简单移动平均平滑MACD柱状图 List<BigDecimal> recentMacdBars = rawMacdBarHistory.subList(historyCount - macdBarsSmoothingPeriod, historyCount); return calculateMA(recentMacdBars, macdBarsSmoothingPeriod); } /** @@ -137,23 +199,43 @@ signalPeriod = 5; } log.info("根据波动率{}调整MACD周期: fast={}, slow={}, signal={}", volatility, fastPeriod, slowPeriod, signalPeriod); } /** * 判断金叉信号(DIF上穿DEA) * @param previousDIF 上一个DIF值 * @param previousDEA 上一个DEA值 * @return 是否形成金叉 */ public boolean isGoldenCross(BigDecimal previousDIF, BigDecimal previousDEA) { return previousDIF.compareTo(previousDEA) < 0 && dif.compareTo(dea) > 0; return previousDIF != null && previousDEA != null && previousDIF.compareTo(previousDEA) < 0 && dif.compareTo(dea) > 0; } /** * 判断死叉信号(DIF下穿DEA) * @param previousDIF 上一个DIF值 * @param previousDEA 上一个DEA值 * @return 是否形成死叉 */ public boolean isDeathCross(BigDecimal previousDIF, BigDecimal previousDEA) { return previousDIF.compareTo(previousDEA) > 0 && dif.compareTo(dea) < 0; return previousDIF != null && previousDEA != null && previousDIF.compareTo(previousDEA) > 0 && dif.compareTo(dea) < 0; } /** * 重置MACD指标状态 */ public void reset() { dif = BigDecimal.ZERO; dea = BigDecimal.ZERO; macdBar = BigDecimal.ZERO; prevFastEMA = null; prevSlowEMA = null; prevDea = null; difHistory.clear(); rawMacdBarHistory.clear(); } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/MACDTest.java
New file @@ -0,0 +1,118 @@ package com.xcong.excoin.modules.okxNewPrice.indicator; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * MACD Indicator Test Class * Used to verify the correctness of MACD calculation logic */ public class MACDTest { public static void main(String[] args) { // Create MACD instance MACD macd = new MACD(); // Set MACD bars parameters (optional, using default values here) macd.setMacdBarsMultiplier(2); // Default multiplier macd.setMacdBarsSmoothingPeriod(0); // No smoothing // Generate test price data (simple upward trend) List<BigDecimal> prices = generateTestPrices(30); System.out.println("=== MACD Indicator Test Start ==="); System.out.println("Price count: " + prices.size()); System.out.println("MACD parameters: fast=" + macd.getFastPeriod() + ", slow=" + macd.getSlowPeriod() + ", signal=" + macd.getSignalPeriod()); System.out.println("MACD bars parameters: multiplier=" + macd.getMacdBarsMultiplier() + ", smoothing=" + macd.getMacdBarsSmoothingPeriod()); System.out.println(); // Calculate MACD macd.calculate(prices); // Output results System.out.println("=== MACD Calculation Results ==="); System.out.println("DIF: " + macd.getDif()); System.out.println("DEA: " + macd.getDea()); System.out.println("MACD Bars: " + macd.getMacdBar()); System.out.println(); // Trend judgment System.out.println("=== Trend Judgment ==="); if (macd.getDif().compareTo(BigDecimal.ZERO) > 0) { System.out.println("DIFF > 0: Bullish trend"); } else if (macd.getDif().compareTo(BigDecimal.ZERO) < 0) { System.out.println("DIFF < 0: Bearish trend"); } else { System.out.println("DIFF = 0: No trend"); } // Test smoothing function testSmoothingFunction(); System.out.println("=== MACD Indicator Test End ==="); } /** * Generate test price data * @param count Number of data points * @return Price list */ private static List<BigDecimal> generateTestPrices(int count) { List<BigDecimal> prices = new ArrayList<>(); // Start from 100, simple upward trend with some random fluctuations BigDecimal basePrice = new BigDecimal(100); for (int i = 0; i < count; i++) { // Add random fluctuation between 0.1 and 0.5 BigDecimal price = basePrice.add(new BigDecimal(Math.random() * 0.4 + 0.1)); prices.add(price.setScale(2, BigDecimal.ROUND_HALF_UP)); basePrice = price; } return prices; } /** * Test MACD bars smoothing function */ private static void testSmoothingFunction() { System.out.println("=== MACD Bars Smoothing Function Test ==="); MACD macd = new MACD(); macd.setMacdBarsMultiplier(2); macd.setMacdBarsSmoothingPeriod(3); // 3-day smoothing // Generate test price data with more fluctuations List<BigDecimal> prices = generateVolatileTestPrices(30); macd.calculate(prices); System.out.println("Price count: " + prices.size()); System.out.println("MACD parameters: fast=" + macd.getFastPeriod() + ", slow=" + macd.getSlowPeriod() + ", signal=" + macd.getSignalPeriod()); System.out.println("MACD bars parameters: multiplier=" + macd.getMacdBarsMultiplier() + ", smoothing=" + macd.getMacdBarsSmoothingPeriod()); System.out.println(); System.out.println("Smoothed MACD Results:"); System.out.println("DIF: " + macd.getDif()); System.out.println("DEA: " + macd.getDea()); System.out.println("MACD Bars: " + macd.getMacdBar()); System.out.println(); } /** * Generate test price data with more fluctuations * @param count Number of data points * @return Price list */ private static List<BigDecimal> generateVolatileTestPrices(int count) { List<BigDecimal> prices = new ArrayList<>(); // Start from 100 with more random fluctuations BigDecimal basePrice = new BigDecimal(100); for (int i = 0; i < count; i++) { // Add random fluctuation between -1.0 and 1.0 BigDecimal price = basePrice.add(new BigDecimal(Math.random() * 2 - 1)); prices.add(price.setScale(2, BigDecimal.ROUND_HALF_UP)); basePrice = price; } return prices; } } src/main/java/com/xcong/excoin/modules/okxNewPrice/indicator/macdAndMatrategy/MacdMaStrategy.java
@@ -1,6 +1,10 @@ package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy; import com.xcong.excoin.modules.okxNewPrice.indicator.*; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.CoinEnums; import com.xcong.excoin.modules.okxNewPrice.okxWs.enums.OrderParamEnums; import com.xcong.excoin.modules.okxNewPrice.okxWs.param.TradeRequestParam; import java.math.BigDecimal; import java.util.List; @@ -51,43 +55,15 @@ private final RSI rsi = new RSI(10); // 波动率指标(15周期):ETH波动频繁,使用更短周期捕捉市场变化 private final Volatility volatility = new Volatility(15); /** * 策略配置参数(适配ETH合约特点) */ // 止损比例(ETH波动较大,设置2.5%) private final BigDecimal stopLossRatio = new BigDecimal("0.025"); // 止盈比例(ETH趋势明显,设置6%) private final BigDecimal takeProfitRatio = new BigDecimal("0.06"); // 账户初始余额(USD) private BigDecimal accountBalance = new BigDecimal("10000"); // 每次交易风险比例(ETH合约风险较高,设置0.8%) private final BigDecimal riskPerTrade = new BigDecimal("0.008"); // 杠杆倍数(ETH合约建议使用3-5倍,这里使用4倍) private final int leverage = 4; /** * 策略运行状态变量 */ // 当前持仓状态:空仓/多头/空头 private PositionStatus position = PositionStatus.FLAT; // 最新价格:用于策略判断 private BigDecimal lastPrice; // 当前市场趋势:牛市/熊市 private TrendDirection trend; // 持仓均价:用于计算盈亏和止损止盈 private BigDecimal entryPrice; // 止损价格:根据止损比例计算 private BigDecimal stopLossPrice; // 止盈价格:根据止盈比例计算 private BigDecimal takeProfitPrice; // 当前持仓数量(ETH) private BigDecimal positionSize = BigDecimal.ZERO; // 已用保证金(USD) private BigDecimal usedMargin = BigDecimal.ZERO; /** * 策略执行的主入口方法 * * 第一步:更新所有技术指标 * * @param prices 历史价格数据列表 * @throws IllegalArgumentException 如果价格数据不足200个 @@ -97,31 +73,44 @@ if (prices.size() < 200) { throw new IllegalArgumentException("至少需要200个价格数据点才能运行该策略"); } // 第一步:更新所有技术指标 updateIndicators(prices); // 第二步:确定当前市场趋势 determineTrend(prices); trend = getTrend(); // 第三步:检查是否需要跳过当前交易 if (shouldSkipTrade()) { return; } } // 第四步:根据持仓状态执行相应的交易逻辑 if (position == PositionStatus.FLAT) { // 空仓状态下检查入场信号 checkEntrySignal(); } else { // 持仓状态下检查离场信号 checkExitSignal(); } /** * 第二步:确定当前市场趋势 */ public TrendDirection getTrend(){ return determineTrend(); } /** * 第三步:检查是否需要跳过当前交易 */ public boolean getSkip(){ return shouldSkipTrade(); } /** * 第四步:是否允许开仓 */ public TradeRequestParam getOrderParamOpen(TradeRequestParam tradeRequestParam){ return checkEntrySignal(tradeRequestParam); } /** * 第五步:是否允许平仓 */ public TradeRequestParam getOrderParamClose(TradeRequestParam tradeRequestParam){ return checkExitSignal(tradeRequestParam); } /** * 更新所有技术指标的计算结果 * * * @param prices 历史价格数据列表 */ private void updateIndicators(List<BigDecimal> prices) { @@ -138,15 +127,15 @@ lastPrice = prices.get(prices.size()-1); } /** * 确定当前市场趋势(牛市/熊市) * * @param prices 历史价格数据列表 */ private void determineTrend(List<BigDecimal> prices) { private TrendDirection determineTrend() { BigDecimal currentMa100 = ma100.getMa(); // 获取当前100日移动平均线 // 根据最新价格与100日MA的关系判断趋势:价格高于100日MA为牛市,否则为熊市 trend = lastPrice.compareTo(currentMa100) > 0 ? return lastPrice.compareTo(currentMa100) > 0 ? TrendDirection.BULLISH : TrendDirection.BEARISH; } @@ -176,7 +165,9 @@ /** * 检查是否满足入场信号条件 */ private void checkEntrySignal() { private TradeRequestParam checkEntrySignal(TradeRequestParam tradeRequestParam) { String poSide = null; String side = null; BigDecimal currentMa30 = ma30.getMa(); // 获取当前30日移动平均线 // 获取MACD的最新值用于判断动量方向 @@ -206,9 +197,10 @@ currentVolatility.compareTo(new BigDecimal("0.05")) < 0; if (macdBull && macdBarPositive && priceAboveMAWithStrength && rsiInRange && volatilityModerate) { enterLong(); poSide = CoinEnums.POSSIDE_LONG.getCode(); side = CoinEnums.SIDE_BUY.getCode(); } } else { } else if (trend == TrendDirection.BEARISH){ // 熊市入场条件增强: // 1. MACD空头信号(DIF<DEA) // 2. MACD柱状图为负(确认空头力量) @@ -224,152 +216,49 @@ currentVolatility.compareTo(new BigDecimal("0.05")) < 0; if (macdBear && macdBarNegative && priceBelowMAWithStrength && rsiInRange && volatilityModerate) { enterShort(); poSide = CoinEnums.POSSIDE_SHORT.getCode(); side = CoinEnums.SIDE_SELL.getCode(); } } tradeRequestParam.setPosSide(poSide); tradeRequestParam.setSide(side); return tradeRequestParam; } /** * 检查是否满足离场信号条件 */ private void checkExitSignal() { private TradeRequestParam checkExitSignal(TradeRequestParam tradeRequestParam) { BigDecimal currentMa30 = ma30.getMa(); // 获取当前30日移动平均线 // 获取MACD的最新值用于判断动量变化 BigDecimal currentDif = macd.getDif(); BigDecimal currentDea = macd.getDea(); if (position == PositionStatus.LONG) { String posSide = tradeRequestParam.getPosSide(); if (posSide == CoinEnums.POSSIDE_LONG.getCode()) { // 多头持仓离场条件: // 1. MACD转为空头信号(DIF<DEA) // 2. 价格跌破30日MA // 3. 价格触及止损价格 // 4. 价格触及止盈价格 boolean macdExit = currentDif.compareTo(currentDea) < 0; boolean priceExit = lastPrice.compareTo(currentMa30) < 0; boolean stopLossExit = lastPrice.compareTo(stopLossPrice) <= 0; boolean takeProfitExit = lastPrice.compareTo(takeProfitPrice) >= 0; if (macdExit || priceExit || stopLossExit || takeProfitExit) { if (stopLossExit) { System.out.println("触发止损 - "); } else if (takeProfitExit) { System.out.println("触发止盈 - "); } exitPosition(); if (macdExit && priceExit) { tradeRequestParam.setSide(CoinEnums.SIDE_SELL.getCode()); } } else { } else if (posSide == CoinEnums.POSSIDE_SHORT.getCode()){ // 空头持仓离场条件: // 1. MACD转为多头信号(DIF>DEA) // 2. 价格突破30日MA // 3. 价格触及止损价格 // 4. 价格触及止盈价格 boolean macdExit = currentDif.compareTo(currentDea) > 0; boolean priceExit = lastPrice.compareTo(currentMa30) > 0; boolean stopLossExit = lastPrice.compareTo(stopLossPrice) >= 0; boolean takeProfitExit = lastPrice.compareTo(takeProfitPrice) <= 0; if (macdExit || priceExit || stopLossExit || takeProfitExit) { if (stopLossExit) { System.out.println("触发止损 - "); } else if (takeProfitExit) { System.out.println("触发止盈 - "); } exitPosition(); if (macdExit && priceExit) { tradeRequestParam.setSide(CoinEnums.SIDE_BUY.getCode()); } } return tradeRequestParam; } /** * 计算基于风险的仓位大小 * * @return 计算得到的仓位大小(ETH数量) */ private BigDecimal calculatePositionSize() { // 计算每次交易可承受的最大风险金额 BigDecimal maxRiskAmount = accountBalance.multiply(riskPerTrade); // 计算单合约风险金额(价格波动 * 数量) BigDecimal priceRisk = entryPrice.subtract(stopLossPrice).abs(); // 计算基础仓位大小(不考虑杠杆) BigDecimal basePositionSize = maxRiskAmount.divide(priceRisk, 6, BigDecimal.ROUND_HALF_UP); // 应用杠杆计算实际仓位大小 BigDecimal leveragedPositionSize = basePositionSize.multiply(new BigDecimal(leverage)) .setScale(4, BigDecimal.ROUND_DOWN); return leveragedPositionSize; } /** * 执行开多仓操作 */ private void enterLong() { position = PositionStatus.LONG; // 更新持仓状态为多头 entryPrice = lastPrice; // 记录入场价格 // 多头止损价格 = 入场价格 * (1 - 止损比例) stopLossPrice = entryPrice.multiply(BigDecimal.ONE.subtract(stopLossRatio)) .setScale(2, BigDecimal.ROUND_HALF_UP); // 多头止盈价格 = 入场价格 * (1 + 止盈比例) takeProfitPrice = entryPrice.multiply(BigDecimal.ONE.add(takeProfitRatio)) .setScale(2, BigDecimal.ROUND_HALF_UP); // 计算仓位大小 positionSize = calculatePositionSize(); // 计算已用保证金(不考虑手续费) usedMargin = positionSize.multiply(entryPrice).divide(new BigDecimal(leverage), 2, BigDecimal.ROUND_HALF_UP); // 实际交易中,这里会执行买入操作 System.out.println("开多仓 @ " + lastPrice + ", 止损价格: " + stopLossPrice + ", 止盈价格: " + takeProfitPrice); System.out.println("仓位大小: " + positionSize + " ETH, 已用保证金: " + usedMargin + " USD, 账户余额: " + accountBalance + " USD"); } /** * 执行开空仓操作 */ private void enterShort() { position = PositionStatus.SHORT; // 更新持仓状态为空头 entryPrice = lastPrice; // 记录入场价格 // 空头止损价格 = 入场价格 * (1 + 止损比例) stopLossPrice = entryPrice.multiply(BigDecimal.ONE.add(stopLossRatio)) .setScale(2, BigDecimal.ROUND_HALF_UP); // 空头止盈价格 = 入场价格 * (1 - 止盈比例) takeProfitPrice = entryPrice.multiply(BigDecimal.ONE.subtract(takeProfitRatio)) .setScale(2, BigDecimal.ROUND_HALF_UP); // 计算仓位大小 positionSize = calculatePositionSize(); // 计算已用保证金(不考虑手续费) usedMargin = positionSize.multiply(entryPrice).divide(new BigDecimal(leverage), 2, BigDecimal.ROUND_HALF_UP); // 实际交易中,这里会执行卖出操作 System.out.println("开空仓 @ " + lastPrice + ", 止损价格: " + stopLossPrice + ", 止盈价格: " + takeProfitPrice); System.out.println("仓位大小: " + positionSize + " ETH, 已用保证金: " + usedMargin + " USD, 账户余额: " + accountBalance + " USD"); } /** * 执行平仓操作 */ private void exitPosition() { // 计算交易盈亏 BigDecimal profitLoss; if (position == PositionStatus.LONG) { // 多头盈亏 = (平仓价格 - 入场价格) * 持仓数量 profitLoss = lastPrice.subtract(entryPrice).multiply(positionSize); } else { // 空头盈亏 = (入场价格 - 平仓价格) * 持仓数量 profitLoss = entryPrice.subtract(lastPrice).multiply(positionSize); } // 更新账户余额 accountBalance = accountBalance.add(profitLoss).setScale(2, BigDecimal.ROUND_HALF_UP); // 实际交易中,这里会执行平仓操作 System.out.println("平仓 @ " + lastPrice + ", 盈亏: " + profitLoss.setScale(2, BigDecimal.ROUND_HALF_UP) + " USD"); System.out.println("最新账户余额: " + accountBalance + " USD"); // 重置持仓状态 position = PositionStatus.FLAT; positionSize = BigDecimal.ZERO; usedMargin = BigDecimal.ZERO; } // 枚举定义 enum PositionStatus { FLAT, LONG, SHORT }