@tradejs/strategies 1.0.5 → 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-IMLNXICX.mjs +547 -0
- package/dist/chunk-QVWMBYYM.mjs +836 -0
- package/dist/chunk-SOVTOGY4.mjs +163 -0
- package/dist/chunk-TDUTYEGH.mjs +797 -0
- package/dist/chunk-UK4YHD2E.mjs +683 -0
- package/dist/index.d.mts +325 -4
- package/dist/index.d.ts +325 -4
- package/dist/index.js +5689 -1195
- package/dist/index.mjs +61 -14
- package/dist/{strategy-ZVNTA6UR.mjs → strategy-ABIO65CR.mjs} +3 -46
- package/dist/strategy-D7H5J3C4.mjs +348 -0
- package/dist/strategy-HQIPCUOY.mjs +399 -0
- package/dist/{strategy-Y4SOK6FF.mjs → strategy-JQIJILHQ.mjs} +28 -109
- package/dist/strategy-QEIPAPY4.mjs +373 -0
- package/dist/strategy-WYN4FZ5S.mjs +613 -0
- package/dist/{strategy-UHRWSGZC.mjs → strategy-XTUKPYPM.mjs} +484 -128
- package/package.json +5 -5
- package/dist/chunk-3PN7ZSJJ.mjs +0 -27
- package/dist/chunk-MVIV5ZII.mjs +0 -137
- package/dist/chunk-RDK2JK3K.mjs +0 -28
- package/dist/chunk-RYEPHOGL.mjs +0 -28
- package/dist/chunk-ULLCAH5C.mjs +0 -67
- package/dist/strategy-M3BRWDRR.mjs +0 -377
- package/dist/strategy-UZBWST3G.mjs +0 -156
package/dist/index.mjs
CHANGED
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
import {
|
|
2
|
-
breakoutManifest
|
|
3
|
-
|
|
2
|
+
breakoutManifest,
|
|
3
|
+
config as config2
|
|
4
|
+
} from "./chunk-37ZWRG3W.mjs";
|
|
4
5
|
import {
|
|
5
|
-
config,
|
|
6
|
+
config as config6,
|
|
6
7
|
trendLineManifest
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TDUTYEGH.mjs";
|
|
8
9
|
import {
|
|
10
|
+
config as config5,
|
|
11
|
+
trendShiftAiAdapter,
|
|
12
|
+
trendShiftManifest
|
|
13
|
+
} from "./chunk-SOVTOGY4.mjs";
|
|
14
|
+
import {
|
|
15
|
+
config as config4,
|
|
16
|
+
reverseTrendLineAiAdapter,
|
|
17
|
+
reverseTrendLineManifest
|
|
18
|
+
} from "./chunk-UK4YHD2E.mjs";
|
|
19
|
+
import {
|
|
20
|
+
config as config3,
|
|
9
21
|
maStrategyAiAdapter,
|
|
10
22
|
maStrategyManifest,
|
|
11
23
|
maStrategyMlAdapter
|
|
12
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-H4MHFD4B.mjs";
|
|
13
25
|
import {
|
|
14
26
|
adaptiveMomentumRibbonAiAdapter,
|
|
15
27
|
adaptiveMomentumRibbonManifest,
|
|
16
|
-
adaptiveMomentumRibbonMlAdapter
|
|
17
|
-
|
|
28
|
+
adaptiveMomentumRibbonMlAdapter,
|
|
29
|
+
config
|
|
30
|
+
} from "./chunk-IMLNXICX.mjs";
|
|
18
31
|
import {
|
|
32
|
+
config as config7,
|
|
19
33
|
volumeDivergenceAiAdapter,
|
|
20
34
|
volumeDivergenceManifest,
|
|
21
35
|
volumeDivergenceMlAdapter
|
|
22
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-QVWMBYYM.mjs";
|
|
23
37
|
import "./chunk-HEBXNMVQ.mjs";
|
|
24
38
|
|
|
25
39
|
// src/index.ts
|
|
@@ -40,48 +54,81 @@ var strategyEntries = [
|
|
|
40
54
|
{
|
|
41
55
|
manifest: breakoutManifest,
|
|
42
56
|
creator: createLazyStrategyCreator(
|
|
43
|
-
() => import("./strategy-
|
|
57
|
+
() => import("./strategy-JQIJILHQ.mjs"),
|
|
44
58
|
"BreakoutStrategyCreator"
|
|
45
59
|
)
|
|
46
60
|
},
|
|
47
61
|
{
|
|
48
62
|
manifest: trendLineManifest,
|
|
49
63
|
creator: createLazyStrategyCreator(
|
|
50
|
-
() => import("./strategy-
|
|
64
|
+
() => import("./strategy-D7H5J3C4.mjs"),
|
|
51
65
|
"TrendlineStrategyCreator"
|
|
52
66
|
)
|
|
53
67
|
},
|
|
68
|
+
{
|
|
69
|
+
manifest: trendShiftManifest,
|
|
70
|
+
creator: createLazyStrategyCreator(
|
|
71
|
+
() => import("./strategy-QEIPAPY4.mjs"),
|
|
72
|
+
"TrendShiftStrategyCreator"
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
manifest: reverseTrendLineManifest,
|
|
77
|
+
creator: createLazyStrategyCreator(
|
|
78
|
+
() => import("./strategy-HQIPCUOY.mjs"),
|
|
79
|
+
"ReverseTrendLineStrategyCreator"
|
|
80
|
+
)
|
|
81
|
+
},
|
|
54
82
|
{
|
|
55
83
|
manifest: maStrategyManifest,
|
|
56
84
|
creator: createLazyStrategyCreator(
|
|
57
|
-
() => import("./strategy-
|
|
85
|
+
() => import("./strategy-ABIO65CR.mjs"),
|
|
58
86
|
"MaStrategyCreator"
|
|
59
87
|
)
|
|
60
88
|
},
|
|
61
89
|
{
|
|
62
90
|
manifest: adaptiveMomentumRibbonManifest,
|
|
63
91
|
creator: createLazyStrategyCreator(
|
|
64
|
-
() => import("./strategy-
|
|
92
|
+
() => import("./strategy-XTUKPYPM.mjs"),
|
|
65
93
|
"AdaptiveMomentumRibbonStrategyCreator"
|
|
66
94
|
)
|
|
67
95
|
},
|
|
68
96
|
{
|
|
69
97
|
manifest: volumeDivergenceManifest,
|
|
70
98
|
creator: createLazyStrategyCreator(
|
|
71
|
-
() => import("./strategy-
|
|
99
|
+
() => import("./strategy-WYN4FZ5S.mjs"),
|
|
72
100
|
"VolumeDivergenceStrategyCreator"
|
|
73
101
|
)
|
|
74
102
|
}
|
|
75
103
|
];
|
|
104
|
+
var builtInStrategyDefaultConfigs = {
|
|
105
|
+
Breakout: config2,
|
|
106
|
+
TrendLine: config6,
|
|
107
|
+
TrendShift: config5,
|
|
108
|
+
ReverseTrendLine: config4,
|
|
109
|
+
MaStrategy: config3,
|
|
110
|
+
AdaptiveMomentumRibbon: config,
|
|
111
|
+
VolumeDivergence: config7
|
|
112
|
+
};
|
|
113
|
+
var getBuiltInStrategyDefaultConfig = (strategyName) => builtInStrategyDefaultConfigs[strategyName];
|
|
76
114
|
var index_default = defineStrategyPlugin({ strategyEntries });
|
|
77
115
|
export {
|
|
78
116
|
adaptiveMomentumRibbonAiAdapter,
|
|
117
|
+
config as adaptiveMomentumRibbonDefaultConfig,
|
|
79
118
|
adaptiveMomentumRibbonMlAdapter,
|
|
119
|
+
config2 as breakoutDefaultConfig,
|
|
80
120
|
index_default as default,
|
|
121
|
+
getBuiltInStrategyDefaultConfig,
|
|
81
122
|
maStrategyAiAdapter,
|
|
123
|
+
config3 as maStrategyDefaultConfig,
|
|
82
124
|
maStrategyMlAdapter,
|
|
125
|
+
reverseTrendLineAiAdapter,
|
|
126
|
+
config4 as reverseTrendLineDefaultConfig,
|
|
83
127
|
strategyEntries,
|
|
84
|
-
|
|
128
|
+
config6 as trendLineDefaultConfig,
|
|
129
|
+
trendShiftAiAdapter,
|
|
130
|
+
config5 as trendShiftDefaultConfig,
|
|
85
131
|
volumeDivergenceAiAdapter,
|
|
132
|
+
config7 as volumeDivergenceDefaultConfig,
|
|
86
133
|
volumeDivergenceMlAdapter
|
|
87
134
|
};
|
|
@@ -1,44 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
config,
|
|
2
3
|
maStrategyManifest
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-H4MHFD4B.mjs";
|
|
4
5
|
import "./chunk-HEBXNMVQ.mjs";
|
|
5
6
|
|
|
6
7
|
// src/MaStrategy/strategy.ts
|
|
7
8
|
import { createStrategyRuntime } from "@tradejs/node/strategies";
|
|
8
9
|
|
|
9
|
-
// src/MaStrategy/config.ts
|
|
10
|
-
var config = {
|
|
11
|
-
ENV: "BACKTEST",
|
|
12
|
-
INTERVAL: "15",
|
|
13
|
-
MAKE_ORDERS: true,
|
|
14
|
-
CLOSE_OPPOSITE_POSITIONS: false,
|
|
15
|
-
BACKTEST_PRICE_MODE: "mid",
|
|
16
|
-
AI_ENABLED: false,
|
|
17
|
-
ML_ENABLED: false,
|
|
18
|
-
ML_THRESHOLD: 0.1,
|
|
19
|
-
MIN_AI_QUALITY: 3,
|
|
20
|
-
FEE_PERCENT: 5e-3,
|
|
21
|
-
MAX_LOSS_VALUE: 10,
|
|
22
|
-
MAX_CORRELATION: 0.45,
|
|
23
|
-
TRADE_COOLDOWN_MS: 0,
|
|
24
|
-
MA_FAST: 21,
|
|
25
|
-
MA_SLOW: 55,
|
|
26
|
-
LONG: {
|
|
27
|
-
enable: true,
|
|
28
|
-
direction: "LONG",
|
|
29
|
-
TP: 2,
|
|
30
|
-
SL: 1,
|
|
31
|
-
minRiskRatio: 1.5
|
|
32
|
-
},
|
|
33
|
-
SHORT: {
|
|
34
|
-
enable: true,
|
|
35
|
-
direction: "SHORT",
|
|
36
|
-
TP: 2,
|
|
37
|
-
SL: 1,
|
|
38
|
-
minRiskRatio: 1.5
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
10
|
// src/MaStrategy/core.ts
|
|
43
11
|
import { round } from "@tradejs/core/math";
|
|
44
12
|
|
|
@@ -134,15 +102,7 @@ var detectCross = (maFast, maSlow) => {
|
|
|
134
102
|
return null;
|
|
135
103
|
};
|
|
136
104
|
var createMaStrategyCore = async ({ config: config2, strategyApi, indicatorsState }) => {
|
|
137
|
-
const {
|
|
138
|
-
ENV,
|
|
139
|
-
FEE_PERCENT,
|
|
140
|
-
MAX_LOSS_VALUE,
|
|
141
|
-
MAX_CORRELATION,
|
|
142
|
-
TRADE_COOLDOWN_MS,
|
|
143
|
-
LONG,
|
|
144
|
-
SHORT
|
|
145
|
-
} = config2;
|
|
105
|
+
const { FEE_PERCENT, MAX_LOSS_VALUE, TRADE_COOLDOWN_MS, LONG, SHORT } = config2;
|
|
146
106
|
const lastTradeController = strategyApi.createLastTradeController({
|
|
147
107
|
enabled: Number(TRADE_COOLDOWN_MS ?? 0) > 0,
|
|
148
108
|
cooldownMs: Number(TRADE_COOLDOWN_MS ?? 0)
|
|
@@ -205,9 +165,6 @@ var createMaStrategyCore = async ({ config: config2, strategyApi, indicatorsStat
|
|
|
205
165
|
return strategyApi.skip(`RISK_RATIO:${round(riskRatio)}`);
|
|
206
166
|
}
|
|
207
167
|
const correlation = indicatorsState.latestNumber("correlation");
|
|
208
|
-
if (ENV !== "BACKTEST" && correlation != null && correlation >= MAX_CORRELATION) {
|
|
209
|
-
return strategyApi.skip(`MAX_CORRELATION:${round(correlation)}`);
|
|
210
|
-
}
|
|
211
168
|
lastTradeController.markTrade(timestamp);
|
|
212
169
|
return strategyApi.entry({
|
|
213
170
|
code: cross.kind === "bullish" ? "MA_BULLISH_CROSS" : "MA_BEARISH_CROSS",
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTrendlineStructuralContext,
|
|
3
|
+
buildTrendlineTimingContext,
|
|
4
|
+
config,
|
|
5
|
+
trendLineManifest
|
|
6
|
+
} from "./chunk-TDUTYEGH.mjs";
|
|
7
|
+
import "./chunk-HEBXNMVQ.mjs";
|
|
8
|
+
|
|
9
|
+
// src/TrendLine/strategy.ts
|
|
10
|
+
import { createStrategyRuntime } from "@tradejs/node/strategies";
|
|
11
|
+
|
|
12
|
+
// src/TrendLine/core.ts
|
|
13
|
+
import { round as round2 } from "@tradejs/core/math";
|
|
14
|
+
import { createTrendlineEngine } from "@tradejs/core/indicators";
|
|
15
|
+
|
|
16
|
+
// src/TrendLine/filters.ts
|
|
17
|
+
import { diffRel } from "@tradejs/core/math";
|
|
18
|
+
import { ATR_PCT } from "@tradejs/indicators";
|
|
19
|
+
var MAX_CANDLE_VOLATILITY = 0.025;
|
|
20
|
+
var filterByVeryVolatility = (data) => {
|
|
21
|
+
const lastCandle = data[data.length - 1];
|
|
22
|
+
const prevCandle = data[data.length - 2];
|
|
23
|
+
const isVeryVolatility = diffRel(lastCandle.low, lastCandle.high) > MAX_CANDLE_VOLATILITY || diffRel(prevCandle.low, prevCandle.high) > MAX_CANDLE_VOLATILITY;
|
|
24
|
+
if (isVeryVolatility) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/TrendLine/figures.ts
|
|
31
|
+
var buildTrendLineFigures = (bestLine) => ({
|
|
32
|
+
lines: [
|
|
33
|
+
{
|
|
34
|
+
id: bestLine.id,
|
|
35
|
+
kind: "trendline",
|
|
36
|
+
points: [...bestLine.points ?? []].sort(
|
|
37
|
+
(left, right) => left.timestamp - right.timestamp
|
|
38
|
+
),
|
|
39
|
+
color: bestLine.mode === "lows" ? "#facc15" : "#fb923c",
|
|
40
|
+
width: 2,
|
|
41
|
+
style: "solid"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
points: [
|
|
45
|
+
{
|
|
46
|
+
id: `${bestLine.id}-points`,
|
|
47
|
+
kind: "trendline_points",
|
|
48
|
+
points: [...bestLine.points ?? [], ...bestLine.touches ?? []].sort(
|
|
49
|
+
(left, right) => left.timestamp - right.timestamp
|
|
50
|
+
),
|
|
51
|
+
color: "#ef4444",
|
|
52
|
+
radius: 4
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// src/TrendLine/risk.ts
|
|
58
|
+
import { round } from "@tradejs/core/math";
|
|
59
|
+
var MIN_STOP_BUFFER_PCT = 0.15;
|
|
60
|
+
var LINE_BUFFER_ATR_FACTOR = 0.35;
|
|
61
|
+
var LINE_BUFFER_BASE_SL_FACTOR = 0.15;
|
|
62
|
+
var ATR_STOP_FLOOR_FACTOR = 0.8;
|
|
63
|
+
var MIN_STOP_LOSS_FACTOR = 0.75;
|
|
64
|
+
var MAX_STOP_LOSS_FACTOR = 2.25;
|
|
65
|
+
var clampNumber = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
66
|
+
var getTimingStopFactor = (entryTiming) => {
|
|
67
|
+
if (entryTiming === "ready_retest") {
|
|
68
|
+
return 0.9;
|
|
69
|
+
}
|
|
70
|
+
if (entryTiming === "ready_follow_through") {
|
|
71
|
+
return 1.05;
|
|
72
|
+
}
|
|
73
|
+
return 1;
|
|
74
|
+
};
|
|
75
|
+
var getTimingTargetRiskRatio = ({
|
|
76
|
+
direction,
|
|
77
|
+
entryTiming
|
|
78
|
+
}) => {
|
|
79
|
+
if (direction === "LONG") {
|
|
80
|
+
if (entryTiming === "ready_retest") {
|
|
81
|
+
return 2.45;
|
|
82
|
+
}
|
|
83
|
+
if (entryTiming === "ready_follow_through") {
|
|
84
|
+
return 2.3;
|
|
85
|
+
}
|
|
86
|
+
return 2.6;
|
|
87
|
+
}
|
|
88
|
+
if (entryTiming === "ready_retest") {
|
|
89
|
+
return 2.3;
|
|
90
|
+
}
|
|
91
|
+
if (entryTiming === "ready_follow_through") {
|
|
92
|
+
return 2.15;
|
|
93
|
+
}
|
|
94
|
+
return 2.45;
|
|
95
|
+
};
|
|
96
|
+
var buildTrendlineRiskPlan = ({
|
|
97
|
+
direction,
|
|
98
|
+
modeConfig,
|
|
99
|
+
structuralContext,
|
|
100
|
+
timingContext
|
|
101
|
+
}) => {
|
|
102
|
+
const baseStopLossDelta = modeConfig.SL;
|
|
103
|
+
const atrPct = structuralContext.atrPct ?? baseStopLossDelta;
|
|
104
|
+
const priceVsLinePctAbs = structuralContext.priceVsLinePctAbs ?? 0;
|
|
105
|
+
const breakVsAtrRatio = structuralContext.breakVsAtrRatio ?? 0;
|
|
106
|
+
const touches = structuralContext.touches ?? 0;
|
|
107
|
+
const distance = structuralContext.distance ?? null;
|
|
108
|
+
const lineBufferPct = Math.max(
|
|
109
|
+
atrPct * LINE_BUFFER_ATR_FACTOR,
|
|
110
|
+
baseStopLossDelta * LINE_BUFFER_BASE_SL_FACTOR,
|
|
111
|
+
MIN_STOP_BUFFER_PCT
|
|
112
|
+
);
|
|
113
|
+
const lineInvalidationPct = priceVsLinePctAbs + lineBufferPct;
|
|
114
|
+
const volatilityFloorPct = Math.max(
|
|
115
|
+
atrPct * ATR_STOP_FLOOR_FACTOR,
|
|
116
|
+
baseStopLossDelta * MIN_STOP_LOSS_FACTOR
|
|
117
|
+
);
|
|
118
|
+
let stopLossDelta = Math.max(lineInvalidationPct, volatilityFloorPct);
|
|
119
|
+
if (touches >= 6) {
|
|
120
|
+
stopLossDelta *= 0.95;
|
|
121
|
+
} else if (touches > 0 && touches <= 4) {
|
|
122
|
+
stopLossDelta *= 1.05;
|
|
123
|
+
}
|
|
124
|
+
if (distance != null && distance >= 250) {
|
|
125
|
+
stopLossDelta *= 1.08;
|
|
126
|
+
} else if (distance != null && distance <= 120) {
|
|
127
|
+
stopLossDelta *= 0.95;
|
|
128
|
+
}
|
|
129
|
+
stopLossDelta *= getTimingStopFactor(timingContext.entryTiming);
|
|
130
|
+
if (direction === "SHORT") {
|
|
131
|
+
stopLossDelta *= 1.08;
|
|
132
|
+
}
|
|
133
|
+
if (breakVsAtrRatio >= 1.5) {
|
|
134
|
+
stopLossDelta *= 0.95;
|
|
135
|
+
}
|
|
136
|
+
stopLossDelta = clampNumber(
|
|
137
|
+
stopLossDelta,
|
|
138
|
+
baseStopLossDelta * MIN_STOP_LOSS_FACTOR,
|
|
139
|
+
baseStopLossDelta * MAX_STOP_LOSS_FACTOR
|
|
140
|
+
);
|
|
141
|
+
let targetRiskRatio = getTimingTargetRiskRatio({
|
|
142
|
+
direction,
|
|
143
|
+
entryTiming: timingContext.entryTiming
|
|
144
|
+
});
|
|
145
|
+
if (breakVsAtrRatio >= 1.25) {
|
|
146
|
+
targetRiskRatio += 0.2;
|
|
147
|
+
} else if (breakVsAtrRatio > 0 && breakVsAtrRatio < 0.75) {
|
|
148
|
+
targetRiskRatio -= 0.15;
|
|
149
|
+
}
|
|
150
|
+
if (touches >= 6) {
|
|
151
|
+
targetRiskRatio += 0.1;
|
|
152
|
+
}
|
|
153
|
+
if (distance != null && distance >= 120 && distance <= 350) {
|
|
154
|
+
targetRiskRatio += 0.1;
|
|
155
|
+
}
|
|
156
|
+
if (direction === "SHORT" && distance != null && distance > 450) {
|
|
157
|
+
targetRiskRatio -= 0.25;
|
|
158
|
+
}
|
|
159
|
+
if (direction === "LONG" && distance != null && distance > 500) {
|
|
160
|
+
targetRiskRatio -= 0.15;
|
|
161
|
+
}
|
|
162
|
+
const minTargetRiskRatio = modeConfig.minRiskRatio + 0.05;
|
|
163
|
+
const maxTargetRiskRatio = Math.max(modeConfig.TP / modeConfig.SL, minTargetRiskRatio) + 0.4;
|
|
164
|
+
targetRiskRatio = clampNumber(
|
|
165
|
+
targetRiskRatio,
|
|
166
|
+
minTargetRiskRatio,
|
|
167
|
+
maxTargetRiskRatio
|
|
168
|
+
);
|
|
169
|
+
return {
|
|
170
|
+
stopLossDelta: round(stopLossDelta, 3),
|
|
171
|
+
targetRiskRatio: round(targetRiskRatio, 2),
|
|
172
|
+
takeProfitDelta: round(stopLossDelta * targetRiskRatio, 3)
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/TrendLine/core.ts
|
|
177
|
+
var buildTrendlineSignalSeed = ({
|
|
178
|
+
direction,
|
|
179
|
+
currentPrice,
|
|
180
|
+
indicators,
|
|
181
|
+
bestLine,
|
|
182
|
+
trendlineTiming
|
|
183
|
+
}) => ({
|
|
184
|
+
direction,
|
|
185
|
+
prices: { currentPrice },
|
|
186
|
+
indicators,
|
|
187
|
+
additionalIndicators: {
|
|
188
|
+
touches: bestLine.touches.length + 2,
|
|
189
|
+
distance: bestLine.distance,
|
|
190
|
+
trendLine: bestLine,
|
|
191
|
+
...trendlineTiming ? { trendlineTiming } : {}
|
|
192
|
+
},
|
|
193
|
+
figures: {
|
|
194
|
+
trendLine: bestLine
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
var isOpenPosition = (position) => Boolean(
|
|
198
|
+
position && typeof position.price === "number" && Number.isFinite(position.price) && typeof position.qty === "number" && Number.isFinite(position.qty) && position.qty > 0 && (position.direction === "LONG" || position.direction === "SHORT")
|
|
199
|
+
);
|
|
200
|
+
var isFailedBreakout = ({
|
|
201
|
+
direction,
|
|
202
|
+
priceVsLinePct
|
|
203
|
+
}) => {
|
|
204
|
+
if (priceVsLinePct == null) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return direction === "LONG" ? priceVsLinePct < 0 : priceVsLinePct > 0;
|
|
208
|
+
};
|
|
209
|
+
var createTrendLineCore = async ({ config: config2, data: cachedData, strategyApi, indicatorsState }) => {
|
|
210
|
+
const { TRENDLINE, FEE_PERCENT, MAX_LOSS_VALUE, HIGHS, LOWS } = config2;
|
|
211
|
+
const lastTradeController = strategyApi.createLastTradeController();
|
|
212
|
+
const trendlineOptions = {
|
|
213
|
+
bestLines: 1,
|
|
214
|
+
capture: true,
|
|
215
|
+
...TRENDLINE
|
|
216
|
+
};
|
|
217
|
+
const getLowsTrendlines = createTrendlineEngine(cachedData, {
|
|
218
|
+
mode: "lows",
|
|
219
|
+
...trendlineOptions
|
|
220
|
+
});
|
|
221
|
+
const getHighsTrendlines = createTrendlineEngine(cachedData, {
|
|
222
|
+
mode: "highs",
|
|
223
|
+
...trendlineOptions
|
|
224
|
+
});
|
|
225
|
+
return async (candle) => {
|
|
226
|
+
const lowsTrendlines = getLowsTrendlines.next(candle);
|
|
227
|
+
const highsTrendlines = getHighsTrendlines.next(candle);
|
|
228
|
+
indicatorsState.onBar();
|
|
229
|
+
const currentPosition = await strategyApi.getCurrentPosition();
|
|
230
|
+
if (isOpenPosition(currentPosition)) {
|
|
231
|
+
const { currentPrice: currentPrice2 } = await strategyApi.getMarketData();
|
|
232
|
+
const activeLine = currentPosition.direction === "LONG" ? highsTrendlines[0] : lowsTrendlines[0];
|
|
233
|
+
const activeModeConfig = currentPosition.direction === "LONG" ? HIGHS : LOWS;
|
|
234
|
+
if (activeLine) {
|
|
235
|
+
const indicators2 = indicatorsState.snapshot();
|
|
236
|
+
const manageSignalSeed = buildTrendlineSignalSeed({
|
|
237
|
+
direction: activeModeConfig.direction,
|
|
238
|
+
currentPrice: currentPrice2,
|
|
239
|
+
indicators: indicators2,
|
|
240
|
+
bestLine: activeLine
|
|
241
|
+
});
|
|
242
|
+
const structuralContext2 = buildTrendlineStructuralContext(manageSignalSeed);
|
|
243
|
+
if (isFailedBreakout({
|
|
244
|
+
direction: currentPosition.direction,
|
|
245
|
+
priceVsLinePct: structuralContext2.priceVsLinePct
|
|
246
|
+
})) {
|
|
247
|
+
return strategyApi.exit({
|
|
248
|
+
code: "TRENDLINE_FAILED_BREAKOUT_EXIT",
|
|
249
|
+
direction: currentPosition.direction
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return strategyApi.skip("POSITION_EXISTS");
|
|
254
|
+
}
|
|
255
|
+
const bestLine = lowsTrendlines.length > 0 ? lowsTrendlines[0] : highsTrendlines[0];
|
|
256
|
+
if (!bestLine) {
|
|
257
|
+
return strategyApi.skip("NO_TRENDLINE");
|
|
258
|
+
}
|
|
259
|
+
if (lastTradeController.isInCooldown(candle.timestamp)) {
|
|
260
|
+
return strategyApi.skip("DEV_TRADE_COOLDOWN");
|
|
261
|
+
}
|
|
262
|
+
const modeConfig = bestLine.mode === "highs" ? HIGHS : LOWS;
|
|
263
|
+
const { direction, minRiskRatio, enable } = modeConfig;
|
|
264
|
+
if (!enable) {
|
|
265
|
+
return strategyApi.skip("STRATEGY_DISABLED");
|
|
266
|
+
}
|
|
267
|
+
const { fullData, timestamp, currentPrice } = await strategyApi.getMarketData();
|
|
268
|
+
if (!filterByVeryVolatility(fullData)) {
|
|
269
|
+
return strategyApi.skip("VERY_VOLATILITY");
|
|
270
|
+
}
|
|
271
|
+
const indicators = indicatorsState.snapshot();
|
|
272
|
+
const signalSeed = buildTrendlineSignalSeed({
|
|
273
|
+
direction,
|
|
274
|
+
currentPrice,
|
|
275
|
+
indicators,
|
|
276
|
+
bestLine
|
|
277
|
+
});
|
|
278
|
+
const structuralContext = buildTrendlineStructuralContext(signalSeed);
|
|
279
|
+
if (structuralContext.structuralHardBlockReasons.length > 0) {
|
|
280
|
+
return strategyApi.skip(
|
|
281
|
+
`TRENDLINE_STRUCTURE:${structuralContext.structuralHardBlockReasons[0]}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
const timingContext = buildTrendlineTimingContext({
|
|
285
|
+
signal: signalSeed,
|
|
286
|
+
candles: fullData,
|
|
287
|
+
structuralContext
|
|
288
|
+
});
|
|
289
|
+
if (!timingContext.entryReadyNow) {
|
|
290
|
+
const timingCode = timingContext.entryTiming === "stale_breakout" ? "STALE_BREAKOUT" : timingContext.entryTiming === "wait_retest_confirmation" ? "WAIT_RETEST_CONFIRMATION" : "WAIT_RETEST";
|
|
291
|
+
return strategyApi.skip(`TRENDLINE_TIMING:${timingCode}`);
|
|
292
|
+
}
|
|
293
|
+
const riskPlan = buildTrendlineRiskPlan({
|
|
294
|
+
direction,
|
|
295
|
+
modeConfig,
|
|
296
|
+
structuralContext,
|
|
297
|
+
timingContext
|
|
298
|
+
});
|
|
299
|
+
const { stopLossPrice, takeProfitPrice, riskRatio, qty } = strategyApi.getDirectionalTpSlPrices({
|
|
300
|
+
price: currentPrice,
|
|
301
|
+
direction,
|
|
302
|
+
takeProfitDelta: riskPlan.takeProfitDelta,
|
|
303
|
+
stopLossDelta: riskPlan.stopLossDelta,
|
|
304
|
+
unit: "percent",
|
|
305
|
+
maxLossValue: MAX_LOSS_VALUE,
|
|
306
|
+
feePercent: Number(FEE_PERCENT ?? 0)
|
|
307
|
+
});
|
|
308
|
+
if (!qty || !Number.isFinite(qty) || qty <= 0) {
|
|
309
|
+
return strategyApi.skip("INVALID_QTY");
|
|
310
|
+
}
|
|
311
|
+
if (riskRatio <= minRiskRatio) {
|
|
312
|
+
return strategyApi.skip(`RISK_RATIO:${round2(riskRatio)}`);
|
|
313
|
+
}
|
|
314
|
+
lastTradeController.markTrade(timestamp);
|
|
315
|
+
return strategyApi.entry({
|
|
316
|
+
code: "TRENDLINE_SIGNAL",
|
|
317
|
+
figures: {
|
|
318
|
+
...buildTrendLineFigures(bestLine)
|
|
319
|
+
},
|
|
320
|
+
direction,
|
|
321
|
+
indicators,
|
|
322
|
+
additionalIndicators: buildTrendlineSignalSeed({
|
|
323
|
+
direction,
|
|
324
|
+
currentPrice,
|
|
325
|
+
indicators,
|
|
326
|
+
bestLine,
|
|
327
|
+
trendlineTiming: timingContext
|
|
328
|
+
}).additionalIndicators,
|
|
329
|
+
orderPlan: {
|
|
330
|
+
qty,
|
|
331
|
+
stopLossPrice,
|
|
332
|
+
takeProfits: [{ rate: 1, price: takeProfitPrice }]
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// src/TrendLine/strategy.ts
|
|
339
|
+
var TrendlineStrategyCreator = createStrategyRuntime({
|
|
340
|
+
strategyName: "TrendLine",
|
|
341
|
+
defaults: config,
|
|
342
|
+
createCore: createTrendLineCore,
|
|
343
|
+
manifest: trendLineManifest,
|
|
344
|
+
strategyDirectory: __dirname
|
|
345
|
+
});
|
|
346
|
+
export {
|
|
347
|
+
TrendlineStrategyCreator
|
|
348
|
+
};
|