Transpiled version (30028L) is out of date.
1 | concept Chaser extends G22TradingStrategy { |
2 | // Juicer parameters |
3 | |
4 | // Maximum duration of a position |
5 | settable double maxPositionHours = 4; |
6 | |
7 | // Max loss per position before leverage |
8 | settable double maxLoss = 0.6; |
9 | |
10 | // Ordinary pullback (fixed price percentage) |
11 | settable double pullback = 0.4; |
12 | |
13 | // Relative pullback (percentage of profit) |
14 | settable double relativePullback; // e.g. 20 |
15 | settable double minPullback; // e.g. 0.1 |
16 | settable double maxPullback; // e.g. 4 |
17 | |
18 | settable Opener opener; |
19 | |
20 | // internal |
21 | |
22 | settableWithVar int lastDirection; |
23 | new RunLengthCounter<Int> rlc; |
24 | OpenSignal openSignal; |
25 | |
26 | // trigger = 0 to 1 |
27 | record noeq CloseSignal(S reason, double trigger) { |
28 | double trigger() { ret trigger; } |
29 | toString { ret "Close signal " + reason + " (" + iround(trigger*100) + "%)"; } |
30 | } |
31 | |
32 | // trigger = 0 to 1 |
33 | record noeq OpenSignal(S reason, double trigger, int direction) { |
34 | double trigger() { ret trigger; } |
35 | toString { ret "Open signal " + reason + " (" + iround(trigger*100) + "%)"; } |
36 | } |
37 | |
38 | persistable class BaseJuicer { |
39 | settable double maxLoss; |
40 | |
41 | L<CloseSignal> closeSignals(Position p) { |
42 | new L<CloseSignal> signals; |
43 | double profit = p.profitBeforeLeverage(); |
44 | |
45 | // How close are we to the max closing time? |
46 | if (p.maxClosingTime() < infinity()) |
47 | signals.add(new CloseSignal("Age", transformToZeroToOne(currentTime(), p.openingTime(), p.maxClosingTime()))); |
48 | |
49 | // How close are we to our loss limit? |
50 | if (profit < 0) |
51 | signals.add(new CloseSignal("Loss", doubleRatio(-profit, maxLoss)); |
52 | |
53 | ret signals; |
54 | } |
55 | } |
56 | |
57 | // Juicer with fixed pullback |
58 | persistable class Juicer extends BaseJuicer { |
59 | settable double pullback; |
60 | |
61 | *(double *maxLoss, double *pullback) {} |
62 | |
63 | toString { ret renderRecord("Juicer", +maxLoss, +pullback); } |
64 | |
65 | L<CloseSignal> closeSignals(Position p) { |
66 | L<CloseSignal> signals = super.closeSignals(p); |
67 | |
68 | double profit = p.profitBeforeLeverage(); |
69 | |
70 | // How close are we to the pullback limit? |
71 | if (profit >= 0) |
72 | signals.add(new CloseSignal("Profit", transformToZeroToOne(profit, |
73 | p.crest, p.crest-pullback)); |
74 | |
75 | ret signals; |
76 | } |
77 | } |
78 | |
79 | // Juicer with relative pullback |
80 | persistable class RelativePullbackJuicer extends BaseJuicer { |
81 | settable double relativePullback; // e.g. 20 |
82 | settable double minPullback; // e.g. 0.1 |
83 | settable double maxPullback; // e.g. 4 |
84 | |
85 | toString { ret renderRecord("RelativePullbackJuicer", +maxLoss, +relativePullback, +minPullback, +maxPullback); } |
86 | |
87 | L<CloseSignal> closeSignals(Position p) { |
88 | L<CloseSignal> signals = super.closeSignals(p); |
89 | |
90 | double profit = p.profitBeforeLeverage(); |
91 | |
92 | // How close are we to the pullback limit? |
93 | if (profit >= 0) |
94 | signals.add(new CloseSignal("Profit", transformToZeroToOne(profit, |
95 | p.crest, p.crest-calculatePullback(p.crest))); |
96 | |
97 | ret signals; |
98 | } |
99 | |
100 | double calculatePullback(double crest) { |
101 | ret clamp(relativePullback/100*crest, minPullback, maxPullback); |
102 | } |
103 | } |
104 | |
105 | abstract sclass Opener { |
106 | abstract OpenSignal openSignal(); |
107 | abstract Opener cloneInto(Chaser strat); |
108 | } |
109 | |
110 | record PreRunOpener(int minRunUp, int minRunDown) extends Opener { |
111 | Opener cloneInto(Chaser strat) { |
112 | ret strat.new PreRunOpener(minRunUp, minRunDown); |
113 | } |
114 | |
115 | OpenSignal openSignal() { |
116 | int direction = Chaser.this.direction; |
117 | if (direction == 0) null; |
118 | int preRun = direction > 1 ? minRunUp : minRunDown; |
119 | int runLength = toInt(rlc.runLength()); |
120 | |
121 | /*printConcat( |
122 | "[", |
123 | formatLocalDateWithSeconds(currentTime()), |
124 | "] ", |
125 | "Runlength ", |
126 | runLength, |
127 | " price: ", |
128 | formatPriceX(currentPrice()) |
129 | );*/ |
130 | |
131 | // TODO: even more precise signal |
132 | //if (runLength >= preRun) print("Pre-run signal!"); |
133 | ret new OpenSignal("RunLength " + runLength, doubleRatio(runLength, preRun), direction); |
134 | } |
135 | |
136 | toString { ret "PreRunOpener up " + minRunUp + ", down " + minRunDown; } |
137 | } |
138 | |
139 | persistable class Position extends G22TradingStrategy.Position { |
140 | settable OpenSignal openSignal; |
141 | settable BaseJuicer juicer; |
142 | |
143 | // highest profit this position has reached |
144 | double crest = -infinity(); |
145 | |
146 | double maxClosingTime() { |
147 | ret usingLiveData ? infinity() : openingTime()+hoursToMS(maxPositionHours); |
148 | } |
149 | |
150 | L<CloseSignal> closeSignals() { |
151 | ret juicer == null ?: juicer.closeSignals(this); |
152 | } |
153 | |
154 | // >= 1 to close |
155 | // otherwise keep open |
156 | double closeSignal() { |
157 | CloseSignal strongestSignal = closeSignalWithDescription(); |
158 | ret strongestSignal == null ? 0 : strongestSignal.trigger; |
159 | } |
160 | |
161 | CloseSignal closeSignalWithDescription() { |
162 | ret highestBy(closeSignals(), signal -> signal.trigger); |
163 | } |
164 | |
165 | void update { |
166 | crest = max(crest, profitBeforeLeverage()); |
167 | } |
168 | |
169 | toString { |
170 | var closeSignal = closed() ? null : closeSignalWithDescription(); |
171 | ret commaCombine(super.toString(), |
172 | closeSignal |
173 | ); |
174 | } |
175 | } |
176 | |
177 | LS status() { |
178 | ret listCombine( |
179 | commaCombine( |
180 | "Max loss: " + formatDouble3(maxLoss) + "%", |
181 | relativePullback == 0 |
182 | ? "pullback: " + formatDouble3(pullback) + "%" |
183 | : commaCombine( |
184 | "relative pullback: " + formatDouble3(relativePullback) + "%", |
185 | "min pullback: " + formatDouble3(minPullback) + "%", |
186 | "max pullback: " + formatDouble3(maxPullback) + "%", |
187 | ) |
188 | ), |
189 | "Opener: " + opener, |
190 | "Run length: " + signToUpDown(unnull(rlc.value())) + " " + rlc.runLength(), |
191 | openSignal, |
192 | super.status() |
193 | ); |
194 | } |
195 | |
196 | Position openPosition(OpenSignal openSignal) { |
197 | int direction = openSignal.direction; |
198 | if (l(positionsInDirection(direction)) > 0) { |
199 | //log("Not opening a second position in direction " + direction); |
200 | null; |
201 | } |
202 | |
203 | nextStep(); |
204 | new Position p; |
205 | p.openSignal(openSignal); |
206 | p.juicer(makeJuicer()); |
207 | ret openPosition(p, direction); |
208 | } |
209 | |
210 | BaseJuicer makeJuicer() { |
211 | if (relativePullback != 0) |
212 | ret new RelativePullbackJuicer() |
213 | .relativePullback(relativePullback) |
214 | .minPullback(minPullback) |
215 | .maxPullback(maxPullback) |
216 | .maxLoss(maxLoss); |
217 | else |
218 | ret new Juicer(maxLoss, pullback); |
219 | } |
220 | |
221 | void start { |
222 | if (started()) fail("Already started"); |
223 | if (currentPrice() == 0) |
224 | ret with primed(true); |
225 | |
226 | // Save starting price, create price cells & digitizer |
227 | |
228 | startingPrice = currentPrice(); |
229 | var priceCells = makePriceCells(startingPrice); |
230 | digitizer = new PriceDigitizer2(priceCells); |
231 | digitizer.verbose(verbose); |
232 | |
233 | log("Starting CHASER at " + startingPrice + " +/- " + cellSize); |
234 | } |
235 | |
236 | void price(double price) { |
237 | if (currentPrice == price) ret; |
238 | currentPrice = price; |
239 | |
240 | if (!started()) { |
241 | if (primed) { |
242 | primed(false); |
243 | start(); |
244 | nextStep(); |
245 | beforeStep(); |
246 | afterStep(); |
247 | } |
248 | ret; |
249 | } |
250 | |
251 | digitizer.digitize(price); |
252 | direction = sign(digitizedPrice()-lastDigitizedPrice()); |
253 | if (direction != 0) lastDirection = direction; |
254 | |
255 | if (started()) |
256 | step(); |
257 | } |
258 | |
259 | swappable void step { |
260 | // Update positions with new price |
261 | |
262 | for (p : openPositions()) { |
263 | cast p to Position; |
264 | p.update(); |
265 | } |
266 | |
267 | // Find positions to close |
268 | |
269 | L<G22TradingStrategy.Position> toClose = new L; |
270 | for (p : openPositions()) { |
271 | cast p to Position; |
272 | CloseSignal signal = p.closeSignalWithDescription(); |
273 | if (signal != null && signal.trigger() >= 1) { |
274 | p.closeReason(signal); |
275 | log("Closing position because " + signal + ": " + p); |
276 | toClose.add(p); |
277 | } |
278 | } |
279 | |
280 | if (nempty(toClose)) { |
281 | nextStep(); |
282 | closePositions(toClose); |
283 | afterStep(); |
284 | } |
285 | |
286 | beforeStep(); |
287 | double p1 = lastDigitizedPrice(); |
288 | double p2 = digitizedPrice(); |
289 | direction = sign(p2-p1); |
290 | |
291 | if (direction != 0) |
292 | rlc.add(direction); |
293 | |
294 | // Don't open anything while we're feeding back data |
295 | if (haveBackData()) { |
296 | openSignal = makeOpenSignal(); |
297 | if (openSignal != null && openSignal.trigger >= 1) |
298 | openPosition(openSignal); |
299 | |
300 | afterStep(); |
301 | } |
302 | } |
303 | |
304 | swappable OpenSignal makeOpenSignal() { |
305 | ret opener == null ?: opener.openSignal(); |
306 | } |
307 | |
308 | swappable void beforeStep() {} |
309 | |
310 | S baseToString() { |
311 | ret colonCombine( |
312 | _conceptID() == 0 ? "" : "Strategy " + _conceptID(), |
313 | "Chaser profit " + formatMarginProfit(coinProfit())); |
314 | } |
315 | |
316 | S fieldsToReset() { |
317 | ret lines(ll(super.fieldsToReset(), [[lastDirection rlc openSignal]])); |
318 | } |
319 | |
320 | Chaser emptyClone() { |
321 | Chaser strat = cast super.emptyClone(); |
322 | ret strat.opener(opener?.cloneInto(strat)); |
323 | } |
324 | |
325 | // Chaser.toString |
326 | toString { ret baseToString(); } |
327 | |
328 | selfType preRunOpener(int minRunUp, int minRunDown) { |
329 | ret opener(new PreRunOpener(minRunUp, minRunDown)); |
330 | } |
331 | } |
Began life as a copy of #1036196
download show line numbers debug dex old transpilations
Travelled to 2 computer(s): elmgxqgtpvxh, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1036259 |
Snippet name: | Chaser |
Eternal ID of this version: | #1036259/64 |
Text MD5: | eb33884196d87a21882b1ab93d00af52 |
Author: | stefan |
Category: | javax / gazelle 22 |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2023-03-16 16:38:40 |
Source code size: | 9527 bytes / 331 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 280 / 635 |
Version history: | 63 change(s) |
Referenced in: | [show references] |