Libraryless. Click here for Pure Java version (31812L/204K).
1 | // Design notes. |
2 | // |
3 | // NG stands for "Non-Garbage" |
4 | // |
5 | // !Design notes end |
6 | |
7 | sclass NGSStrategy2 extends G22CandleBasedStrategyWithTrailingStop is IntegrityCheckable { |
8 | settable int period1 = 100; // the "slow" MA |
9 | settable int period2 = 5; // the fast MA (for opening) |
10 | settable double maMinMove = 0.07; // MA trend threshold |
11 | settable double maMoveHysteresis = 0.01; // MA trend non-oscillation threshold |
12 | |
13 | settable Side longSide = new(1); |
14 | settable Side shortSide = new(-1); |
15 | |
16 | // false = only manual openLong() and openShort(), |
17 | // not based on indicators. |
18 | // true = auto-open positions indefinitely depending on |
19 | // indicators. |
20 | settable bool autoPositions = true; |
21 | |
22 | // temporal fields follow |
23 | |
24 | S fieldsToReset() { |
25 | ret lineCombine(super.fieldsToReset(), [[ |
26 | ma1 ma2 lastMA1 maMove movingAverage1_cache movingAverage2_cache |
27 | maMoveHistory maDirection maDirectionHistory |
28 | ]]); |
29 | } |
30 | |
31 | settable double ma1; |
32 | settable double ma2; |
33 | settable double lastMA1 = Double.NaN; |
34 | settable double maMove; |
35 | settable int maDirection; |
36 | |
37 | // Too big, disabling this by default now |
38 | gettable TickerSequence maMoveHistory/* = new TickerSequence("maMove")*/; |
39 | gettable TickerSequence maDirectionHistory = new TickerSequence("maDirection"); |
40 | |
41 | settable transient O visualizer; |
42 | |
43 | selfType emptyClone() { |
44 | selfType clone = cast super.emptyClone(); |
45 | clone.longSide(longSide.emptyClone(clone)); |
46 | clone.shortSide(shortSide.emptyClone(clone)); |
47 | ret clone; |
48 | } |
49 | |
50 | // Side = logic for one side (long/short = direction 1/-1) |
51 | record noeq Side(int direction) { |
52 | // Our condition is: |
53 | // Price has to be in a band close to the MA first (band 1) |
54 | // and then move into a band further away from the MA (band 2) |
55 | |
56 | // Are we allowed to open positions in this direction at all? |
57 | settable bool enabled = true; |
58 | |
59 | // First band (close to MA) |
60 | settable DoubleRange band1 = new(0.2, 0.6); |
61 | |
62 | // Second band |
63 | settable DoubleRange band2 = new(0.6, 1.0); |
64 | |
65 | // Percentage to close the position at (below band/above band) |
66 | settable double closeLevel = 0; |
67 | |
68 | // Do we require the MA to go in our direction? |
69 | // (If no, we just require it to not go the other direction.) |
70 | settable bool needMADirection = true; |
71 | |
72 | // temporal fields follow |
73 | |
74 | selfType emptyClone(NGSStrategy2 stratClone) { |
75 | ret copyFields(this, stratClone.new Side(), |
76 | identifiers("direction enabled band1 band2 closeLevel")); |
77 | } |
78 | |
79 | // Has price visited band1 recently? |
80 | settable bool wasInBand1; |
81 | |
82 | // convert percent band to current price range |
83 | DoubleRange instantiateBand(DoubleRange band, double ma1 default ma1) { |
84 | ret doubleRange( |
85 | maPlusPercent(ma1, direction*band.start), |
86 | maPlusPercent(ma1, direction*band.end)).sort(); |
87 | } |
88 | |
89 | double priceToPercent(double price, double ma1 default ma1) { |
90 | ret (price/ma1-1)*direction*100; |
91 | } |
92 | |
93 | double relativePrice() { |
94 | ret priceToPercent(currentPrice()); |
95 | } |
96 | |
97 | void step { |
98 | var relativePrice = relativePrice(); |
99 | |
100 | // close if too close to band/beyond band |
101 | if (relativePrice < closeLevel && sign(drift()) == direction) { |
102 | closeAllPositions(direction > 0 ? "Below band" : "Above band"); |
103 | } |
104 | |
105 | // price on wrong side => reset |
106 | if (relativePrice < 0) wasInBand1(false); |
107 | |
108 | // Above band2 => reset |
109 | if (relativePrice > band2.end) wasInBand1(false); |
110 | |
111 | // We hit band 1 |
112 | if (band1.contains(relativePrice)) wasInBand1(true); |
113 | |
114 | if (autoPositions && enabled && wasInBand1 && band2.contains(relativePrice) |
115 | && ma2*direction < currentPrice()*direction |
116 | && (needMADirection ? maDirection == direction : maDirection != -direction)) { |
117 | log("Trigger " + this); |
118 | wasInBand1(false); |
119 | if (drift() == 0 && !inCooldown()) |
120 | openPosition(direction); |
121 | } |
122 | } |
123 | |
124 | // "short" or "long" |
125 | toString { ret trading_directionToPositionType(direction); } |
126 | |
127 | LS status() { |
128 | ret listCombine( |
129 | enabled ? null : "disabled", |
130 | "Relative price: "+ formatDouble1(relativePrice()), |
131 | !wasInBand1 ? null : "Was in band 1", |
132 | "Band 1: " + band1, |
133 | "Band 2: " + band2, |
134 | "Close level: " + closeLevel |
135 | ); |
136 | } |
137 | } // end of Side |
138 | |
139 | void ngsStep aka ngsLogic() { |
140 | //double close = currentPrice(); |
141 | //double drift = drift(); |
142 | |
143 | for (side : sides()) |
144 | side.step(); |
145 | |
146 | change(); |
147 | } |
148 | |
149 | simplyCached MAIndicator movingAverage1() { |
150 | ret new MAIndicator(period1); |
151 | } |
152 | |
153 | simplyCached MAIndicator movingAverage2() { |
154 | ret new MAIndicator(period2); |
155 | } |
156 | |
157 | L<CandleBasedIndicator> indicators() { |
158 | ret ll(movingAverage1(), movingAverage2()); |
159 | } |
160 | |
161 | bool maRising() { ret maDirection > 0; } |
162 | bool maFalling() { ret maDirection < 0; } |
163 | double moveSignal() { ret maMove/maMinMove*100; } |
164 | |
165 | double maPlusPercent(dbl ma1 default ma1, double percent) { ret ma1*(1+percent/100); } |
166 | double maMinusPercent(dbl ma1 default ma1, double percent) { ret ma1*(1-percent/100); } |
167 | |
168 | { |
169 | granularity(30); |
170 | |
171 | onNewPrice(price -> ngsStep()); |
172 | onCandleCompleted(candle -> { |
173 | lastMA1(ma1); |
174 | ma1(movingAverage1()!); |
175 | ma2(movingAverage2()!); |
176 | maMove((ma1/lastMA1-1)*100); |
177 | maMoveHistory?.addIfPriceChanged(maMove, currentTime()); |
178 | if (maMove >= maMinMove) |
179 | maDirection(1); |
180 | else if (maMove <= -maMinMove) |
181 | maDirection(-1); |
182 | else if (maDirection < 0 && maMove >= -maMinMove2() |
183 | || maDirection > 0 && maMove <= maMinMove2()) |
184 | maDirection(0); |
185 | maDirectionHistory?.addIfPriceChanged(maDirection, currentTime()); |
186 | ngsStep(); |
187 | }); |
188 | } |
189 | |
190 | double maMinMove2() { ret maMinMove-maMoveHysteresis; } |
191 | |
192 | LS status() { |
193 | ret listCombine( |
194 | "Min distance, MA min move: " + maMinMove + " (current move: " + formatDouble(maMove, 4) + ", direction: " + maDirection + ")", |
195 | "MA periods: " + period1 + " " + period2, |
196 | "Do: " + (!autoPositions ? "Only manual positions" : commaCombine(stringIf(doLongs(), "Longs"), stringIf(doShorts(), "Shorts"))), |
197 | "Moving averages: " + formatDouble_significant(ma1, 4) + ", " + formatDouble_significant(ma2, 4), |
198 | map(sides(), side -> firstToUpper(str(side)) + " side: " + commaCombine(side.status())), |
199 | super.status() |
200 | ); |
201 | } |
202 | |
203 | void afterStep :: before { |
204 | } |
205 | |
206 | bool doShorts() { ret shortSide.enabled; } |
207 | bool doLongs() { ret longSide.enabled; } |
208 | |
209 | L<Side> sides() { |
210 | ret ll(longSide, shortSide); |
211 | } |
212 | |
213 | int candlesNeededBeforeOperational() { ret period1; } |
214 | |
215 | bool usingCells() { false; } |
216 | |
217 | public void integrityCheck { |
218 | movingAverage1().integrityCheck(); |
219 | movingAverage2().integrityCheck(); |
220 | } |
221 | } |
Began life as a copy of #1036558
download show line numbers debug dex old transpilations
Travelled to 2 computer(s): mqqgnosmbjvj, wnsclhtenguj
No comments. add comment
Snippet ID: | #1036572 |
Snippet name: | NGSStrategy2 - "NGS" Strategy v2 #themoneymakingstrategy (not anymore though - now we have the Mystery Strategy etc) |
Eternal ID of this version: | #1036572/42 |
Text MD5: | 8171b475658e80e436a42215660863c9 |
Transpilation MD5: | 92e0f187d3228019ac821e357ef1d816 |
Author: | stefan |
Category: | javax / trading |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2023-04-29 00:33:17 |
Source code size: | 7045 bytes / 221 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 258 / 475 |
Version history: | 41 change(s) |
Referenced in: | [show references] |