Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

331
LINES

< > BotCompany Repo | #1036259 // Chaser

JavaX fragment (include) [tags: use-pretranspiled]

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  
}

Author comment

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