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: | 743 / 1146 |
| Version history: | 63 change(s) |
| Referenced in: | [show references] |