@tradejs/strategies 1.0.6 → 1.0.9

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
- adaptiveMomentumRibbonManifest
3
- } from "./chunk-MOBKL73M.mjs";
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
- const timestamp = toFiniteNumber(item?.time);
11037
- const value = toFiniteNumber(item?.value);
11038
- if (timestamp == null || value == null) continue;
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
- pineContext,
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 = getPinePlotSeries(pineContext, plotName);
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 readAmrSnapshot = (pineContext, linePlots) => {
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 = asPositiveInt(config2.AMR_LOOKBACK_BARS, 0);
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 pineContext;
11491
+ let evaluation;
11154
11492
  try {
11155
- pineContext = await runPineScript({
11493
+ evaluation = evaluateAdaptiveMomentumRibbon({
11156
11494
  candles,
11157
- script,
11158
- symbol,
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 pine run failed for %s: %s",
11501
+ "AdaptiveMomentumRibbon evaluation failed for %s: %s",
11166
11502
  symbol,
11167
11503
  String(error)
11168
11504
  );
11169
11505
  }
11170
- return strategyApi.skip("AMR_SCRIPT_FAILED");
11506
+ return strategyApi.skip("AMR_EVALUATION_FAILED");
11171
11507
  }
11172
- const amr = readAmrSnapshot(pineContext, linePlots);
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
- pineContext,
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: asPositiveInt(config2.AMR_MOMENTUM_PERIOD, 20),
11239
- butterworthSmoothing: asPositiveInt(
11575
+ momentumPeriod: asPositiveInt2(config2.AMR_MOMENTUM_PERIOD, 20),
11576
+ butterworthSmoothing: asPositiveInt2(
11240
11577
  config2.AMR_BUTTERWORTH_SMOOTHING,
11241
11578
  3
11242
11579
  ),
11243
- kcLength: asPositiveInt(config2.AMR_KC_LENGTH, 20),
11244
- atrLength: asPositiveInt(config2.AMR_ATR_LENGTH, 14),
11245
- atrMultiplier: asPositiveNumber(config2.AMR_ATR_MULTIPLIER, 2)
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.6",
3
+ "version": "1.0.9",
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.6",
36
- "@tradejs/indicators": "^1.0.6",
37
- "@tradejs/node": "^1.0.6",
38
- "@tradejs/types": "^1.0.6"
35
+ "@tradejs/core": "^1.0.9",
36
+ "@tradejs/indicators": "^1.0.9",
37
+ "@tradejs/node": "^1.0.9",
38
+ "@tradejs/types": "^1.0.9"
39
39
  },
40
40
  "devDependencies": {
41
41
  "tsup": "^8.5.1",