@tradejs/strategies 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-37ZWRG3W.mjs +128 -0
- package/dist/chunk-H4MHFD4B.mjs +62 -0
- package/dist/{chunk-MOBKL73M.mjs → chunk-IMLNXICX.mjs} +183 -41
- package/dist/{chunk-XMRB45ZO.mjs → chunk-QVWMBYYM.mjs} +92 -45
- package/dist/chunk-SOVTOGY4.mjs +163 -0
- package/dist/{chunk-H2TU2YMA.mjs → chunk-TDUTYEGH.mjs} +111 -76
- package/dist/{chunk-GNQJ5TVU.mjs → chunk-UK4YHD2E.mjs} +34 -38
- package/dist/index.d.mts +294 -13
- package/dist/index.d.ts +294 -13
- package/dist/index.js +1825 -906
- package/dist/index.mjs +51 -18
- package/dist/{strategy-FYNNJDOH.mjs → strategy-ABIO65CR.mjs} +2 -33
- package/dist/{strategy-AFIGEHDS.mjs → strategy-D7H5J3C4.mjs} +1 -71
- package/dist/{strategy-LC2FSFVN.mjs → strategy-HQIPCUOY.mjs} +1 -72
- package/dist/{strategy-Y4SOK6FF.mjs → strategy-JQIJILHQ.mjs} +28 -109
- package/dist/strategy-QEIPAPY4.mjs +373 -0
- package/dist/{strategy-6TS2NFSC.mjs → strategy-WYN4FZ5S.mjs} +2 -125
- package/dist/{strategy-OI4WRB3S.mjs → strategy-XTUKPYPM.mjs} +466 -120
- package/package.json +5 -5
- package/dist/chunk-3PN7ZSJJ.mjs +0 -27
- package/dist/chunk-RDK2JK3K.mjs +0 -28
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
adaptiveMomentumRibbonManifest
|
|
3
|
-
|
|
2
|
+
adaptiveMomentumRibbonManifest,
|
|
3
|
+
config
|
|
4
|
+
} from "./chunk-IMLNXICX.mjs";
|
|
4
5
|
import {
|
|
5
6
|
__commonJS,
|
|
6
7
|
__esm,
|
|
@@ -10914,52 +10915,8 @@ var require_winston = __commonJS({
|
|
|
10914
10915
|
// src/AdaptiveMomentumRibbon/strategy.ts
|
|
10915
10916
|
import { createStrategyRuntime } from "@tradejs/node/strategies";
|
|
10916
10917
|
|
|
10917
|
-
// src/AdaptiveMomentumRibbon/config.ts
|
|
10918
|
-
var config = {
|
|
10919
|
-
ENV: "BACKTEST",
|
|
10920
|
-
INTERVAL: "15",
|
|
10921
|
-
MAKE_ORDERS: true,
|
|
10922
|
-
CLOSE_OPPOSITE_POSITIONS: false,
|
|
10923
|
-
BACKTEST_PRICE_MODE: "mid",
|
|
10924
|
-
AI_ENABLED: false,
|
|
10925
|
-
ML_ENABLED: false,
|
|
10926
|
-
ML_THRESHOLD: 0.1,
|
|
10927
|
-
MIN_AI_QUALITY: 3,
|
|
10928
|
-
FEE_PERCENT: 5e-3,
|
|
10929
|
-
MAX_LOSS_VALUE: 10,
|
|
10930
|
-
AMR_LOOKBACK_BARS: 400,
|
|
10931
|
-
AMR_MOMENTUM_PERIOD: 20,
|
|
10932
|
-
AMR_BUTTERWORTH_SMOOTHING: 3,
|
|
10933
|
-
AMR_WAIT_CLOSE: true,
|
|
10934
|
-
AMR_SHOW_INVALIDATION_LEVELS: true,
|
|
10935
|
-
AMR_SHOW_KELTNER_CHANNEL: true,
|
|
10936
|
-
AMR_KC_LENGTH: 20,
|
|
10937
|
-
AMR_KC_MA_TYPE: "EMA",
|
|
10938
|
-
AMR_ATR_LENGTH: 14,
|
|
10939
|
-
AMR_ATR_MULTIPLIER: 2,
|
|
10940
|
-
AMR_EXIT_ON_INVALIDATION: true,
|
|
10941
|
-
AMR_LINE_PLOTS: ["kcMidline", "kcUpper", "kcLower", "invalidationLevel"],
|
|
10942
|
-
LONG: {
|
|
10943
|
-
enable: true,
|
|
10944
|
-
direction: "LONG",
|
|
10945
|
-
TP: 2,
|
|
10946
|
-
SL: 1
|
|
10947
|
-
},
|
|
10948
|
-
SHORT: {
|
|
10949
|
-
enable: true,
|
|
10950
|
-
direction: "SHORT",
|
|
10951
|
-
TP: 2,
|
|
10952
|
-
SL: 1
|
|
10953
|
-
}
|
|
10954
|
-
};
|
|
10955
|
-
|
|
10956
10918
|
// src/AdaptiveMomentumRibbon/core.ts
|
|
10957
|
-
import {
|
|
10958
|
-
getLatestPineBooleanPlotValues,
|
|
10959
|
-
getLatestPineNumberPlotValues,
|
|
10960
|
-
runPineScript
|
|
10961
|
-
} from "@tradejs/node/pine";
|
|
10962
|
-
import { asPositiveInt, asPositiveNumber } from "@tradejs/core/math";
|
|
10919
|
+
import { asPositiveInt as asPositiveInt2, asPositiveNumber as asPositiveNumber2 } from "@tradejs/core/math";
|
|
10963
10920
|
|
|
10964
10921
|
// ../infra/src/logger.ts
|
|
10965
10922
|
var import_winston = __toESM(require_winston());
|
|
@@ -11000,11 +10957,437 @@ var logger = (0, import_winston.createLogger)({
|
|
|
11000
10957
|
]
|
|
11001
10958
|
});
|
|
11002
10959
|
|
|
10960
|
+
// src/AdaptiveMomentumRibbon/engine.ts
|
|
10961
|
+
import { asPositiveInt, asPositiveNumber } from "@tradejs/core/math";
|
|
10962
|
+
var PI = 3.14159265359;
|
|
10963
|
+
var MAX_PLOT_POINTS = 240;
|
|
10964
|
+
var toFinite = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
10965
|
+
var percentileNearestRank = (values, percent) => {
|
|
10966
|
+
if (!values.length) {
|
|
10967
|
+
return null;
|
|
10968
|
+
}
|
|
10969
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
10970
|
+
const rank = Math.max(
|
|
10971
|
+
0,
|
|
10972
|
+
Math.min(sorted.length - 1, Math.ceil(percent / 100 * sorted.length) - 1)
|
|
10973
|
+
);
|
|
10974
|
+
return sorted[rank] ?? null;
|
|
10975
|
+
};
|
|
10976
|
+
var stdev = (values) => {
|
|
10977
|
+
if (!values.length) {
|
|
10978
|
+
return null;
|
|
10979
|
+
}
|
|
10980
|
+
const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
10981
|
+
const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / values.length;
|
|
10982
|
+
return Math.sqrt(variance);
|
|
10983
|
+
};
|
|
10984
|
+
var pushAndTrim = (values, value, limit) => {
|
|
10985
|
+
values.push(value);
|
|
10986
|
+
if (values.length > limit) {
|
|
10987
|
+
values.splice(0, values.length - limit);
|
|
10988
|
+
}
|
|
10989
|
+
};
|
|
10990
|
+
var createMovingAverageState = (type, length) => {
|
|
10991
|
+
switch (type) {
|
|
10992
|
+
case "SMA":
|
|
10993
|
+
return {
|
|
10994
|
+
type,
|
|
10995
|
+
length,
|
|
10996
|
+
window: [],
|
|
10997
|
+
sum: 0
|
|
10998
|
+
};
|
|
10999
|
+
case "EMA":
|
|
11000
|
+
return {
|
|
11001
|
+
type,
|
|
11002
|
+
length,
|
|
11003
|
+
seedWindow: [],
|
|
11004
|
+
value: null
|
|
11005
|
+
};
|
|
11006
|
+
case "SMMA (RMA)":
|
|
11007
|
+
return {
|
|
11008
|
+
type,
|
|
11009
|
+
length,
|
|
11010
|
+
seedWindow: [],
|
|
11011
|
+
value: null
|
|
11012
|
+
};
|
|
11013
|
+
case "WMA":
|
|
11014
|
+
return {
|
|
11015
|
+
type,
|
|
11016
|
+
length,
|
|
11017
|
+
window: []
|
|
11018
|
+
};
|
|
11019
|
+
case "VWMA":
|
|
11020
|
+
return {
|
|
11021
|
+
type,
|
|
11022
|
+
length,
|
|
11023
|
+
priceWindow: [],
|
|
11024
|
+
volumeWindow: [],
|
|
11025
|
+
weightedSum: 0,
|
|
11026
|
+
volumeSum: 0
|
|
11027
|
+
};
|
|
11028
|
+
}
|
|
11029
|
+
};
|
|
11030
|
+
var updateMovingAverage = (state, price, volume) => {
|
|
11031
|
+
switch (state.type) {
|
|
11032
|
+
case "SMA": {
|
|
11033
|
+
state.window.push(price);
|
|
11034
|
+
state.sum += price;
|
|
11035
|
+
if (state.window.length > state.length) {
|
|
11036
|
+
state.sum -= state.window.shift() ?? 0;
|
|
11037
|
+
}
|
|
11038
|
+
if (state.window.length < state.length) {
|
|
11039
|
+
return null;
|
|
11040
|
+
}
|
|
11041
|
+
return state.sum / state.length;
|
|
11042
|
+
}
|
|
11043
|
+
case "EMA": {
|
|
11044
|
+
if (state.value == null) {
|
|
11045
|
+
state.seedWindow.push(price);
|
|
11046
|
+
if (state.seedWindow.length < state.length) {
|
|
11047
|
+
return null;
|
|
11048
|
+
}
|
|
11049
|
+
if (state.seedWindow.length === state.length) {
|
|
11050
|
+
state.value = state.seedWindow.reduce((sum, item) => sum + item, 0) / state.length;
|
|
11051
|
+
return state.value;
|
|
11052
|
+
}
|
|
11053
|
+
}
|
|
11054
|
+
const alpha = 2 / (state.length + 1);
|
|
11055
|
+
state.value = price * alpha + (state.value ?? price) * (1 - alpha);
|
|
11056
|
+
return state.value;
|
|
11057
|
+
}
|
|
11058
|
+
case "SMMA (RMA)": {
|
|
11059
|
+
if (state.value == null) {
|
|
11060
|
+
state.seedWindow.push(price);
|
|
11061
|
+
if (state.seedWindow.length < state.length) {
|
|
11062
|
+
return null;
|
|
11063
|
+
}
|
|
11064
|
+
if (state.seedWindow.length === state.length) {
|
|
11065
|
+
state.value = state.seedWindow.reduce((sum, item) => sum + item, 0) / state.length;
|
|
11066
|
+
return state.value;
|
|
11067
|
+
}
|
|
11068
|
+
}
|
|
11069
|
+
state.value = ((state.value ?? price) * (state.length - 1) + price) / state.length;
|
|
11070
|
+
return state.value;
|
|
11071
|
+
}
|
|
11072
|
+
case "WMA": {
|
|
11073
|
+
state.window.push(price);
|
|
11074
|
+
if (state.window.length > state.length) {
|
|
11075
|
+
state.window.shift();
|
|
11076
|
+
}
|
|
11077
|
+
if (state.window.length < state.length) {
|
|
11078
|
+
return null;
|
|
11079
|
+
}
|
|
11080
|
+
let weightedSum = 0;
|
|
11081
|
+
let weightSum = 0;
|
|
11082
|
+
for (let index = 0; index < state.window.length; index += 1) {
|
|
11083
|
+
const weight = index + 1;
|
|
11084
|
+
weightedSum += state.window[index] * weight;
|
|
11085
|
+
weightSum += weight;
|
|
11086
|
+
}
|
|
11087
|
+
return weightSum > 0 ? weightedSum / weightSum : null;
|
|
11088
|
+
}
|
|
11089
|
+
case "VWMA": {
|
|
11090
|
+
state.priceWindow.push(price);
|
|
11091
|
+
state.volumeWindow.push(volume);
|
|
11092
|
+
state.weightedSum += price * volume;
|
|
11093
|
+
state.volumeSum += volume;
|
|
11094
|
+
if (state.priceWindow.length > state.length) {
|
|
11095
|
+
const removedPrice = state.priceWindow.shift() ?? 0;
|
|
11096
|
+
const removedVolume = state.volumeWindow.shift() ?? 0;
|
|
11097
|
+
state.weightedSum -= removedPrice * removedVolume;
|
|
11098
|
+
state.volumeSum -= removedVolume;
|
|
11099
|
+
}
|
|
11100
|
+
if (state.priceWindow.length < state.length || state.volumeSum <= 0) {
|
|
11101
|
+
return null;
|
|
11102
|
+
}
|
|
11103
|
+
return state.weightedSum / state.volumeSum;
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
};
|
|
11107
|
+
var createAtrState = (length) => ({
|
|
11108
|
+
length,
|
|
11109
|
+
previousClose: null,
|
|
11110
|
+
trSeedWindow: [],
|
|
11111
|
+
atrValue: null
|
|
11112
|
+
});
|
|
11113
|
+
var updateAtr = (state, candle) => {
|
|
11114
|
+
const previousClose = state.previousClose;
|
|
11115
|
+
const tr = previousClose == null ? candle.high - candle.low : Math.max(
|
|
11116
|
+
candle.high - candle.low,
|
|
11117
|
+
Math.abs(candle.high - previousClose),
|
|
11118
|
+
Math.abs(candle.low - previousClose)
|
|
11119
|
+
);
|
|
11120
|
+
state.previousClose = candle.close;
|
|
11121
|
+
if (state.atrValue == null) {
|
|
11122
|
+
state.trSeedWindow.push(tr);
|
|
11123
|
+
if (state.trSeedWindow.length < state.length) {
|
|
11124
|
+
return null;
|
|
11125
|
+
}
|
|
11126
|
+
if (state.trSeedWindow.length === state.length) {
|
|
11127
|
+
state.atrValue = state.trSeedWindow.reduce((sum, value) => sum + value, 0) / state.length;
|
|
11128
|
+
return state.atrValue;
|
|
11129
|
+
}
|
|
11130
|
+
}
|
|
11131
|
+
state.atrValue = ((state.atrValue ?? tr) * (state.length - 1) + tr) / state.length;
|
|
11132
|
+
return state.atrValue;
|
|
11133
|
+
};
|
|
11134
|
+
var createButterworthState = (length) => ({
|
|
11135
|
+
length,
|
|
11136
|
+
prev1: null,
|
|
11137
|
+
prev2: null
|
|
11138
|
+
});
|
|
11139
|
+
var updateButterworth = (state, source) => {
|
|
11140
|
+
if (source == null) {
|
|
11141
|
+
return null;
|
|
11142
|
+
}
|
|
11143
|
+
const safeLength = Math.max(state.length, 1);
|
|
11144
|
+
const a = Math.exp(-Math.sqrt(2) * PI / safeLength);
|
|
11145
|
+
const b = 2 * a * Math.cos(Math.sqrt(2) * PI / safeLength);
|
|
11146
|
+
const c2 = b;
|
|
11147
|
+
const c3 = -(a * a);
|
|
11148
|
+
const c1 = 1 - c2 - c3;
|
|
11149
|
+
if (state.prev1 == null || state.prev2 == null) {
|
|
11150
|
+
state.prev1 = source;
|
|
11151
|
+
state.prev2 = source;
|
|
11152
|
+
return source;
|
|
11153
|
+
}
|
|
11154
|
+
const result = c1 * source + c2 * state.prev1 + c3 * state.prev2;
|
|
11155
|
+
state.prev2 = state.prev1;
|
|
11156
|
+
state.prev1 = result;
|
|
11157
|
+
return result;
|
|
11158
|
+
};
|
|
11159
|
+
var pushPlotPoint = (plotSeries, plotName, candle, value) => {
|
|
11160
|
+
if (value == null) {
|
|
11161
|
+
return;
|
|
11162
|
+
}
|
|
11163
|
+
const points = plotSeries[plotName] ?? [];
|
|
11164
|
+
points.push({
|
|
11165
|
+
time: candle.timestamp,
|
|
11166
|
+
value
|
|
11167
|
+
});
|
|
11168
|
+
if (points.length > MAX_PLOT_POINTS) {
|
|
11169
|
+
points.splice(0, points.length - MAX_PLOT_POINTS);
|
|
11170
|
+
}
|
|
11171
|
+
plotSeries[plotName] = points;
|
|
11172
|
+
};
|
|
11173
|
+
var evaluateAdaptiveMomentumRibbon = ({
|
|
11174
|
+
candles,
|
|
11175
|
+
config: config2,
|
|
11176
|
+
linePlots
|
|
11177
|
+
}) => {
|
|
11178
|
+
const momentumPeriod = asPositiveInt(config2.AMR_MOMENTUM_PERIOD, 20);
|
|
11179
|
+
const smoothingLength = asPositiveInt(config2.AMR_BUTTERWORTH_SMOOTHING, 3);
|
|
11180
|
+
const waitClose = Boolean(config2.AMR_WAIT_CLOSE);
|
|
11181
|
+
const confirmOnNextBar = Boolean(config2.AMR_CONFIRM_ON_NEXT_BAR);
|
|
11182
|
+
const minSignalOscAbs = asPositiveNumber(config2.AMR_MIN_SIGNAL_OSC_ABS, 0.55);
|
|
11183
|
+
const requireKcBias = Boolean(config2.AMR_REQUIRE_KC_BIAS);
|
|
11184
|
+
const minBarsBetweenSignals = asPositiveInt(
|
|
11185
|
+
config2.AMR_MIN_BARS_BETWEEN_SIGNALS,
|
|
11186
|
+
12
|
|
11187
|
+
);
|
|
11188
|
+
const showInvalidationLevels = Boolean(config2.AMR_SHOW_INVALIDATION_LEVELS);
|
|
11189
|
+
const showKeltnerChannel = Boolean(config2.AMR_SHOW_KELTNER_CHANNEL);
|
|
11190
|
+
const kcLength = asPositiveInt(config2.AMR_KC_LENGTH, 20);
|
|
11191
|
+
const kcMaType = config2.AMR_KC_MA_TYPE === "SMA" || config2.AMR_KC_MA_TYPE === "EMA" || config2.AMR_KC_MA_TYPE === "SMMA (RMA)" || config2.AMR_KC_MA_TYPE === "WMA" || config2.AMR_KC_MA_TYPE === "VWMA" ? config2.AMR_KC_MA_TYPE : "EMA";
|
|
11192
|
+
const atrLength = asPositiveInt(config2.AMR_ATR_LENGTH, 14);
|
|
11193
|
+
const atrMultiplier = asPositiveNumber(config2.AMR_ATR_MULTIPLIER, 2);
|
|
11194
|
+
const sourceWindow = [];
|
|
11195
|
+
const deviationWindow = [];
|
|
11196
|
+
const maState = createMovingAverageState(kcMaType, kcLength);
|
|
11197
|
+
const atrState = createAtrState(atrLength);
|
|
11198
|
+
const butterworthState = createButterworthState(smoothingLength);
|
|
11199
|
+
const plotSeries = {};
|
|
11200
|
+
let previousSignalOsc = null;
|
|
11201
|
+
let lastAcceptedSignalIndex = null;
|
|
11202
|
+
let pendingSignal = null;
|
|
11203
|
+
let invalidationLevel = null;
|
|
11204
|
+
let activeBuy = false;
|
|
11205
|
+
let activeSell = false;
|
|
11206
|
+
let lastSnapshot = {
|
|
11207
|
+
entryLong: false,
|
|
11208
|
+
entryShort: false,
|
|
11209
|
+
invalidated: false,
|
|
11210
|
+
activeBuy: false,
|
|
11211
|
+
activeSell: false,
|
|
11212
|
+
signalOsc: null,
|
|
11213
|
+
kcMidline: null,
|
|
11214
|
+
kcUpper: null,
|
|
11215
|
+
kcLower: null,
|
|
11216
|
+
invalidationLevel: null,
|
|
11217
|
+
lineValues: Object.fromEntries(
|
|
11218
|
+
linePlots.map((plotName) => [plotName, null])
|
|
11219
|
+
)
|
|
11220
|
+
};
|
|
11221
|
+
for (let index = 0; index < candles.length; index += 1) {
|
|
11222
|
+
const candle = candles[index];
|
|
11223
|
+
const previousCandle = index > 0 ? candles[index - 1] : null;
|
|
11224
|
+
const kcMidline = updateMovingAverage(
|
|
11225
|
+
maState,
|
|
11226
|
+
candle.close,
|
|
11227
|
+
Number(candle.volume ?? 0)
|
|
11228
|
+
);
|
|
11229
|
+
const atrValue = updateAtr(atrState, candle);
|
|
11230
|
+
const kcUpper = kcMidline != null && atrValue != null ? kcMidline + atrMultiplier * atrValue : null;
|
|
11231
|
+
const kcLower = kcMidline != null && atrValue != null ? kcMidline - atrMultiplier * atrValue : null;
|
|
11232
|
+
const sourceCandle = waitClose ? previousCandle : candle;
|
|
11233
|
+
const sourceClose = sourceCandle?.close ?? null;
|
|
11234
|
+
let signalOsc = null;
|
|
11235
|
+
let entryLong = false;
|
|
11236
|
+
let entryShort = false;
|
|
11237
|
+
if (sourceClose != null) {
|
|
11238
|
+
pushAndTrim(sourceWindow, sourceClose, momentumPeriod);
|
|
11239
|
+
if (sourceWindow.length >= momentumPeriod) {
|
|
11240
|
+
const medianValue = percentileNearestRank(sourceWindow, 50);
|
|
11241
|
+
const deviation = medianValue != null ? sourceClose - medianValue : null;
|
|
11242
|
+
if (deviation != null) {
|
|
11243
|
+
pushAndTrim(deviationWindow, deviation, momentumPeriod);
|
|
11244
|
+
}
|
|
11245
|
+
if (deviation != null && deviationWindow.length >= momentumPeriod) {
|
|
11246
|
+
const absoluteDeviationWindow = deviationWindow.map(
|
|
11247
|
+
(value) => Math.abs(value)
|
|
11248
|
+
);
|
|
11249
|
+
const medDeviation = percentileNearestRank(
|
|
11250
|
+
absoluteDeviationWindow,
|
|
11251
|
+
50
|
|
11252
|
+
);
|
|
11253
|
+
const scale = medDeviation === 0 ? stdev(sourceWindow) : medDeviation != null ? medDeviation * 1.4826 : null;
|
|
11254
|
+
const rawOsc = scale != null && scale !== 0 ? deviation / scale : 0;
|
|
11255
|
+
signalOsc = updateButterworth(butterworthState, rawOsc);
|
|
11256
|
+
}
|
|
11257
|
+
}
|
|
11258
|
+
}
|
|
11259
|
+
if (signalOsc != null && previousSignalOsc != null) {
|
|
11260
|
+
const rawEntryLong = previousSignalOsc <= 0 && signalOsc > 0;
|
|
11261
|
+
const rawEntryShort = previousSignalOsc >= 0 && signalOsc < 0;
|
|
11262
|
+
const strongEnough = Math.abs(signalOsc) >= minSignalOscAbs;
|
|
11263
|
+
const spacingOk = lastAcceptedSignalIndex == null || index - lastAcceptedSignalIndex >= minBarsBetweenSignals;
|
|
11264
|
+
const longKcBiasOk = !requireKcBias || kcMidline != null && candle.close > kcMidline;
|
|
11265
|
+
const shortKcBiasOk = !requireKcBias || kcMidline != null && candle.close < kcMidline;
|
|
11266
|
+
if (confirmOnNextBar) {
|
|
11267
|
+
if (pendingSignal?.direction === "LONG") {
|
|
11268
|
+
const pendingStillValid = pendingSignal.invalidationLevel == null || candle.low >= pendingSignal.invalidationLevel;
|
|
11269
|
+
const confirmed = pendingStillValid && signalOsc > 0 && strongEnough && longKcBiasOk;
|
|
11270
|
+
if (confirmed) {
|
|
11271
|
+
entryLong = true;
|
|
11272
|
+
invalidationLevel = pendingSignal.invalidationLevel;
|
|
11273
|
+
lastAcceptedSignalIndex = index;
|
|
11274
|
+
}
|
|
11275
|
+
pendingSignal = null;
|
|
11276
|
+
} else if (pendingSignal?.direction === "SHORT") {
|
|
11277
|
+
const pendingStillValid = pendingSignal.invalidationLevel == null || candle.high <= pendingSignal.invalidationLevel;
|
|
11278
|
+
const confirmed = pendingStillValid && signalOsc < 0 && strongEnough && shortKcBiasOk;
|
|
11279
|
+
if (confirmed) {
|
|
11280
|
+
entryShort = true;
|
|
11281
|
+
invalidationLevel = pendingSignal.invalidationLevel;
|
|
11282
|
+
lastAcceptedSignalIndex = index;
|
|
11283
|
+
}
|
|
11284
|
+
pendingSignal = null;
|
|
11285
|
+
}
|
|
11286
|
+
if (!entryLong && !entryShort && spacingOk) {
|
|
11287
|
+
if (rawEntryLong && strongEnough && longKcBiasOk && sourceCandle) {
|
|
11288
|
+
pendingSignal = {
|
|
11289
|
+
direction: "LONG",
|
|
11290
|
+
invalidationLevel: sourceCandle.low
|
|
11291
|
+
};
|
|
11292
|
+
} else if (rawEntryShort && strongEnough && shortKcBiasOk && sourceCandle) {
|
|
11293
|
+
pendingSignal = {
|
|
11294
|
+
direction: "SHORT",
|
|
11295
|
+
invalidationLevel: sourceCandle.high
|
|
11296
|
+
};
|
|
11297
|
+
}
|
|
11298
|
+
}
|
|
11299
|
+
} else {
|
|
11300
|
+
entryLong = rawEntryLong && strongEnough && spacingOk && longKcBiasOk;
|
|
11301
|
+
entryShort = rawEntryShort && strongEnough && spacingOk && shortKcBiasOk;
|
|
11302
|
+
}
|
|
11303
|
+
}
|
|
11304
|
+
if (signalOsc != null) {
|
|
11305
|
+
previousSignalOsc = signalOsc;
|
|
11306
|
+
}
|
|
11307
|
+
if (entryLong && sourceCandle) {
|
|
11308
|
+
if (invalidationLevel == null) {
|
|
11309
|
+
invalidationLevel = sourceCandle.low;
|
|
11310
|
+
}
|
|
11311
|
+
activeBuy = true;
|
|
11312
|
+
activeSell = false;
|
|
11313
|
+
}
|
|
11314
|
+
if (entryShort && sourceCandle) {
|
|
11315
|
+
if (invalidationLevel == null) {
|
|
11316
|
+
invalidationLevel = sourceCandle.high;
|
|
11317
|
+
}
|
|
11318
|
+
activeSell = true;
|
|
11319
|
+
activeBuy = false;
|
|
11320
|
+
}
|
|
11321
|
+
const checkCandle = waitClose ? previousCandle : candle;
|
|
11322
|
+
let invalidated = false;
|
|
11323
|
+
if (activeBuy && checkCandle && invalidationLevel != null && checkCandle.low < invalidationLevel) {
|
|
11324
|
+
invalidated = true;
|
|
11325
|
+
}
|
|
11326
|
+
if (activeSell && checkCandle && invalidationLevel != null && checkCandle.high > invalidationLevel) {
|
|
11327
|
+
invalidated = true;
|
|
11328
|
+
}
|
|
11329
|
+
if (invalidated) {
|
|
11330
|
+
activeBuy = false;
|
|
11331
|
+
activeSell = false;
|
|
11332
|
+
}
|
|
11333
|
+
const displayedKcMidline = showKeltnerChannel ? kcMidline : null;
|
|
11334
|
+
const displayedKcUpper = showKeltnerChannel ? kcUpper : null;
|
|
11335
|
+
const displayedKcLower = showKeltnerChannel ? kcLower : null;
|
|
11336
|
+
const displayedInvalidationLevel = showInvalidationLevels ? invalidationLevel : null;
|
|
11337
|
+
pushPlotPoint(plotSeries, "signalOsc", candle, signalOsc);
|
|
11338
|
+
pushPlotPoint(plotSeries, "kcMidline", candle, displayedKcMidline);
|
|
11339
|
+
pushPlotPoint(plotSeries, "kcUpper", candle, displayedKcUpper);
|
|
11340
|
+
pushPlotPoint(plotSeries, "kcLower", candle, displayedKcLower);
|
|
11341
|
+
pushPlotPoint(
|
|
11342
|
+
plotSeries,
|
|
11343
|
+
"invalidationLevel",
|
|
11344
|
+
candle,
|
|
11345
|
+
displayedInvalidationLevel
|
|
11346
|
+
);
|
|
11347
|
+
const currentLineValues = {};
|
|
11348
|
+
for (const plotName of linePlots) {
|
|
11349
|
+
switch (plotName) {
|
|
11350
|
+
case "signalOsc":
|
|
11351
|
+
currentLineValues[plotName] = signalOsc;
|
|
11352
|
+
break;
|
|
11353
|
+
case "kcMidline":
|
|
11354
|
+
currentLineValues[plotName] = displayedKcMidline;
|
|
11355
|
+
break;
|
|
11356
|
+
case "kcUpper":
|
|
11357
|
+
currentLineValues[plotName] = displayedKcUpper;
|
|
11358
|
+
break;
|
|
11359
|
+
case "kcLower":
|
|
11360
|
+
currentLineValues[plotName] = displayedKcLower;
|
|
11361
|
+
break;
|
|
11362
|
+
case "invalidationLevel":
|
|
11363
|
+
currentLineValues[plotName] = displayedInvalidationLevel;
|
|
11364
|
+
break;
|
|
11365
|
+
default:
|
|
11366
|
+
currentLineValues[plotName] = null;
|
|
11367
|
+
break;
|
|
11368
|
+
}
|
|
11369
|
+
}
|
|
11370
|
+
lastSnapshot = {
|
|
11371
|
+
entryLong,
|
|
11372
|
+
entryShort,
|
|
11373
|
+
invalidated,
|
|
11374
|
+
activeBuy,
|
|
11375
|
+
activeSell,
|
|
11376
|
+
signalOsc: toFinite(signalOsc),
|
|
11377
|
+
kcMidline: toFinite(displayedKcMidline),
|
|
11378
|
+
kcUpper: toFinite(displayedKcUpper),
|
|
11379
|
+
kcLower: toFinite(displayedKcLower),
|
|
11380
|
+
invalidationLevel: toFinite(displayedInvalidationLevel),
|
|
11381
|
+
lineValues: currentLineValues
|
|
11382
|
+
};
|
|
11383
|
+
}
|
|
11384
|
+
return {
|
|
11385
|
+
snapshot: lastSnapshot,
|
|
11386
|
+
plotSeries
|
|
11387
|
+
};
|
|
11388
|
+
};
|
|
11389
|
+
|
|
11003
11390
|
// src/AdaptiveMomentumRibbon/figures.ts
|
|
11004
|
-
import {
|
|
11005
|
-
getPinePlotSeries,
|
|
11006
|
-
toFiniteNumber
|
|
11007
|
-
} from "@tradejs/node/pine";
|
|
11008
11391
|
var DEFAULT_COLORS = ["#2962ff", "#f23645", "#089981", "#f59e0b"];
|
|
11009
11392
|
var LINE_STYLE_BY_PLOT = {
|
|
11010
11393
|
kcMidline: {
|
|
@@ -11033,18 +11416,18 @@ var toFigurePoints = (series, maxPoints) => {
|
|
|
11033
11416
|
const points = [];
|
|
11034
11417
|
for (let i = start; i < series.length; i += 1) {
|
|
11035
11418
|
const item = series[i];
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11419
|
+
if (!Number.isFinite(item?.time) || !Number.isFinite(item?.value)) {
|
|
11420
|
+
continue;
|
|
11421
|
+
}
|
|
11039
11422
|
points.push({
|
|
11040
|
-
timestamp,
|
|
11041
|
-
value
|
|
11423
|
+
timestamp: item.time,
|
|
11424
|
+
value: item.value
|
|
11042
11425
|
});
|
|
11043
11426
|
}
|
|
11044
11427
|
return points;
|
|
11045
11428
|
};
|
|
11046
11429
|
var buildAdaptiveMomentumRibbonFigures = ({
|
|
11047
|
-
|
|
11430
|
+
plotSeries,
|
|
11048
11431
|
linePlots,
|
|
11049
11432
|
direction,
|
|
11050
11433
|
entryTimestamp,
|
|
@@ -11052,7 +11435,7 @@ var buildAdaptiveMomentumRibbonFigures = ({
|
|
|
11052
11435
|
maxPoints = 180
|
|
11053
11436
|
}) => {
|
|
11054
11437
|
const lines = linePlots.map((plotName, index) => {
|
|
11055
|
-
const series =
|
|
11438
|
+
const series = plotSeries[plotName] ?? [];
|
|
11056
11439
|
const points = toFigurePoints(series, maxPoints);
|
|
11057
11440
|
if (!points.length) {
|
|
11058
11441
|
return null;
|
|
@@ -11085,62 +11468,17 @@ var buildAdaptiveMomentumRibbonFigures = ({
|
|
|
11085
11468
|
};
|
|
11086
11469
|
|
|
11087
11470
|
// src/AdaptiveMomentumRibbon/core.ts
|
|
11088
|
-
var AMR_PINE_FILE_NAME = "adaptiveMomentumRibbon.pine";
|
|
11089
|
-
var AMR_BOOLEAN_PLOTS = [
|
|
11090
|
-
"entryLong",
|
|
11091
|
-
"entryShort",
|
|
11092
|
-
"invalidated",
|
|
11093
|
-
"activeBuy",
|
|
11094
|
-
"activeSell"
|
|
11095
|
-
];
|
|
11096
|
-
var AMR_NUMBER_PLOTS = [
|
|
11097
|
-
"signalOsc",
|
|
11098
|
-
"kcMidline",
|
|
11099
|
-
"kcUpper",
|
|
11100
|
-
"kcLower",
|
|
11101
|
-
"invalidationLevel"
|
|
11102
|
-
];
|
|
11103
|
-
var asKcMaType = (value) => {
|
|
11104
|
-
if (value === "SMA" || value === "EMA" || value === "SMMA (RMA)" || value === "WMA" || value === "VWMA") {
|
|
11105
|
-
return value;
|
|
11106
|
-
}
|
|
11107
|
-
return "EMA";
|
|
11108
|
-
};
|
|
11109
|
-
var resolveAmrInputs = (config2) => ({
|
|
11110
|
-
"Momentum Period": asPositiveInt(config2.AMR_MOMENTUM_PERIOD, 20),
|
|
11111
|
-
"Butterworth Smoothing": asPositiveInt(config2.AMR_BUTTERWORTH_SMOOTHING, 3),
|
|
11112
|
-
"Confirm Signals on Bar Close": Boolean(config2.AMR_WAIT_CLOSE),
|
|
11113
|
-
"Show Invalidation Levels": Boolean(config2.AMR_SHOW_INVALIDATION_LEVELS),
|
|
11114
|
-
"Show Keltner Channel": Boolean(config2.AMR_SHOW_KELTNER_CHANNEL),
|
|
11115
|
-
"KC Length": asPositiveInt(config2.AMR_KC_LENGTH, 20),
|
|
11116
|
-
"KC MA Type": asKcMaType(config2.AMR_KC_MA_TYPE),
|
|
11117
|
-
"ATR Length": asPositiveInt(config2.AMR_ATR_LENGTH, 14),
|
|
11118
|
-
"ATR Multiplier": asPositiveNumber(config2.AMR_ATR_MULTIPLIER, 2)
|
|
11119
|
-
});
|
|
11120
11471
|
var resolveLinePlots = (value) => {
|
|
11121
11472
|
if (!Array.isArray(value)) {
|
|
11122
11473
|
return [];
|
|
11123
11474
|
}
|
|
11124
11475
|
return value.map((item) => String(item ?? "").trim()).filter((item) => item.length > 0);
|
|
11125
11476
|
};
|
|
11126
|
-
var
|
|
11127
|
-
return {
|
|
11128
|
-
...getLatestPineBooleanPlotValues(pineContext, AMR_BOOLEAN_PLOTS),
|
|
11129
|
-
...getLatestPineNumberPlotValues(pineContext, AMR_NUMBER_PLOTS),
|
|
11130
|
-
lineValues: getLatestPineNumberPlotValues(pineContext, linePlots)
|
|
11131
|
-
};
|
|
11132
|
-
};
|
|
11133
|
-
var createAdaptiveMomentumRibbonCore = async ({ config: config2, symbol, loadPineScriptFile, strategyApi }) => {
|
|
11134
|
-
const script = loadPineScriptFile(AMR_PINE_FILE_NAME);
|
|
11477
|
+
var createAdaptiveMomentumRibbonCore = async ({ config: config2, symbol, strategyApi }) => {
|
|
11135
11478
|
const { LONG, SHORT, AMR_EXIT_ON_INVALIDATION, MAX_LOSS_VALUE, FEE_PERCENT } = config2;
|
|
11136
11479
|
const linePlots = resolveLinePlots(config2.AMR_LINE_PLOTS);
|
|
11137
|
-
const lookbackBars =
|
|
11138
|
-
const pineInputs = resolveAmrInputs(config2);
|
|
11139
|
-
const timeframe = String(config2.INTERVAL ?? "15");
|
|
11480
|
+
const lookbackBars = asPositiveInt2(config2.AMR_LOOKBACK_BARS, 0);
|
|
11140
11481
|
return async () => {
|
|
11141
|
-
if (!script) {
|
|
11142
|
-
return strategyApi.skip("AMR_SCRIPT_EMPTY");
|
|
11143
|
-
}
|
|
11144
11482
|
const { fullData, currentPrice, timestamp } = await strategyApi.getMarketData();
|
|
11145
11483
|
if (fullData.length < 2) {
|
|
11146
11484
|
return strategyApi.skip("WAIT_DATA");
|
|
@@ -11150,26 +11488,24 @@ var createAdaptiveMomentumRibbonCore = async ({ config: config2, symbol, loadPin
|
|
|
11150
11488
|
position && typeof position.qty === "number" && position.qty > 0
|
|
11151
11489
|
);
|
|
11152
11490
|
const candles = lookbackBars > 0 ? fullData.slice(-lookbackBars) : fullData;
|
|
11153
|
-
let
|
|
11491
|
+
let evaluation;
|
|
11154
11492
|
try {
|
|
11155
|
-
|
|
11493
|
+
evaluation = evaluateAdaptiveMomentumRibbon({
|
|
11156
11494
|
candles,
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
timeframe,
|
|
11160
|
-
inputs: pineInputs
|
|
11495
|
+
config: config2,
|
|
11496
|
+
linePlots
|
|
11161
11497
|
});
|
|
11162
11498
|
} catch (error) {
|
|
11163
11499
|
if (typeof globalThis.setImmediate === "function") {
|
|
11164
11500
|
logger.warn(
|
|
11165
|
-
"AdaptiveMomentumRibbon
|
|
11501
|
+
"AdaptiveMomentumRibbon evaluation failed for %s: %s",
|
|
11166
11502
|
symbol,
|
|
11167
11503
|
String(error)
|
|
11168
11504
|
);
|
|
11169
11505
|
}
|
|
11170
|
-
return strategyApi.skip("
|
|
11506
|
+
return strategyApi.skip("AMR_EVALUATION_FAILED");
|
|
11171
11507
|
}
|
|
11172
|
-
const amr =
|
|
11508
|
+
const { snapshot: amr, plotSeries } = evaluation;
|
|
11173
11509
|
if (amr.entryLong && amr.entryShort) {
|
|
11174
11510
|
return strategyApi.skip("AMR_SIGNAL_CONFLICT");
|
|
11175
11511
|
}
|
|
@@ -11221,7 +11557,7 @@ var createAdaptiveMomentumRibbonCore = async ({ config: config2, symbol, loadPin
|
|
|
11221
11557
|
code: amr.entryLong ? "AMR_ENTRY_LONG" : "AMR_ENTRY_SHORT",
|
|
11222
11558
|
direction: modeConfig.direction,
|
|
11223
11559
|
figures: buildAdaptiveMomentumRibbonFigures({
|
|
11224
|
-
|
|
11560
|
+
plotSeries,
|
|
11225
11561
|
linePlots,
|
|
11226
11562
|
direction: modeConfig.direction,
|
|
11227
11563
|
entryTimestamp: timestamp,
|
|
@@ -11232,17 +11568,27 @@ var createAdaptiveMomentumRibbonCore = async ({ config: config2, symbol, loadPin
|
|
|
11232
11568
|
amrSignalTiming: {
|
|
11233
11569
|
entryTiming: "zero_cross",
|
|
11234
11570
|
waitClose: Boolean(config2.AMR_WAIT_CLOSE),
|
|
11571
|
+
confirmOnNextBar: Boolean(config2.AMR_CONFIRM_ON_NEXT_BAR),
|
|
11235
11572
|
lookbackBars
|
|
11236
11573
|
},
|
|
11237
11574
|
amrConfigSnapshot: {
|
|
11238
|
-
momentumPeriod:
|
|
11239
|
-
butterworthSmoothing:
|
|
11575
|
+
momentumPeriod: asPositiveInt2(config2.AMR_MOMENTUM_PERIOD, 20),
|
|
11576
|
+
butterworthSmoothing: asPositiveInt2(
|
|
11240
11577
|
config2.AMR_BUTTERWORTH_SMOOTHING,
|
|
11241
11578
|
3
|
|
11242
11579
|
),
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11580
|
+
minSignalOscAbs: asPositiveNumber2(
|
|
11581
|
+
config2.AMR_MIN_SIGNAL_OSC_ABS,
|
|
11582
|
+
0.55
|
|
11583
|
+
),
|
|
11584
|
+
requireKcBias: Boolean(config2.AMR_REQUIRE_KC_BIAS),
|
|
11585
|
+
minBarsBetweenSignals: asPositiveInt2(
|
|
11586
|
+
config2.AMR_MIN_BARS_BETWEEN_SIGNALS,
|
|
11587
|
+
12
|
|
11588
|
+
),
|
|
11589
|
+
kcLength: asPositiveInt2(config2.AMR_KC_LENGTH, 20),
|
|
11590
|
+
atrLength: asPositiveInt2(config2.AMR_ATR_LENGTH, 14),
|
|
11591
|
+
atrMultiplier: asPositiveNumber2(config2.AMR_ATR_MULTIPLIER, 2)
|
|
11246
11592
|
}
|
|
11247
11593
|
},
|
|
11248
11594
|
orderPlan: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/strategies",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Built-in strategy plugin catalog for the TradeJS open-source framework.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@tradejs/core": "^1.0.
|
|
36
|
-
"@tradejs/indicators": "^1.0.
|
|
37
|
-
"@tradejs/node": "^1.0.
|
|
38
|
-
"@tradejs/types": "^1.0.
|
|
35
|
+
"@tradejs/core": "^1.0.8",
|
|
36
|
+
"@tradejs/indicators": "^1.0.8",
|
|
37
|
+
"@tradejs/node": "^1.0.8",
|
|
38
|
+
"@tradejs/types": "^1.0.8"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"tsup": "^8.5.1",
|