diff --git a/@libs/indicators/MAX.ts b/@libs/indicators/MAX.ts index 4729f9e..c5adf86 100644 --- a/@libs/indicators/MAX.ts +++ b/@libs/indicators/MAX.ts @@ -13,28 +13,31 @@ export const useMAX = (source: Series, period: number) => { // 单调队列: 保存当前窗口期内,比最新的元素小的元素的索引 const queue = useRef([]); useEffect(() => { - const i = source.length - 2; - if (i < 0) return; + const previousIndex = source.previousIndex; + if (previousIndex < 0) return; // 从栈顶开始,移除所有小于当前值的元素 while ( queue.current.length > 0 && - source[queue.current[queue.current.length - 1]] <= source[i] + source[queue.current[queue.current.length - 1]] <= source.previousValue ) { queue.current.pop(); } // 将当前值入栈 - queue.current.push(i); + queue.current.push(previousIndex); // 移除超出窗口期的元素 (通常一次只会移除一个) - while (queue.current.length > 0 && queue.current[0] <= i - period) { + while ( + queue.current.length > 0 && + queue.current[0] <= previousIndex - period + ) { queue.current.shift(); } }, [source.length]); useEffect(() => { - const i = source.length - 1; - if (i < 0) return; + const currentIndex = source.currentIndex; + if (currentIndex < 0) return; // 队首元素即为当前窗口期内的最小值 - MAX[i] = Math.max( - source[source.length - 1], + MAX[currentIndex] = Math.max( + source.currentValue, source[queue.current[0]] || -Infinity ); }); diff --git a/@libs/indicators/MIN.ts b/@libs/indicators/MIN.ts index 0e68c9f..1a681e4 100644 --- a/@libs/indicators/MIN.ts +++ b/@libs/indicators/MIN.ts @@ -13,28 +13,31 @@ export const useMIN = (source: Series, period: number) => { // 单调队列: 保存当前窗口期内,比最新的元素小的元素的索引 const queue = useRef([]); useEffect(() => { - const i = source.length - 2; - if (i < 0) return; + const previousIndex = source.previousIndex; + if (previousIndex < 0) return; // 从栈顶开始,移除所有大于当前值的元素 while ( queue.current.length > 0 && - source[queue.current[queue.current.length - 1]] >= source[i] + source[queue.current[queue.current.length - 1]] >= source.previousValue ) { queue.current.pop(); } // 将当前值入栈 - queue.current.push(i); + queue.current.push(previousIndex); // 移除超出窗口期的元素 (通常一次只会移除一个) - while (queue.current.length > 0 && queue.current[0] <= i - period) { + while ( + queue.current.length > 0 && + queue.current[0] <= previousIndex - period + ) { queue.current.shift(); } }, [source.length]); useEffect(() => { - const i = source.length - 1; - if (i < 0) return; + const currentIndex = source.currentIndex; + if (currentIndex < 0) return; // 队首元素即为当前窗口期内的最小值 - MIN[i] = Math.min( - source[source.length - 1], + MIN[currentIndex] = Math.min( + source.currentValue, source[queue.current[0]] || Infinity ); }); diff --git a/@libs/indicators/RANGE.ts b/@libs/indicators/RANGE.ts index 47bf636..452316c 100644 --- a/@libs/indicators/RANGE.ts +++ b/@libs/indicators/RANGE.ts @@ -13,9 +13,9 @@ export const useRANGE = (source: Series, period: number) => { chart: "new", }); useEffect(() => { - const i = source.length - 1; - if (i < 0) return; - Range[i] = max[i] - min[i]; + const currentIndex = source.currentIndex; + if (currentIndex < 0) return; + Range[currentIndex] = max.currentValue - min.currentValue; }); return Range; }; diff --git a/@libs/indicators/SAR.ts b/@libs/indicators/SAR.ts index 156f138..5cdbfe7 100644 --- a/@libs/indicators/SAR.ts +++ b/@libs/indicators/SAR.ts @@ -22,72 +22,76 @@ export const useSAR = ( const direction = useSeries("direction", high); // 1 for upward, -1 for downward, 0 for unknown useEffect(() => { - const i = high.length - 1; + const i = high.currentIndex; if (i < 0) return; if (i === 0) { - MAX[i] = high[i]; - MIN[i] = low[i]; + MAX[i] = high.currentValue; + MIN[i] = low.currentValue; AF[i] = start; - U[i] = high[i]; - D[i] = low[i]; + U[i] = high.currentValue; + D[i] = low.currentValue; direction[i] = 0; return; } // U[i] = U[i - 1]; // D[i] = D[i - 1]; - if (direction[i - 1] !== 1) { + if (direction.previousValue !== 1) { // 按下行处理 // 检查是否向上反转 - if (high[i] >= U[i - 1]) { + if (high.currentValue >= U.previousValue) { direction[i] = 1; - MAX[i] = high[i]; - MIN[i] = low[i]; + MAX[i] = high.currentValue; + MIN[i] = low.currentValue; U[i] = NaN; // MAX[i]; - D[i] = MIN[i - 1]; // 从上一个区间的最小值开始 + D[i] = MIN.previousValue; // 从上一个区间的最小值开始 AF[i] = start; return; } - direction[i] = direction[i - 1]; + direction[i] = direction.previousValue; // 检查是否创新低 - if (low[i] < MIN[i - 1]) { - AF[i] = Math.min(max, AF[i - 1] + increment); + if (low.currentValue < MIN.previousValue) { + AF[i] = Math.min(max, AF.previousValue + increment); } else { - AF[i] = AF[i - 1]; + AF[i] = AF.previousValue; } // 维护区间极值 - MAX[i] = Math.max(MAX[i - 1], high[i]); - MIN[i] = Math.min(MIN[i - 1], low[i]); + MAX[i] = Math.max(MAX.previousValue, high.currentValue); + MIN[i] = Math.min(MIN.previousValue, low.currentValue); - U[i] = U[i - 1] + AF[i] * (MIN[i - 1] - U[i - 1]); + U[i] = + U.previousValue + + AF.currentValue * (MIN.previousValue - U.previousValue); D[i] = NaN; // MIN[i]; } else { // 按上行处理 // 检查是否向下反转 - if (low[i] <= D[i - 1]) { + if (low.currentValue <= D.previousValue) { direction[i] = -1; - MAX[i] = high[i]; - MIN[i] = low[i]; + MAX[i] = high.currentValue; + MIN[i] = low.currentValue; AF[i] = start; - U[i] = MAX[i - 1]; // 从上一个区间的最大值开始 + U[i] = MAX.previousValue; // 从上一个区间的最大值开始 D[i] = NaN; // MIN[i]; return; } - direction[i] = direction[i - 1]; + direction[i] = direction.previousValue; // 检查是否创新高 - if (high[i] < MAX[i - 1]) { - AF[i] = Math.min(max, AF[i - 1] + increment); + if (high.currentValue < MAX.previousValue) { + AF[i] = Math.min(max, AF.previousValue + increment); } else { - AF[i] = AF[i - 1]; + AF[i] = AF.previousValue; } // 维护区间极值 - MAX[i] = Math.max(MAX[i - 1], high[i]); - MIN[i] = Math.min(MIN[i - 1], low[i]); + MAX[i] = Math.max(MAX.previousValue, high.currentValue); + MIN[i] = Math.min(MIN.previousValue, low.currentValue); U[i] = NaN; //MAX[i]; - D[i] = D[i - 1] + AF[i] * (MAX[i - 1] - D[i - 1]); + D[i] = + D.previousValue + + AF.currentValue * (MAX.previousValue - D.previousValue); } }); return { U, D, direction, MAX, MIN, AF }; diff --git a/@libs/indicators/TD.ts b/@libs/indicators/TD.ts index 235e7d6..568e599 100644 --- a/@libs/indicators/TD.ts +++ b/@libs/indicators/TD.ts @@ -6,14 +6,14 @@ export const useTD = (source: Series) => { }); useEffect(() => { - const i = source.length - 1; - if (i < 0) return; - TD[i] = 0; - if (source[i] > source[i - 4]) { - TD[i] = Math.max(0, TD[i - 1] ?? 0) + 1; + const currentIndex = source.currentIndex; + if (currentIndex < 0) return; + TD[currentIndex] = 0; + if (source.currentValue > source[currentIndex - 4]) { + TD[currentIndex] = Math.max(0, TD.previousValue ?? 0) + 1; } - if (source[i] < source[i - 4]) { - TD[i] = Math.min(0, TD[i - 1] ?? 0) - 1; + if (source.currentValue < source[currentIndex - 4]) { + TD[currentIndex] = Math.min(0, TD.previousValue ?? 0) - 1; } }); diff --git a/@libs/indicators/ZIGZAG.ts b/@libs/indicators/ZIGZAG.ts index f22f2e6..f8f32cd 100644 --- a/@libs/indicators/ZIGZAG.ts +++ b/@libs/indicators/ZIGZAG.ts @@ -26,7 +26,7 @@ export const useZigZag = (high: Series, low: Series, period: number) => { useEffect(() => { for ( let i = Math.max(0, lastLowPeak.length) - 1; - i < high.length - 1; + i < high.currentIndex; i++ ) { if (i <= 0) { @@ -100,11 +100,11 @@ export const useZigZag = (high: Series, low: Series, period: number) => { } } if (high.length >= 1) { - lastHighPeak[high.length - 1] = NaN; - lastLowPeak[high.length - 1] = NaN; - currentZigzagValue[high.length - 1] = NaN; - lastZigzagValue[high.length - 1] = NaN; - secondLastZigzagValue[high.length - 1] = NaN; + lastHighPeak[high.currentIndex] = NaN; + lastLowPeak[high.currentIndex] = NaN; + currentZigzagValue[high.currentIndex] = NaN; + lastZigzagValue[high.currentIndex] = NaN; + secondLastZigzagValue[high.currentIndex] = NaN; } }); diff --git a/@libs/utils/useSeriesMap.ts b/@libs/utils/useSeriesMap.ts index 8fb48c7..c0720a1 100644 --- a/@libs/utils/useSeriesMap.ts +++ b/@libs/utils/useSeriesMap.ts @@ -10,7 +10,7 @@ export const useSeriesMap = ( ) => { const series = useSeries(name, parent, tags); useEffect(() => { - const i = parent.length - 1; + const i = parent.currentIndex; if (i < 0) return; series[i] = fn(i, series); }); diff --git a/@libs/utils/useThrottle.ts b/@libs/utils/useThrottle.ts index fc5151f..9f89a65 100644 --- a/@libs/utils/useThrottle.ts +++ b/@libs/utils/useThrottle.ts @@ -7,19 +7,19 @@ export const useThrottle = (series: Series, period: number) => { const ret = useSeries(`THROTTLE(${series.name},${period})`, series, {}); const openIdxRef = useRef(0); useEffect(() => { - const i = series.length - 1; - if (i < 0) return; - if (i < openIdxRef.current) { - ret[i] = NaN; + const currentIndex = series.currentIndex; + if (currentIndex < 0) return; + if (currentIndex < openIdxRef.current) { + ret[currentIndex] = NaN; return; } - if (series[i - 1]) { - openIdxRef.current = i + period; - ret[i - 1] = series[i - 1]; - ret[i] = NaN; + if (series[currentIndex - 1]) { + openIdxRef.current = currentIndex + period; + ret[currentIndex - 1] = series.previousValue; + ret[currentIndex] = NaN; return; } - ret[i] = series[i]; + ret[currentIndex] = series.currentValue; }); return ret; }; diff --git a/@libs/utils/useTopK.ts b/@libs/utils/useTopK.ts index d89fb2d..dd1d049 100644 --- a/@libs/utils/useTopK.ts +++ b/@libs/utils/useTopK.ts @@ -7,26 +7,26 @@ export const useTopK = (series: Series, k: number, period: number) => { const indexes = useRef([]); const ret = useSeries(`TOP_K(${series.name},${k},${period})`, series, {}); - const i = series.length - 1; + const currentIndex = series.currentIndex; useEffect(() => { - if (i < 0) return; + if (currentIndex < 0) return; let isChanged = false; // remove expired indexes - if (indexes.current[0] <= i - period) { + if (indexes.current[0] <= currentIndex - period) { indexes.current.shift(); isChanged = true; } - if (series[i - 1]) { - indexes.current.push(i - 1); + if (series.previousValue) { + indexes.current.push(currentIndex - 1); isChanged = true; } if (isChanged) { const sorted = [...indexes.current].sort((a, b) => series[b] - series[a]); - ret[i] = series[sorted[Math.min(k, sorted.length) - 1]]; + ret[currentIndex] = series[sorted[Math.min(k, sorted.length) - 1]]; } else { - ret[i] = ret[i - 1] ?? NaN; + ret[currentIndex] = ret.previousValue ?? NaN; } - }, [i]); + }, [currentIndex]); return ret; }; diff --git a/@models/CCI-trending.ts b/@models/CCI-trending.ts index d93ba9a..0c22a2f 100644 --- a/@models/CCI-trending.ts +++ b/@models/CCI-trending.ts @@ -37,7 +37,6 @@ import { */ export default () => { const { product_id, high, low, close } = useParamOHLC("SomeKey"); - const idx = close.length - 2; const cciFast = useCCI(high, low, close, 18); const cciSlow = useCCI(high, low, close, 54); @@ -48,42 +47,52 @@ export default () => { accountInfo.account_id, product_id ); - const stopLossPrice = useRef(close[idx] - 6 * atr108[idx]); + const stopLossPrice = useRef(close.previousValue - 6 * atr108.previousValue); useEffect(() => { - if (idx < 108) return; // 确保ATR有足够的数据 + if (close.previousIndex < 108) return; // 确保ATR有足够的数据 // 空头信号:当CCI快线进入正200以上,且下穿慢线时,建立空单。 - if (cciFast[idx] > 200 && cciFast[idx] < cciSlow[idx]) { + if ( + cciFast.previousValue > 200 && + cciFast.previousValue < cciSlow.previousValue + ) { setTargetVolume(-1); - stopLossPrice.current = close[idx] + 6 * atr108[idx]; // 使用6倍ATR的值作为停损点位。 + stopLossPrice.current = close.previousValue + 6 * atr108.previousValue; // 使用6倍ATR的值作为停损点位。 } // 多头信号:当CCI快线进入负200以下,且上穿慢线时,建立多单。 - if (cciFast[idx] < -200 && cciFast[idx] > cciSlow[idx]) { + if ( + cciFast.previousValue < -200 && + cciFast.previousValue > cciSlow.previousValue + ) { setTargetVolume(1); - stopLossPrice.current = close[idx] - 6 * atr108[idx]; // 使用6倍ATR的值作为停损点位。 + stopLossPrice.current = close.previousValue - 6 * atr108.previousValue; // 使用6倍ATR的值作为停损点位。 } // 多单平仓:当CCI快线进入正200以上,且下穿慢线时,平多单。 - if (targetVolume > 0 && cciFast[idx] > 200 && cciFast[idx] < cciSlow[idx]) { + if ( + targetVolume > 0 && + cciFast.previousValue > 200 && + cciFast.previousValue < cciSlow.previousValue + ) { setTargetVolume(0); } // 空单平仓:当CCI快线进入负200以下,且上穿慢线时,平空单。 if ( targetVolume < 0 && - cciFast[idx] < -200 && - cciFast[idx] > cciSlow[idx] + cciFast.previousValue < -200 && + cciFast.previousValue > cciSlow.previousValue ) { setTargetVolume(0); } // 停损条件 if ( - (targetVolume > 0 && close[idx] < stopLossPrice.current) || - (targetVolume < 0 && close[idx] > stopLossPrice.current) + (targetVolume > 0 && close.previousValue < stopLossPrice.current) || + (targetVolume < 0 && close.previousValue > stopLossPrice.current) ) { setTargetVolume(0); } - }, [idx]); + }, [close.previousIndex]); }; diff --git a/@models/R-Breaker.ts b/@models/R-Breaker.ts index 2416002..dae81ee 100644 --- a/@models/R-Breaker.ts +++ b/@models/R-Breaker.ts @@ -1,19 +1,19 @@ // R-Breaker 策略 // 高低周期的回转策略,根据高周期计算几个枢轴价位,然后在低周期上进行交易 -import { useParamOHLC, useSinglePosition } from "@libs"; +import { useParamOHLC, useRuleEffect, useSinglePosition } from "@libs"; export default () => { // 设定参数 const { product_id, close: C } = useParamOHLC("低周期"); // e.g. 1min const { high, low, close } = useParamOHLC("高周期"); // e.g. 1Day - const idx = close.length - 2; - const 中心价位 = (high[idx] + low[idx] + close[idx]) / 3; - const 突破买入价 = high[idx] + 2 * 中心价位 - 2 * low[idx]; - const 观察卖出价 = 中心价位 + high[idx] - low[idx]; - const 反转卖出价 = 2 * 中心价位 - low[idx]; - const 反转买入价 = 2 * 中心价位 - high[idx]; - const 观察买入价 = 中心价位 - (high[idx] - low[idx]); - const 突破卖出价 = low[idx] - 2 * (high[idx] - 中心价位); + const 中心价位 = + (high.previousValue + low.previousValue + close.previousValue) / 3; + const 突破买入价 = high.previousValue + 2 * 中心价位 - 2 * low.previousValue; + const 观察卖出价 = 中心价位 + high.previousValue - low.previousValue; + const 反转卖出价 = 2 * 中心价位 - low.previousValue; + const 反转买入价 = 2 * 中心价位 - high.previousValue; + const 观察买入价 = 中心价位 - (high.previousValue - low.previousValue); + const 突破卖出价 = low.previousValue - 2 * (high.previousValue - 中心价位); // 绘制采样点 const _中心价位 = useSeries("中心价位", C, { display: "line" }); const _突破买入价 = useSeries("突破买入价", C, { display: "line" }); @@ -23,45 +23,52 @@ export default () => { const _观察买入价 = useSeries("观察买入价", C, { display: "line" }); const _突破卖出价 = useSeries("突破卖出价", C, { display: "line" }); useEffect(() => { - const i = C.length - 1; - _中心价位[i] = 中心价位; - _突破买入价[i] = 突破买入价; - _观察卖出价[i] = 观察卖出价; - _反转卖出价[i] = 反转卖出价; - _反转买入价[i] = 反转买入价; - _观察买入价[i] = 观察买入价; - _突破卖出价[i] = 突破卖出价; + const currentIndex = C.currentIndex; + _中心价位[currentIndex] = 中心价位; + _突破买入价[currentIndex] = 突破买入价; + _观察卖出价[currentIndex] = 观察卖出价; + _反转卖出价[currentIndex] = 反转卖出价; + _反转买入价[currentIndex] = 反转买入价; + _观察买入价[currentIndex] = 观察买入价; + _突破卖出价[currentIndex] = 突破卖出价; }); // 设置仓位管理器 const pL = useSinglePosition(product_id, PositionVariant.LONG); const pS = useSinglePosition(product_id, PositionVariant.SHORT); - const price = C[C.length - 1]; + const price = C.currentValue; - useEffect(() => { - if (pL.volume === 0 && pS.volume === 0) { - if (price > 突破买入价) { - pL.setTargetVolume(1); - } - if (price < 突破卖出价) { - pS.setTargetVolume(1); - } - } - if ( - pL.volume > 0 && - high[high.length - 1] > 观察卖出价 && - price < 反转卖出价 - ) { + useRuleEffect( + "突破买入", + () => pL.volume === 0 && pS.volume === 0 && price > 突破买入价, + () => pL.setTargetVolume(1), + [close.previousIndex] + ); + + useRuleEffect( + "突破卖出", + () => pL.volume === 0 && pS.volume === 0 && price < 突破卖出价, + () => pS.setTargetVolume(1), + [close.previousIndex] + ); + + useRuleEffect( + "反转卖出", + () => pL.volume > 0 && high.currentValue > 观察卖出价 && price < 反转卖出价, + () => { pL.setTargetVolume(0); pS.setTargetVolume(1); - } - if ( - pS.volume > 0 && - low[low.length - 1] < 观察买入价 && - price > 反转买入价 - ) { + }, + [close.previousIndex] + ); + + useRuleEffect( + "反转买入", + () => pS.volume > 0 && low.currentValue < 观察买入价 && price > 反转买入价, + () => { pL.setTargetVolume(1); pS.setTargetVolume(0); - } - }, [idx]); + }, + [close.previousIndex] + ); }; diff --git a/@models/consecutive-up-down.ts b/@models/consecutive-up-down.ts index 773e022..c57ef38 100644 --- a/@models/consecutive-up-down.ts +++ b/@models/consecutive-up-down.ts @@ -6,14 +6,13 @@ // 并在附图中绘制连续上涨/下跌的信号的直方图 import { useParamNumber, + useParamOHLC, useSeriesMap, useSinglePosition, - useParamOHLC, } from "@libs"; export default () => { const { product_id, close } = useParamOHLC("SomeKey"); - const idx = close.length - 2; const X = useParamNumber("X", 3); const Y = useParamNumber("Y", 5); @@ -56,15 +55,15 @@ export default () => { useEffect(() => { //如果连续上涨 - if (consecutiveUp[idx] === 1) { + if (consecutiveUp.previousValue === 1) { pL.setTargetVolume(1); pS.setTargetVolume(0); } //如果连续上涨 - if (consecutiveDown[idx] === 1) { + if (consecutiveDown.previousValue === 1) { pL.setTargetVolume(0); pS.setTargetVolume(1); } - }, [idx]); + }, [close.previousIndex]); }; diff --git a/@models/double-ma.ts b/@models/double-ma.ts index cb23ac2..6b37290 100644 --- a/@models/double-ma.ts +++ b/@models/double-ma.ts @@ -1,13 +1,17 @@ // 双均线策略 (Double Moving Average) // 当短期均线由下向上穿越长期均线时做多 (金叉) // 当短期均线由上向下穿越长期均线时做空 (死叉) -import { useParamOHLC, useSMA, useSimplePositionManager } from "@libs"; +import { + useParamOHLC, + useRuleEffect, + useSMA, + useSimplePositionManager, +} from "@libs"; export default () => { // 使用收盘价序列 const { product_id, close } = useParamOHLC("SomeKey"); // NOTE: 使用当前 K 线的上一根 K 线的收盘价,保证策略在 K 线结束时才会执行 - const idx = close.length - 2; // 使用 20,60 均线 const sma20 = useSMA(close, 20); @@ -21,15 +25,17 @@ export default () => { product_id ); - useEffect(() => { - if (idx < 60) return; // 略过一开始不成熟的均线数据 - // 金叉开多平空 - if (sma20[idx] > sma60[idx]) { - setTargetVolume(1); - } - // 死叉开空平多 - if (sma20[idx] < sma60[idx]) { - setTargetVolume(-1); - } - }, [idx]); + useRuleEffect( + "金叉开多平空", + () => sma20.previousValue > sma60.previousValue, + () => setTargetVolume(1), + [close.currentIndex] + ); + + useRuleEffect( + "死叉开空平多", + () => sma20.previousValue < sma60.previousValue, + () => setTargetVolume(-1), + [close.currentIndex] + ); }; diff --git a/@models/dual-thrust.ts b/@models/dual-thrust.ts index b5aef82..b86d4e8 100644 --- a/@models/dual-thrust.ts +++ b/@models/dual-thrust.ts @@ -6,6 +6,7 @@ import { useMIN, useParamNumber, useParamOHLC, + useRuleEffect, useSimplePositionManager, } from "@libs"; @@ -29,19 +30,20 @@ export default () => { }, []); const Range = useSeries("Range", close); useEffect(() => { - const i = close.length - 1; - Range[i] = Math.max(HH[i] - LC[i], HC[i] - LL[i]); + const currentIndex = close.currentIndex; + Range[currentIndex] = Math.max( + HH.currentValue - LC.currentValue, + HC.currentValue - LL.currentValue + ); }); const Upper = useSeries("Upper", close, { display: "line" }); const Lower = useSeries("Lower", close, { display: "line" }); useEffect(() => { - const i = close.length - 1; - Upper[i] = open[i] + K1 * Range[i]; - Lower[i] = open[i] - K2 * Range[i]; + const currentIndex = close.currentIndex; + Upper[currentIndex] = open.currentValue + K1 * Range.currentValue; + Lower[currentIndex] = open.currentValue - K2 * Range.currentValue; }); - // NOTE: 使用当前 K 线的上一根 K 线的收盘价,保证策略在 K 线结束时才会执行 - const idx = close.length - 2; // 设置仓位管理器 const accountInfo = useAccountInfo(); const [targetVolume, setTargetVolume] = useSimplePositionManager( @@ -49,14 +51,17 @@ export default () => { product_id ); - useEffect(() => { - if (idx < N) return; // 略过一开始不成熟的均线数据 + useRuleEffect( + "突破上轨开多", + () => close.previousIndex >= N && close.previousValue > Upper.previousValue, + () => setTargetVolume(1), + [close.previousIndex] + ); - if (close[idx] > Upper[idx]) { - setTargetVolume(1); - } - if (close[idx] < Lower[idx]) { - setTargetVolume(-1); - } - }, [idx]); + useRuleEffect( + "突破下轨开空", + () => close.previousIndex >= N && close.previousValue < Lower.previousValue, + () => setTargetVolume(-1), + [close.previousIndex] + ); }; diff --git a/@models/greedy.ts b/@models/greedy.ts index 9cf03fe..7f89a93 100644 --- a/@models/greedy.ts +++ b/@models/greedy.ts @@ -11,20 +11,20 @@ export default () => { const flag = useRef(0); useEffect(() => { - const idx = open.length - 1; - const prevHigh = high[idx - 1]; - const prevLow = low[idx - 1]; - if (idx < 1) return; + const currentIndex = open.currentIndex; + const prevHigh = high.previousValue; + const prevLow = low.previousValue; + if (currentIndex < 1) return; const netVolume = pL.volume - pS.volume; if (netVolume === 0) { - if (open[idx] > prevHigh) { + if (open.currentValue > prevHigh) { if (pL.volume === 0) { flag.current = 1; pL.setTargetVolume(1); } } - if (open[idx] < prevLow) { + if (open.currentValue < prevLow) { if (pS.volume === 0) { flag.current = 1; pS.setTargetVolume(1); @@ -35,26 +35,32 @@ export default () => { //做多方向 if (netVolume > 0) { //如果上一根 - if (close[idx - 1] > open[idx - 1] && flag.current == 1) { + if (close.previousValue > open.previousValue && flag.current == 1) { pL.setTargetVolume(pL.volume + 1); } else { flag.current = 0; } const price = pL.position_price; - if (price < open[idx] * 0.995 || price > open[idx] * 1.005) { + if ( + price < open.currentValue * 0.995 || + price > open.currentValue * 1.005 + ) { pL.setTargetVolume(0); } } //做空方向 if (netVolume < 0) { - if (close[idx - 1] < open[idx - 1] && flag.current == 1) { + if (close.previousValue < open.previousValue && flag.current == 1) { pS.setTargetVolume(pS.volume + 1); } else { flag.current = 0; } const price = pS.position_price; - if (price < open[idx] * 0.995 || price > open[idx] * 1.005) { + if ( + price < open.currentValue * 0.995 || + price > open.currentValue * 1.005 + ) { pS.setTargetVolume(0); } } diff --git a/@models/grid.ts b/@models/grid.ts index e189005..fb0e7af 100755 --- a/@models/grid.ts +++ b/@models/grid.ts @@ -30,7 +30,7 @@ export default () => { const thePosition = accountInfo.positions.find( (pos) => pos.position_id === `${grid}` ); - if (!thePosition && close[close.length - 1] > grid) { + if (!thePosition && close.currentValue > grid) { orders.push({ client_order_id: UUID(), account_id: accountInfo.account_id, diff --git a/@models/hp-sma.ts b/@models/hp-sma.ts index f968035..089356b 100644 --- a/@models/hp-sma.ts +++ b/@models/hp-sma.ts @@ -3,6 +3,7 @@ import { useHPFilter, useParamNumber, useParamOHLC, + useRuleEffect, useSMA, useSimplePositionManager, } from "@libs"; @@ -11,7 +12,6 @@ export default () => { const { product_id, close } = useParamOHLC("SomeKey"); // 使用收盘价序列 const lambda = useParamNumber("HP Filter 平滑系数"); // NOTE: 使用当前 K 线的上一根 K 线的收盘价,保证策略在 K 线结束时才会执行 - const idx = close.length - 2; const hp = useHPFilter(close, lambda); @@ -26,15 +26,19 @@ export default () => { product_id ); - useEffect(() => { - if (idx < 60) return; // 略过一开始不成熟的均线数据 - // 金叉开多平空 - if (sma20[idx] > sma60[idx]) { - setTargetVolume(1); - } - // 死叉开空平多 - if (sma20[idx] < sma60[idx]) { - setTargetVolume(-1); - } - }, [idx]); + useRuleEffect( + "金叉开多平空", + () => + close.previousIndex >= 60 && sma20.previousValue > sma60.previousValue, + () => setTargetVolume(1), + [close.previousIndex] + ); + + useRuleEffect( + "死叉开空平多", + () => + close.previousIndex >= 60 && sma20.previousValue < sma60.previousValue, + () => setTargetVolume(-1), + [close.previousIndex] + ); }; diff --git a/@models/shannon.ts b/@models/shannon.ts index 21556c3..f7a3620 100644 --- a/@models/shannon.ts +++ b/@models/shannon.ts @@ -28,7 +28,7 @@ export default () => { // Re-balance the position useEffect(() => { if (close.length < 2) return; - const price = close[close.length - 1]; + const price = close.currentValue; const totalValue = accountInfo.money.equity + initial_balance; const totalValueToHold = totalValue * 0.5; // infer the volume to hold diff --git a/@models/super-trend.ts b/@models/super-trend.ts index 04ac85a..defb7c8 100644 --- a/@models/super-trend.ts +++ b/@models/super-trend.ts @@ -10,6 +10,7 @@ import { useATR, useParamNumber, useParamOHLC, + useRuleEffect, useSeriesMap, useSinglePosition, } from "@libs"; @@ -19,10 +20,9 @@ export default () => { const A = useParamNumber("A", 2); const { ATR: atr } = useATR(high, low, close, 14); - const idx = close.length - 2; const pL = useSinglePosition(product_id, PositionVariant.LONG); const pS = useSinglePosition(product_id, PositionVariant.SHORT); - const prevClose = (close[idx] + open[idx]) / 2; + const prevClose = (close.previousValue + open.previousValue) / 2; const upper = useSeriesMap( "Upper", close, @@ -35,19 +35,24 @@ export default () => { { display: "line" }, (i) => prevClose - A * atr[i] ); - useEffect(() => { - if (idx < 1) return; - if (close[idx] > upper[idx]) { - // 进入突破模式,做多 + useRuleEffect( + "突破模式", + () => close.previousIndex > 0 && close.previousValue > upper.previousValue, + () => { pL.setTargetVolume(1); pS.setTargetVolume(0); - } + }, + [close.previousIndex] + ); - if (close[idx] < lower[idx]) { - // 进入下跌模式,做空 + useRuleEffect( + "下跌模式", + () => close.previousIndex > 0 && close.previousValue < lower.previousValue, + () => { pL.setTargetVolume(0); pS.setTargetVolume(1); - } - }, [idx]); + }, + [close.previousIndex] + ); }; diff --git a/@models/turtle-long.ts b/@models/turtle-long.ts index c85f97d..ff6ca92 100644 --- a/@models/turtle-long.ts +++ b/@models/turtle-long.ts @@ -26,13 +26,13 @@ export default () => { const pL = useSinglePosition(product_id, PositionVariant.LONG); const { ATR } = useATR(high, low, close, 14); const price_break = useRef(0); - const idx = close.length - 2; + const previousIndex = close.previousIndex; useRuleEffect( "当价格突破N日价格的最高价时,首次开仓做多,同时记录当前价格", - () => close[idx] > HH[idx - 1] && pL.volume === 0, + () => close.previousValue > HH[previousIndex - 1] && pL.volume === 0, () => { - price_break.current = close[idx]; + price_break.current = close.previousValue; pL.setTargetVolume(1); }, [close.length] @@ -40,7 +40,7 @@ export default () => { useRuleEffect( "当多头头寸在突破过去10日最低价处止盈离市", - () => pL.volume > 0 && close[idx] < LL[idx - 1], + () => pL.volume > 0 && close.previousValue < LL[previousIndex - 1], () => { pL.setTargetVolume(0); }, @@ -49,14 +49,17 @@ export default () => { useRuleEffect( "当市价继续向盈利方向突破1/2 atr时加仓,止损位为2*atr", - () => pL.volume > 0 && close[idx] > price_break.current + 0.5 * ATR[idx], + () => + pL.volume > 0 && + close.previousValue > price_break.current + 0.5 * ATR.previousValue, () => { pL.setTargetVolume(pL.volume + 1); - pL.setStopLossPrice(price_break.current - 2 * ATR[idx]); - price_break.current = close[idx]; + pL.setStopLossPrice(price_break.current - 2 * ATR.previousValue); + price_break.current = close.previousValue; }, [close.length] ); + useSeriesMap( "净值", close,