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

498
LINES

< > BotCompany Repo | #1036257 // Corridor - backup used in the real-money experiment

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

Libraryless. Click here for Pure Java version (25749L/159K).

1  
concept Corridor extends G22TradingStrategy {
2  
  // how many percent the crypto price needs to move
3  
  // for one profitable trade
4  
  settableWithVar double cellSize = 0.2;
5  
  
6  
  // how many cells the corridor has
7  
  settableWithVar int capacity = 6;
8  
  
9  
  settableWithVar int maxPositionsPerDirection = 4;
10  
  
11  
  settableWithVar bool openTwoPositionsAtStart = true;
12  
  settableWithVar bool alwaysOpenTwoPositions;
13  
  
14  
  // position options
15  
  settableWithVar double leverage = 1;
16  
  settableWithVar double marginPerPosition = 1; // in USDT
17  
  
18  
  // internal
19  
  
20  
  // primed = started but waiting for first price
21  
  settableWithVar bool primed;
22  
  
23  
  settableWithVar PriceDigitizer2 digitizer;
24  
  settableWithVar int direction;
25  
  
26  
  settableWithVar double epsilon = 1e-6;
27  
  
28  
  // maximum debt seen
29  
  settableWithVar double maxDebt;
30  
  //settableWithVar double minCoinProfit;
31  
  //settableWithVar double maxBoundCoin;
32  
33  
  record noeq Position(double openingPrice, double direction, double digitizedOpeningPrice) {
34  
    double closingPrice = Double.NaN;
35  
    long openingStep, closingStep;
36  
    gettable long openingTime;
37  
    gettable long closingTime;
38  
    double leverage;
39  
    settable double cryptoAmount;
40  
    settable O openError;
41  
    settable O closeReason;
42  
    settable O closeError;
43  
    gettable double margin;
44  
    settable bool openedOnMarket;
45  
    settable bool closedOnMarket;
46  
    
47  
    Corridor strategy() { ret Corridor.this; }
48  
    
49  
    {
50  
      // Corridor.this is not set when unstructuring
51  
      if (!dynamicObjectIsLoading()) {
52  
        openingStep = stepCount;
53  
        leverage = Corridor.this.leverage;
54  
        openingTime = currentTime();
55  
      }
56  
    }
57  
    
58  
    double digitizedOpeningPrice() { ret digitizedOpeningPrice; }
59  
60  
    bool isLong() { ret direction > 0; }
61  
    bool isShort() { ret direction < 0; }
62  
    
63  
    bool closed() { ret !isNaN(closingPrice); }
64  
    S type() { ret trading_directionToPositionType(direction); }
65  
    
66  
    double profitAtPrice(double price) {
67  
      ret ((price-openingPrice)/openingPrice*direction*100-adversity)*leverage;
68  
    }
69  
    
70  
    double profit() {
71  
      ret profitAtPrice(closed() ? closingPrice : currentPrice());
72  
    }
73  
    
74  
    double coinProfit() {
75  
      ret profit()/100*margin();
76  
    }
77  
    
78  
    void close(O closeReason) {
79  
      if (closed()) fail("Can't close again");
80  
      if (closeReason != null) closeReason(closeReason);
81  
      openPositions.remove(this);
82  
      closingPrice = currentPrice();
83  
      closingTime = currentTime();
84  
      closingStep = stepCount;
85  
      closedPositions.add(this);
86  
      realizedProfit += profit();
87  
      realizedCoinProfit += coinProfit();
88  
      closeOnMarket();
89  
      //print(this);
90  
      //printPositions();
91  
    }
92  
    
93  
    // Position.toString
94  
    toString {
95  
      ret commaCombine(
96  
        spaceCombine(
97  
          closed() ? "Closed " + formatLocalDateWithSeconds(closingTime()) : null,
98  
          formatDouble1(leverage) + "X " + upper(type())
99  
        ),
100  
        "opened " + formatLocalDateWithSeconds(openingTime()),
101  
        !closed() ? null : or(closeReason, "unknown reason"),
102  
        "profit: " + formatProfit(profit()) + "% (" + marginCoin + " " + plusMinusFix(formatPrice(coinProfit())) + ")",
103  
        "margin: " + marginCoin + " " + formatPrice(margin()),
104  
        "crypto: " + formatPrice(cryptoAmount),
105  
        "opening price: " + formatPriceX(openingPrice)
106  
           + " @ step " + openingStep,
107  
        !closed() ? null : "closing price: " + formatPriceX(closingPrice)
108  
          + " @ step " + closingStep,
109  
        //"digitized: " + formatPrice(digitizedOpeningPrice()),
110  
        );
111  
    }
112  
    
113  
    void closeOnMarket {
114  
      try {
115  
        if (market != null) {
116  
          log("Closing on market: " + this);
117  
          market.closePosition(new IFuturesMarket.CloseOrder()
118  
            .holdSide(HoldSide.fromInt(direction))
119  
            .cryptoAmount(cryptoAmount));
120  
          closedOnMarket(true);
121  
          change();
122  
        }
123  
      } catch e {
124  
        closeError = toPersistableThrowable(e);
125  
      }
126  
    }
127  
    
128  
    void open {
129  
      margin = cryptoAmount*openingPrice/leverage;
130  
      openPositions.add(this);
131  
    }
132  
    
133  
    void openOnMarket {
134  
      try {
135  
        if (market != null) {
136  
          log("Opening on market: " + this);
137  
          market.openPosition(new IFuturesMarket.OpenOrder()
138  
            .holdSide(HoldSide.fromInt(direction))
139  
            .cryptoAmount(cryptoAmount)
140  
            .leverage(leverage)
141  
            .isCross(true));
142  
          openedOnMarket(true);
143  
          change();
144  
        }
145  
      } catch e {
146  
        openError(toPersistableThrowable(e));
147  
        log("Open error:" + e);
148  
        close("Open error");
149  
      }
150  
    }
151  
  }
152  
      
153  
  gettable double currentPrice = 0;
154  
  
155  
  gettable double oldPrice = Double.NaN;
156  
  gettable double startingPrice = Double.NaN;
157  
  gettable int lastDirection;
158  
  gettable double realizedProfit;
159  
  gettable double realizedCoinProfit;
160  
  settableWithVar long stepCount;
161  
  settableWithVar long stepSince;
162  
  gettable new LinkedHashSet<Position> openPositions;
163  
  gettable new L<Position> closedPositions;
164  
165  
  Position openPosition(int direction) {
166  
    if (l(positionsInDirection(direction)) >= maxPositionsPerDirection) {
167  
      log("Not opening new position in direction " + direction + ", max reached");
168  
      null;
169  
    }
170  
    
171  
    var price = digitizedPrice();
172  
    if (isNaN(price)) price = digitizer.digitizeIndividually(currentPrice());
173  
    var p = new Position(currentPrice(), direction, price);
174  
    double cryptoAmount = marginPerPosition/price*leverage;
175  
    cryptoAmount = roundTo(cryptoStep, cryptoAmount);
176  
    p.cryptoAmount = max(minCrypto, cryptoAmount);
177  
    p.open();
178  
    //print("Opening " + p);
179  
    //printPositions();
180  
    p.openOnMarket();
181  
    ret p;
182  
  }
183  
  
184  
  bool hasPosition(double price, double direction) {
185  
    ret findPosition(price, direction) != null;
186  
  }
187  
  
188  
  Position closePosition(double price, double direction, O closeReason) {
189  
    var p = findPosition(price, direction);
190  
    p?.close(closeReason);
191  
    ret p;
192  
  }
193  
  
194  
  void closePositions(Cl<Position> positions, O closeReason default null) {
195  
    forEach(positions, -> .close(closeReason));
196  
  }
197  
  
198  
  Position findPosition(double digitizedPrice, double direction) {
199  
    ret firstThat(openPositions(), p ->
200  
      diffRatio(p.digitizedOpeningPrice(), digitizedPrice) <= epsilon() && p.direction == direction);
201  
  }
202  
  
203  
  Position openShort() { ret openPosition(-1); }
204  
  Position openLong() { ret openPosition(1); }
205  
  
206  
  void printPositions {
207  
    print(colonCombine(n2(openPositions, "open position"),
208  
      joinWithComma(openPositions)));
209  
  }
210  
  
211  
  bool started() { ret !isNaN(startingPrice); }
212  
  
213  
  void start {
214  
    if (started()) fail("Already started");
215  
    if (currentPrice() == 0)
216  
      ret with primed(true);
217  
    
218  
    // Save starting price, create price cells & digitizer
219  
    
220  
    startingPrice = currentPrice();
221  
    var priceCells = makePriceCells(startingPrice);
222  
    digitizer = new PriceDigitizer2(priceCells);
223  
    digitizer.verbose(verbose);
224  
    //digitizer.init(startingPrice);
225  
    
226  
    print("Starting CORRIDOR at " + startingPrice + " +/- " + cellSize);
227  
    
228  
    if (openTwoPositionsAtStart) {
229  
      nextStep();
230  
      openPosition(1);
231  
      openPosition(-1);
232  
      afterStep();
233  
    }
234  
  }
235  
  
236  
  void prices(double... prices) {
237  
    fOr (price : prices)
238  
      price(price);
239  
  }
240  
  
241  
  swappable PriceCells makePriceCells(double basePrice) {
242  
    //ret new RegularPriceCells(basePrice, cellSize);
243  
    ret new GeometricPriceCells(basePrice, cellSize);
244  
  }
245  
  
246  
  double digitizedPrice() { ret digitizer.digitizedPrice(); }
247  
  double lastDigitizedPrice() { ret digitizer.lastDigitizedPrice(); }
248  
  
249  
  void handleNewPriceInQ(double price) {
250  
    q.add(-> price(price));
251  
  }
252  
253  
  void currentPrice aka price(double price) {
254  
    if (currentPrice == price) ret;
255  
    currentPrice = price;
256  
    
257  
    if (!started()) {
258  
      if (primed) {
259  
        primed(false);
260  
        start();
261  
      }
262  
      ret;
263  
    }
264  
    
265  
    digitizer.digitize(price);
266  
    direction = sign(digitizedPrice()-lastDigitizedPrice());
267  
    
268  
    // No cell move?
269  
    if (direction == 0) ret with afterStep();
270  
271  
    nextStep();
272  
    //printWithPrecedingNL("New digitized price: " + digitizedPrice());
273  
274  
    if (started())
275  
      step();
276  
      
277  
    print(this);
278  
  }
279  
  
280  
  void nextStep {
281  
    ++stepCount;
282  
    stepSince(now());
283  
  }
284  
  
285  
  swappable void step {
286  
    double p1 = lastDigitizedPrice();
287  
    double p2 = digitizedPrice();
288  
    direction = sign(p2-p1);
289  
    if (direction == 0) ret;
290  
    
291  
    // Find winning positions to close
292  
    
293  
    new L<Position> toClose;
294  
    for (Position p : direction > 0
295  
      ? longPositionsAtOrBelowDigitizedPrice(p1)
296  
      : shortPositionsAtOrAboveDigitizedPrice(p1)) {
297  
      p.closeReason("WIN");
298  
      log("Closing winner position at " + currentPrice + " (" + p1 + " -> " + p2 + "): " + p);
299  
      toClose.add(p);
300  
    }
301  
    
302  
    // Dismantle corridor if too large
303  
    double limit = fromCellNumber(toCellNumber(digitizedPrice())-capacity*direction);
304  
    var toDismantle = direction > 0
305  
      ? shortPositionsAtOrBelowDigitizedPrice(limit)
306  
      : longPositionsAtOrAboveDigitizedPrice(limit);
307  
    print("DISMANTLE LIMIT: " + formatPriceX(limit));
308  
    for (Position p : toDismantle) {
309  
      p.closeReason("DISMANTLE");
310  
      toClose.add(p);
311  
    }
312  
      
313  
    //printVars("step", +p1, +p2, +direction, +closed, +opened);
314  
    
315  
    if (nempty(toClose))
316  
      closePositions(toClose);
317  
318  
    // Open backward position here (if not there yet)
319  
    if (!hasPosition(p2, -direction))
320  
      openPosition(-direction);
321  
      
322  
    // Open forward position if configured as such
323  
    if (alwaysOpenTwoPositions && !hasPosition(p2, direction))
324  
      openPosition(direction);
325  
      
326  
    afterStep();
327  
  }
328  
  
329  
  void afterStep {
330  
    maxDebt = max(maxDebt, debt());
331  
    //minCoinProfit = min(minCoinProfit, coinProfit());
332  
    //maxBoundCoin = max(maxBoundCoin, boundCoin());
333  
    maxInvestment = max(maxInvestment, investment());
334  
    change();
335  
  }
336  
  
337  
  double investment() {
338  
    ret boundCoin()-realizedCoinProfit;
339  
  }
340  
  
341  
  double boundCoin() {
342  
    ret max(openMargins(), max(0, -coinProfit()));
343  
  }
344  
  
345  
  double fromCellNumber(double cellNumber) { ret cells().fromCellNumber(cellNumber); }
346  
  double toCellNumber(double price) { ret cells().toCellNumber(price); }
347  
  
348  
  double unrealizedProfit() {
349  
    double sum = 0;
350  
    for (p : openPositions)
351  
      sum += p.profit();
352  
    ret sum;
353  
  }
354  
  
355  
  double openMargins() {
356  
    double sum = 0;
357  
    for (p : openPositions)
358  
      sum += p.margin();
359  
    ret sum;
360  
  }
361  
  
362  
  double unrealizedCoinProfit() {
363  
    double sum = 0;
364  
    for (p : openPositions)
365  
      sum += p.coinProfit();
366  
    ret sum;
367  
  }
368  
  
369  
  L<Position> positionsInDirection(int direction) {
370  
    ret filter(openPositions, p -> p.direction == direction);
371  
  }
372  
  
373  
  L<Position> longPositions() { ret positionsInDirection(1); }
374  
  L<Position> shortPositions() { ret positionsInDirection(-1); }
375  
  
376  
  L<Position> shortPositionsAtOrBelowPrice(double openingPrice) {
377  
    ret filter(openPositions, p -> p.isShort() && p.openingPrice <= openingPrice);
378  
  }
379  
  
380  
  L<Position> longPositionsAtOrAbovePrice(double openingPrice) {
381  
    ret filter(openPositions, p -> p.isLong() && p.openingPrice >= openingPrice);
382  
  }
383  
  
384  
  L<Position> shortPositionsAtOrBelowDigitizedPrice(double openingPrice) {
385  
    ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() <= openingPrice);
386  
  }
387  
  
388  
  L<Position> shortPositionsAtOrAboveDigitizedPrice(double openingPrice) {
389  
    ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() >= openingPrice);
390  
  }
391  
  
392  
  L<Position> longPositionsAtOrAboveDigitizedPrice(double openingPrice) {
393  
    ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() >= openingPrice);
394  
  }
395  
  
396  
  L<Position> longPositionsAtOrBelowDigitizedPrice(double openingPrice) {
397  
    ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() <= openingPrice);
398  
  }
399  
  
400  
  L<Position> negativePositions() {
401  
    ret filter(openPositions, p -> p.profit() < 0);
402  
  }
403  
  
404  
  double debt() {
405  
    ret max(0, -unrealizedCoinProfit());
406  
  }
407  
  
408  
  double profit() {
409  
    ret realizedProfit+unrealizedProfit();
410  
  }
411  
  
412  
  double coinProfit() {
413  
    ret realizedCoinProfit+unrealizedCoinProfit();
414  
  }
415  
  
416  
  S baseToString() {
417  
    ret colonCombine(
418  
      _conceptID() == 0 ? "" : "Strategy " + _conceptID(),
419  
      "Corridor");
420  
  }
421  
  
422  
  LS status() {
423  
    var r = currentCorridorPriceRange();
424  
    ret llNonNulls(
425  
      //baseToString(),
426  
      !primed() ? null : "Primed",
427  
      !started() ? null : "Started. current price: " + formatPriceX(currentPrice) + ", digitized: " + formatPriceX(digitizedPrice()),
428  
      "Leverage: " + leverage + ", margin per position: " + marginCoin+ " " + formatPrice(marginPerPosition),
429  
      "Cell size: " + formatPercentage(cellSize, 3) + ", max capacity: " + capacity,
430  
      !started() ? null : "Current corridor: " + (r == null ? "-" : formatPrice(r.start) + " to " + formatPrice(r.end) + " (" + formatPercentage(currentCorridorSizeInPercent(), 3) + ")"),
431  
      "Step " + n2(stepCount) + (stepSince == 0 ? "" : " since " + formatHoursMinutesColonSeconds(currentTime()-stepSince)),
432  
      "Profit: " + marginCoin + " " + plusMinusFix(formatMarginPrice(coinProfit())),
433  
      "Realized profit: " + marginCoin + " " + formatProfit(realizedCoinProfit) + " from " + n2(closedPositions, "closed position"),
434  
      "Unrealized profit: " + marginCoin + " " + formatProfit(unrealizedCoinProfit()) + " in " + n2(openPositions, "open position"),
435  
      "Debt: " + marginCoin + " " + formatProfit(debt()) + " (max seen: " + formatProfit(maxDebt) + ")",
436  
      "Investment used: " + marginCoin + " " + formatMarginPrice(maxInvestment()),
437  
    );
438  
  }
439  
  
440  
  LS fullStatus() {
441  
    ret listCombine(
442  
      status(),
443  
      "",
444  
      n2(openPositions, "open position") + ":",
445  
      reversed(openPositions),
446  
      "",
447  
      n2(closedPositions, "closed position") + ":",
448  
      reversed(closedPositions)
449  
    );
450  
  }
451  
  
452  
  S formatProfit(double x) {
453  
    ret plusMinusFix(formatDouble1(x));
454  
  }
455  
  
456  
  // Corridor.toString
457  
  toString { ret baseToString(); } 
458  
  
459  
  double maxCorridorPercentSize() {
460  
    ret capacity*cellSize;
461  
  }
462  
  
463  
  PriceCells cells() {
464  
    ret digitizer?.cells;
465  
  }
466  
  
467  
  DoubleRange currentCorridorPriceRange() {
468  
    double[] openingPrices = mapToDoubleArray(openPositions, ->.openingPrice);
469  
    ret doubleRange(doubleMin(openingPrices), doubleMax(openingPrices));
470  
  }
471  
  
472  
  double currentCorridorSizeInPercent() {
473  
    DoubleRange r = currentCorridorPriceRange();
474  
    ret r == null ? 0 : asPercentIncrease(r.end, r.start);
475  
  }
476  
477  
  S formatPriceX(double price) {
478  
    if (isNaN(price)) ret "-";
479  
    double num = cells().priceToCellNumber(price);
480  
    ret formatPrice(price) + " (C" + formatDouble2(num) + ")";
481  
  }
482  
  
483  
  void reset {
484  
    resetFields(this, [[
485  
      digitizer maxDebt direction oldPrice startingPrice              lastDirection realizedProfit realizedCoinProfit
486  
      stepCount
487  
      openPositions closedPositions
488  
    ]]);
489  
    change();
490  
  }
491  
  
492  
  Corridor emptyClone() {
493  
    //var clone = shallowClone(this);
494  
    var clone = shallowCloneToUnlistedConcept(this);
495  
    clone.reset();
496  
    ret clone;
497  
  }
498  
}

Author comment

Began life as a copy of #1036196

download  show line numbers  debug dex  old transpilations   

Travelled to 2 computer(s): mqqgnosmbjvj, wnsclhtenguj

No comments. add comment

Snippet ID: #1036257
Snippet name: Corridor - backup used in the real-money experiment
Eternal ID of this version: #1036257/2
Text MD5: a8ece345d4f92a456d8983d17960f1ee
Transpilation MD5: 66ca43f9d3185ce1fd51b7e67bc4f158
Author: stefan
Category: javax / gazelle 22
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-11-05 20:13:22
Source code size: 15569 bytes / 498 lines
Pitched / IR pitched: No / No
Views / Downloads: 63 / 92
Version history: 1 change(s)
Referenced in: [show references]