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

1123
LINES

< > BotCompany Repo | #1036209 // G22TradingStrategy

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

Libraryless. Click here for Pure Java version (30654L/194K).

1  
abstract concept G22TradingStrategy extends ConceptWithChangeListeners is Comparable<G22TradingStrategy>, Juiceable {
2  
  S fieldsToReset aka fieldsToReset_G22TradingStrategy() {
3  
    ret [[
4  
      globalID active
5  
      q
6  
      archived primed usingLiveData 
7  
      backDataFed startTime deactivated closedItself
8  
      log timedLog currentTime currentPrice feedNote
9  
      direction
10  
      digitizer maxDebt direction oldPrice startingPrice
11  
      strategyCrest
12  
      maxInvestment maxDrawdown
13  
      realizedProfit realizedCoinProfit
14  
      realizedWins realizedCoinWins
15  
      realizedLosses realizedCoinLosses
16  
      stepCount stepSince
17  
      openPositions closedPositions positionsThatFailedToOpen
18  
      drift driftSystem
19  
    ]];
20  
  }
21  
  
22  
  settable bool verbose;
23  
  
24  
  gettable S globalID = aGlobalID();
25  
  
26  
  // user-given name, overriding the technical name
27  
  settable S customName;
28  
  
29  
  // user-given comment
30  
  settable S comment;
31  
32  
  gettable transient Q q = startQ();
33  
  
34  
  // link to market (optional)
35  
  transient settable IFuturesMarket market;
36  
  
37  
  settable bool mergePositionsForMarket;
38  
  
39  
  settableWithVar S cryptoCoin;
40  
  settableWithVar S marginCoin = "USDT";
41  
  
42  
  // Which exchange are we using ("Bitget", "ByBit" etc.)
43  
  settableWithVar S exchangeName;
44  
  
45  
  new Ref<G22TradingAccount> tradingAccount;
46  
  
47  
  // Optional: Exchange we get our price data from, if different from above
48  
  settableWithVar S priceDataFromExchange;
49  
  
50  
  // An identifier of the trading account (as of yet unused)
51  
  settableWithVar S accountName;
52  
  
53  
  // primed = started but waiting for first price
54  
  settableWithVar bool primed;
55  
  
56  
  settableWithVar bool usingLiveData;
57  
  settableWithVar bool doingRealTrades;
58  
  settableWithVar bool demoCoin;
59  
  settableWithVar bool active;
60  
  
61  
  // set to date when strategy ended itself
62  
  settableWithVar long closedItself;
63  
  
64  
  // set to date when strategy was deactivated (by itself or by the user)
65  
  settableWithVar long deactivated;
66  
  
67  
  // moved to archive (out of main list)
68  
  settableWithVar bool archived;
69  
  
70  
  // area where this strategy is held, e.g. "Candidates"
71  
  settableWithVar S area;
72  
  
73  
  settableWithVar double adversity = 0.2;
74  
  
75  
  // should migrate to the timedLog completely
76  
  settableWithVar new LS log;
77  
  
78  
  // O is always a string for now
79  
  settableWithVar new L<WithTimestamp<O>> timedLog;
80  
  
81  
  settableWithVar double epsilon = 1e-6;
82  
  
83  
  // increment of crypto we can bet on
84  
  settableWithVar double cryptoStep = 0.0001;
85  
  
86  
  // minimum crypto we need to bet on
87  
  settableWithVar double minCrypto = 0.0001;
88  
  
89  
  // maximum crypto we can bet on
90  
  settableWithVar double maxCrypto = infinity();
91  
  
92  
  settableWithVar double maxInvestment;
93  
  
94  
  // highest value of coinProfit
95  
  settableWithVar double strategyCrest;
96  
  
97  
  // Maximum drawdown seen on account (realized+unrealized)
98  
  settableWithVar double maxDrawdown;
99  
  
100  
  settableWithVar double cellSize = 1;
101  
  
102  
  // position options
103  
  settableWithVar double leverage = 1;
104  
  settableWithVar double marginPerPosition = 1; // in USDT
105  
  
106  
  // optional (for compounding): how much of the account's equity to risk per position
107  
  settableWithVar double riskPerTrade; // in percent
108  
  
109  
  // Equity risked in last trade
110  
  settableWithVar double riskedEquity;
111  
  
112  
  settableWithVar bool showClosedPositionsBackwards = true;
113  
  
114  
  // End strategy when this margin coin profit is reached.
115  
  settableWithVar double takeCoinProfit = infinity();
116  
  settableWithVar bool takeCoinProfitEnabled;
117  
  
118  
  // How many hours of recent back data this algorithm likes
119  
  // to be fed with prior to running live.
120  
  settableWithVar double backDataHoursWanted;
121  
  
122  
  // Did we feed the back data?
123  
  settableWithVar bool backDataFed;
124  
  
125  
  // Juicer that is used to stop the whole strategy depending
126  
  // on its profit (optional)
127  
  settableWithVar AbstractJuicer strategyJuicer;
128  
  
129  
  swappable long currentTime() { ret now(); }
130  
  
131  
  settable transient bool logToPrint = true;
132  
  
133  
  // Note from price feeder
134  
  settableWithVar S feedNote;
135  
  
136  
  // optimization to save fees
137  
  settableWithVar bool useDriftSystem;
138  
  
139  
  // The drift system we are connected to
140  
  settableWithVar G22DriftSystem driftSystem;
141  
  
142  
  // Set to the actual ticker used by the system
143  
  settableWithVar transient TickerSequence actualTicker;
144  
  
145  
  // How many minutes to wait after closing a position
146  
  // before a new one can be opened (0 for no cooldown)
147  
  settableWithVar double cooldownMinutes;
148  
  
149  
  // An image showing this strategy's current state
150  
  settable transient BufferedImage image;
151  
  settable transient byte[] imageForWebServer;
152  
  
153  
  // Initial assumed equity for backtest.
154  
  // If not zero, marginPerPosition will be scaled up and down
155  
  // relative to compoundingBaseEquity+realizedCoinProfit()
156  
  settableWithVar double compoundingBaseEquity;
157  
  
158  
  // Use the exchange's TP & SL features
159  
  settableWithVar bool tpSlOnPlatform;
160  
  
161  
  // Strategies are listed according to this key field (alphanum-IC)
162  
  settableWithVar S listOrder;
163  
  
164  
  // add fields here
165  
  
166  
  <A> A log(A o) {
167  
    if (o != null) {
168  
      S s = str(o);
169  
      long time = currentTime();
170  
      log.add(printIf(logToPrint, "[" + formatLocalDateWithSeconds(time) + "] " + s));
171  
      timedLog.add(withTimestamp(time, s));
172  
      change();
173  
    }
174  
    ret o;
175  
  }
176  
  
177  
  void logVars(O... o) {
178  
    log(renderVars(o));
179  
  }
180  
  
181  
  LS activationStatus() {
182  
    ret llNempties(
183  
      active ? "Active" : "Inactive",
184  
      exchangeName,
185  
      empty(cryptoCoin) ? null
186  
        : "coin: " + cryptoCoin + " over " + marginCoin
187  
          + stringIf(demoCoin, " (demo coin)"),
188  
      stringIf(market != null, "connected to market"),
189  
      stringIf(usingLiveData, "using live data"),
190  
      stringIf(doingRealTrades, "real trades")
191  
    );
192  
  }
193  
  
194  
  double unrealizedProfit() {
195  
    double sum = 0;
196  
    for (p : openPositions())
197  
      sum += p.profit();
198  
    ret sum;
199  
  }
200  
  
201  
  double openMargins() {
202  
    double sum = 0;
203  
    for (p : openPositions())
204  
      sum += p.margin();
205  
    ret sum;
206  
  }
207  
  
208  
  double unrealizedCoinProfit() {
209  
    ret unrealizedCoinProfitAtPrice(currentPrice);
210  
  }
211  
  
212  
  double unrealizedCoinProfitAtPrice(double price) {
213  
    double sum = 0;
214  
    for (p : openPositions)
215  
      sum += p.coinProfitAtPrice(price);
216  
    ret sum;
217  
  }
218  
  
219  
  L<Position> positionsInDirection(int direction) {
220  
    ret filter(openPositions, p -> sign(p.direction) == sign(direction));
221  
  }
222  
  
223  
  L<Position> longPositions() { ret positionsInDirection(1); }
224  
  L<Position> shortPositions() { ret positionsInDirection(-1); }
225  
  
226  
  L<Position> shortPositionsAtOrBelowPrice(double openingPrice) {
227  
    ret filter(openPositions, p -> p.isShort() && p.openingPrice <= openingPrice);
228  
  }
229  
  
230  
  L<Position> longPositionsAtOrAbovePrice(double openingPrice) {
231  
    ret filter(openPositions, p -> p.isLong() && p.openingPrice >= openingPrice);
232  
  }
233  
  
234  
  L<Position> negativePositions() {
235  
    ret filter(openPositions, p -> p.profit() < 0);
236  
  }
237  
  
238  
  double debt() {
239  
    ret max(0, -unrealizedCoinProfit());
240  
  }
241  
  
242  
  // TODO: remove
243  
  double profit() {
244  
    ret realizedProfit+unrealizedProfit();
245  
  }
246  
  
247  
  double coinProfit() {
248  
    ret realizedCoinProfit+unrealizedCoinProfit();
249  
  }
250  
  
251  
  double coinProfitAtPrice(double price) {
252  
    ret realizedCoinProfit+unrealizedCoinProfitAtPrice(price);
253  
  }
254  
  
255  
  S formatProfit(double x) {
256  
    ret plusMinusFix(formatDouble1(x));
257  
  }
258  
  
259  
  settableWithVar PriceDigitizer2 digitizer;
260  
  
261  
  // TODO: This doesn't belong here
262  
  settableWithVar int direction;
263  
  
264  
  // maximum debt seen
265  
  settableWithVar double maxDebt;
266  
  //settableWithVar double minCoinProfit;
267  
  //settableWithVar double maxBoundCoin;
268  
269  
  class Position {
270  
    gettable S positionID = aGlobalID();
271  
272  
    settable double marginToUse;
273  
    settable double openingPrice;
274  
    settable double direction;
275  
    settable double digitizedOpeningPrice;
276  
    gettable double closingPrice = Double.NaN;
277  
    long openingStep, closingStep;
278  
    gettable long openingTime;
279  
    gettable long closingTime;
280  
    double leverage;
281  
    settable double cryptoAmount;
282  
    settable O openReason;
283  
    settable O openError;
284  
    settable O closeReason;
285  
    settable O closeError;
286  
    gettable double margin;
287  
    settable bool openedOnMarket;
288  
    settable S openOrderID; // open order ID returned from platform
289  
    settable bool closedOnMarket;
290  
    settable bool dontCloseOnMarket;
291  
    settable S comment;
292  
    settable L<AbstractJuicer> juicers;
293  
    
294  
    settable Double tpPrice;
295  
    settable Double slPrice;
296  
    
297  
    // highest and lowest unrealized P&L seen (after leverage)
298  
    settable double crest = negativeInfinity();
299  
    settable double antiCrest = infinity();
300  
301  
    G22TradingStrategy strategy() { ret G22TradingStrategy.this; }
302  
    
303  
    {
304  
      if (!dynamicObjectIsLoading()) {
305  
        marginToUse = marginToUseForNewPosition();
306  
        openingStep = stepCount;
307  
        leverage = G22TradingStrategy.this.leverage;
308  
      }
309  
    }
310  
    
311  
    bool isLong() { ret direction > 0; }
312  
    bool isShort() { ret direction < 0; }
313  
    
314  
    bool closed() { ret !isNaN(closingPrice); }
315  
    S type() { ret trading_directionToPositionType(direction); }
316  
    
317  
    long closingOrCurrentTime() { ret closed() ? closingTime() : currentTime(); }
318  
    
319  
    long duration() { ret closingOrCurrentTime()-openingTime(); }
320  
    
321  
    double profitAtPrice(double price) {
322  
      ret profitAtPriceBeforeLeverage(price)*leverage;
323  
    }
324  
    
325  
    double profitAtPriceBeforeLeverage(double price) {
326  
      ret ((price-openingPrice)/openingPrice*direction*100-adversity);
327  
    }
328  
    
329  
    double workingPrice() {
330  
      ret closed() ? closingPrice : currentPrice();
331  
    }
332  
    
333  
    double profit() {
334  
      ret profitAtPrice(workingPrice());
335  
    }
336  
    
337  
    double profitBeforeLeverage() {
338  
      ret profitAtPriceBeforeLeverage(workingPrice());
339  
    }
340  
    
341  
    settable Double imaginaryProfit;
342  
    
343  
    double coinProfit() {
344  
      try object imaginaryProfit;
345  
      ret coinProfitAtPrice(workingPrice());
346  
    }
347  
    
348  
    bool isOpenError() { ret openError != null; }
349  
    
350  
    double coinProfitAtPrice(double price) {
351  
      ret isOpenError() ? 0 : profitAtPrice(price)/100*margin();
352  
    }
353  
    
354  
    void close(O closeReason) {
355  
      if (closed()) fail("Can't close again");
356  
      if (closeReason != null) closeReason(closeReason);
357  
      closingPrice = currentPrice();
358  
      closingTime = currentTime();
359  
      closingStep = stepCount;
360  
      closeOnMarket();
361  
      
362  
      // This only executes when the market close succeeds
363  
      openPositions.remove(this);
364  
      if (!isOpenError()) 
365  
        closedPositions.add(this);
366  
      addToRealizedStats();
367  
      log(this);
368  
      //print(this);
369  
      //printPositions();
370  
    }
371  
    
372  
    Position partialClose(double amount, O closeReason) {
373  
      new Position p;
374  
      p.cryptoAmount = cryptoAmount-amount;
375  
      log("Partial close (" + amount + ") of " + this + ", creating remainder position with amount " + p.cryptoAmount + ". Reason: " + closeReason);
376  
      cryptoAmount = amount;
377  
      ret openPosition(p, (int) direction, closeReason);
378  
    }
379  
    
380  
    void makeFake(double coinProfit) {
381  
      closeReason("Fake");
382  
      imaginaryProfit(coinProfit);
383  
      closedPositions.add(this);
384  
      addToRealizedStats();
385  
      log(this);
386  
    }
387  
    
388  
    void addToRealizedStats {
389  
      double cp = coinProfit();
390  
      realizedProfit += profit();
391  
      realizedCoinProfit += cp;
392  
      if (cp > 0) {
393  
        realizedWins++;
394  
        realizedCoinWins += cp;
395  
      } else {
396  
        realizedLosses++;
397  
        realizedCoinLosses += cp;
398  
      }
399  
      change();
400  
    }
401  
    
402  
    S winnerOrLoser() {
403  
      var profit = coinProfit();
404  
      if (profit == 0) ret "NEUTRAL";
405  
      if (profit > 0) ret "WIN";
406  
      ret "LOSS";
407  
    }
408  
    
409  
    // Position.toString
410  
    toString {
411  
      ret commaCombine(
412  
        spaceCombine(
413  
          !closed() ? null :
414  
            winnerOrLoser()
415  
              + appendBracketed(strOrNull(closeReason))
416  
              + appendBracketed(comment)
417  
              + " " + formatLocalDateWithSeconds(closingTime())
418  
              + " " + formatProfit(profit()) + "% (" + marginCoin + " " + formatMarginProfit(coinProfit()) + ")"
419  
              + " (min " + formatProfit(antiCrest())
420  
              + ", max " + formatProfit(crest()) + ")"
421  
              + ", " + formatDouble1(leverage) + "X " + upper(type())
422  
              + " held " + formatHoursMinutesColonSeconds(duration()),
423  
          
424  
        ),
425  
        openingTime() == 0 ? null : "opened " + formatLocalDateWithSeconds(openingTime())
426  
          + (openReason == null ? "" : " " + roundBracket(str(openReason))),
427  
        "before leverage: " + formatProfit(profitBeforeLeverage()) + "%",
428  
        "margin: " + marginCoin + " " + formatMarginPrice(margin()),
429  
        "crypto: " + formatPrice(cryptoAmount),
430  
        "opening price: " + formatPriceX(openingPrice)
431  
           + (isNaN(digitizedOpeningPrice()) ? "" : " (digitized: " + formatPrice(digitizedOpeningPrice())
432  
           + ")") + (openingStep == 0 ? "" : " @ step " + openingStep),
433  
        !closed() ? null : "closing price: " + formatPriceX(closingPrice)
434  
          + (closingStep == 0 ? "" : " @ step " + closingStep),
435  
        );
436  
    }
437  
    
438  
    void closeOnMarket {
439  
      if (dontCloseOnMarket) ret;
440  
      if (isOpenError()) ret with log("Not closing because open error: " + this);
441  
      try {
442  
        if (market != null) {
443  
          log("Closing on market: " + this);
444  
          market.closePosition(new IFuturesMarket.CloseOrder()
445  
            .holdSide(HoldSide.fromInt(direction))
446  
            .cryptoAmount(cryptoAmount)
447  
            .leverage(leverage));
448  
          closedOnMarket(true);
449  
          change();
450  
        }
451  
      } on fail e {
452  
        closeError(toPersistableThrowable(e));
453  
      }
454  
    }
455  
    
456  
    void open {
457  
      margin = cryptoAmount*openingPrice/leverage;
458  
      log("Opening: " + this);
459  
      openingTime = currentTime();
460  
      openPositions.add(this);
461  
      change();
462  
    }
463  
    
464  
    void openOnMarket {
465  
      try {
466  
        if (market != null) {
467  
          log("Opening on market: " + this);
468  
          var order = new IFuturesMarket.OpenOrder()
469  
            .clientOrderID(positionID())
470  
            .holdSide(HoldSide.fromInt(direction))
471  
            .cryptoAmount(cryptoAmount)
472  
            .leverage(leverage)
473  
            .isCross(true);
474  
            
475  
          if (tpSlOnPlatform) {
476  
            if (tpPrice != null)
477  
              order.takeProfitPrice(tpPrice);
478  
            if (slPrice != null)
479  
              order.stopLossPrice(slPrice);
480  
          }
481  
          
482  
          market.openPosition(order);
483  
          openOrderID(order.orderID());
484  
          openedOnMarket(true);
485  
          change();
486  
        }
487  
      } catch e {
488  
        openError(toPersistableThrowable(e));
489  
        log("Open error: " + getStackTrace(e));
490  
        positionsThatFailedToOpen.add(this);
491  
        close("Open error");
492  
      }
493  
    }
494  
    
495  
    void updateStats {
496  
      double profit = profit();
497  
      crest(max(crest, profit));
498  
      antiCrest(min(antiCrest, profit));
499  
    }
500  
    
501  
    void addJuicer(AbstractJuicer juicer) {
502  
      juicers(listCreateAndAdd(juicers, juicer));
503  
    }
504  
    
505  
    void removeJuicer(AbstractJuicer juicer) {
506  
      juicers(removeDyn(juicers, juicer));
507  
    }
508  
    
509  
    // Call this when position was closed manually on the platform
510  
    void notification_closedOutsideOfStrategy() {
511  
      closedOnMarket(true);
512  
      close("Closed outside of strategy");
513  
    }
514  
    
515  
  } // end of Position
516  
      
517  
  gettable double currentPrice = 0;
518  
  
519  
  gettable double oldPrice = Double.NaN;
520  
  gettable double startingPrice = Double.NaN;
521  
  settable long startTime;
522  
  settableWithVar double realizedProfit;
523  
  settableWithVar double realizedCoinProfit;
524  
  settableWithVar int realizedWins;
525  
  settableWithVar double realizedCoinWins;
526  
  settableWithVar int realizedLosses;
527  
  settableWithVar double realizedCoinLosses;
528  
  settableWithVar long stepCount;
529  
  settableWithVar long stepSince;
530  
  new LinkedHashSet<Position> openPositions;
531  
  gettable new L<Position> closedPositions;
532  
  gettable new L<Position> positionsThatFailedToOpen;
533  
  
534  
  void closeOnMarketMerged(Cl<? extends Position> positions) {
535  
    if (market == null) ret;
536  
    closeOnMarketMerged_oneDirection(filter(positions, -> .isShort()));
537  
    closeOnMarketMerged_oneDirection(filter(positions, -> .isLong()));
538  
  }
539  
  
540  
  // all positions must have the same direction
541  
  void closeOnMarketMerged_oneDirection(L<? extends Position> positions) {
542  
    if (empty(positions)) ret;
543  
    
544  
    double direction = first(positions).direction;
545  
    double cryptoAmount = doubleSum(positions, -> .cryptoAmount);
546  
    log("Closing on market: " + positions);
547  
    
548  
    try {
549  
      market.closePosition(new IFuturesMarket.CloseOrder()
550  
        .holdSide(HoldSide.fromInt(direction))
551  
        .cryptoAmount(cryptoAmount);
552  
        
553  
      for (p : positions)
554  
        p.closedOnMarket(true);
555  
      change();
556  
    } catch e {
557  
      var e2 = toPersistableThrowable(e);
558  
      for (p : positions)
559  
        p.closeError(e2);
560  
    }
561  
  }
562  
563  
  bool hasPosition(double price, double direction) {
564  
    ret findPosition(price, direction) != null;
565  
  }
566  
  
567  
  Position closePosition(double price, double direction, O closeReason) {
568  
    var p = findPosition(price, direction);
569  
    p?.close(closeReason);
570  
    ret p;
571  
  }
572  
  
573  
  void closePositions(Cl<? extends Position> positions, O closeReason default null) {
574  
    if (mergePositionsForMarket) {
575  
      for (p : positions)
576  
        p.dontCloseOnMarket(true).close(closeReason);
577  
      closeOnMarketMerged(positions);
578  
    } else
579  
      forEach(positions, -> .close(closeReason));
580  
  }
581  
  
582  
  Position findPosition(double digitizedPrice, double direction) {
583  
    ret firstThat(openPositions(), p ->
584  
      diffRatio(p.digitizedOpeningPrice(), digitizedPrice) <= epsilon() && sign(p.direction) == sign(direction));
585  
  }
586  
  
587  
  void printPositions {
588  
    print(colonCombine(n2(openPositions, "open position"),
589  
      joinWithComma(openPositions)));
590  
  }
591  
  
592  
  bool started() { ret !isNaN(startingPrice); }
593  
  void prices(double... prices) {
594  
    fOr (price : prices) {
595  
      if (!active()) ret;
596  
      price(price);
597  
    }
598  
  }
599  
  
600  
  swappable PriceCells makePriceCells(double basePrice) {
601  
    ret new GeometricPriceCells(basePrice, cellSize);
602  
  }
603  
  
604  
  double digitizedPrice() { ret digitizer == null ? Double.NaN : digitizer.digitizedPrice(); }
605  
  double lastDigitizedPrice() { ret digitizer == null ? Double.NaN : digitizer.lastDigitizedPrice(); }
606  
  int digitizedCellNumber() { ret digitizer == null ? 0 : digitizer.cellNumber(); }
607  
  
608  
  void handleNewPriceInQ(double price) {
609  
    q.add(-> price(price));
610  
  }
611  
612  
  void nextStep {
613  
    ++stepCount;
614  
    stepSince(currentTime());
615  
  }
616  
  
617  
  void afterStep {
618  
    double coinProfit = coinProfit();
619  
    maxDebt(max(maxDebt, debt()));
620  
    //minCoinProfit = min(minCoinProfit, coinProfit);
621  
    //maxBoundCoin = max(maxBoundCoin, boundCoin());
622  
    maxInvestment(max(maxInvestment, investment()));
623  
    strategyCrest(max(strategyCrest, coinProfit));
624  
    maxDrawdown(max(maxDrawdown, strategyCrest-coinProfit));
625  
    if (takeCoinProfitEnabled() && coinProfit >= takeCoinProfit) {
626  
      log("Taking coin profit.");
627  
      closeMyself();
628  
    }
629  
    
630  
    for (p : openPositions) p.updateStats();
631  
  }
632  
  
633  
  double investment() {
634  
    ret boundCoin()-realizedCoinProfit;
635  
  }
636  
  
637  
  double boundCoin() {
638  
    ret max(openMargins(), max(0, -coinProfit()));
639  
  }
640  
  
641  
  double fromCellNumber(double cellNumber) { ret cells().fromCellNumber(cellNumber); }
642  
  double toCellNumber(double price) { ret cells().toCellNumber(price); }
643  
  
644  
  L<Position> shortPositionsAtOrBelowDigitizedPrice(double openingPrice) {
645  
    ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() <= openingPrice);
646  
  }
647  
  
648  
  L<Position> shortPositionsAtOrAboveDigitizedPrice(double openingPrice) {
649  
    ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() >= openingPrice);
650  
  }
651  
  
652  
  L<Position> longPositionsAtOrAboveDigitizedPrice(double openingPrice) {
653  
    ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() >= openingPrice);
654  
  }
655  
  
656  
  L<Position> longPositionsAtOrBelowDigitizedPrice(double openingPrice) {
657  
    ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() <= openingPrice);
658  
  }
659  
  
660  
  PriceCells cells aka priceCells() {
661  
    ret digitizer?.cells;
662  
  }
663  
  
664  
  S formatPriceX(double price) {
665  
    if (isNaN(price)) ret "-";
666  
    S s = formatPrice(price);
667  
    if (cells() == null) ret s;
668  
    double num = cells().priceToCellNumber(price);
669  
    ret s + " (C" + formatDouble2(num) + ")";
670  
  }
671  
  
672  
  abstract void price aka currentPrice(double price);
673  
674  
  bool inCooldown() {  
675  
    if (cooldownMinutes <= 0) false;
676  
    if (nempty(openPositions)) true;
677  
    var lastClosed = last(closedPositions);
678  
    if (lastClosed == null) false;
679  
    var minutes = toMinutes(currentTime()-lastClosed.closingTime);
680  
    ret minutes < cooldownMinutes;
681  
  }
682  
  
683  
  void assureCanOpen(Position p) {
684  
    if (cooldownMinutes > 0) {
685  
      if (nempty(openPositions))
686  
        fail("Already have an open position");
687  
      var lastClosed = last(closedPositions);
688  
      if (lastClosed != null) {
689  
        var minutes = toMinutes(currentTime()-lastClosed.closingTime);
690  
        if (minutes < cooldownMinutes)
691  
          fail("Last position closed " + formatMinutes(fromMinutes(minutes)) + " ago, cooldown is " + cooldownMinutes);
692  
      }
693  
    }
694  
  }
695  
  
696  
  <P extends Position> P openPosition(P p, int direction, O openReason default null) {
697  
    try {
698  
      assureCanOpen(p);
699  
  
700  
      p.openReason(openReason);
701  
      var price = digitizedPrice();
702  
      var realPrice = currentPrice();
703  
      logVars("openPosition", +realPrice, +price, +digitizer);
704  
      if ((isNaN(price) || price == 0) && digitizer != null) {
705  
        price = digitizer.digitizeIndividually(currentPrice());
706  
        print("digitized individually: " + price);
707  
      }
708  
      p.openingPrice(realPrice);
709  
      p.direction(direction);
710  
      p.digitizedOpeningPrice(price);
711  
      
712  
      // Calculate quantity (cryptoAmount) from margin
713  
      // unless cryptoAmount is already set
714  
      
715  
      if (p.cryptoAmount == 0)
716  
        p.cryptoAmount = p.marginToUse/realPrice*leverage;
717  
        
718  
      // Round cryptoAmount to the allowed increments
719  
      p.cryptoAmount = roundTo(cryptoStep, p.cryptoAmount);
720  
      
721  
      // Clamp to minimum/maximum order
722  
      p.cryptoAmount = clamp(p.cryptoAmount, minCrypto, maxCrypto);
723  
      
724  
      log(renderVars("openPosition", +marginPerPosition, +realPrice, +leverage, cryptoAmount := p.cryptoAmount, +cryptoStep));
725  
      p.open();
726  
      //print("Opening " + p);
727  
      //printPositions();
728  
      p.openOnMarket();
729  
      ret p;
730  
    } on fail e {
731  
      log(e);
732  
    }
733  
  }
734  
  
735  
  LS status() {
736  
    double mulProf = multiplicativeProfit();
737  
    ret llNonNulls(
738  
      empty(comment) ? null : "Comment: " + comment,
739  
      "Profit: " + marginCoin + " " + plusMinusFix(formatMarginPrice(coinProfit())),
740  
      "Realized profit: " + marginCoin + " " + formatMarginProfit(realizedCoinProfit) + " from " + n2(closedPositions, "closed position")
741  
        + " (" + formatMarginProfit(realizedCoinWins) + " from " + n2(realizedWins, "win")
742  
        + ", " + formatMarginProfit(realizedCoinLosses) + " from " + n2(realizedLosses, "loss", "losses") + ")",
743  
      "Unrealized profit: " + marginCoin + " " + formatMarginProfit(unrealizedCoinProfit()) + " in " + n2(openPositions, "open position"),
744  
      isNaN(mulProf) ? null : "Multiplicative profit: " + formatProfit(mulProf) + "%",
745  
      //baseToString(),
746  
      !primed() ? null : "Primed",
747  
      !started() ? null : "Started. current price: " + formatPriceX(currentPrice)
748  
        + (isNaN(digitizedPrice()) ? "" : ", digitized: " + formatPriceX(digitizedPrice())),
749  
      (riskPerTrade == 0 ? "" : "Risk per trade: " + formatDouble1(riskPerTrade) + "%. ")
750  
      + "Position size: " + marginCoin + " " + formatPrice(marginPerPosition) + "x" + formatDouble1(leverage) + " = " + marginCoin + " " + formatPrice(positionSize()),
751  
      !usingCells() ? null : "Cell size: " + formatCellSize(cellSize),
752  
      spaceCombine("Step " + n2(stepCount), renderStepSince()),
753  
      //"Debt: " + marginCoin + " " + formatMarginProfit(debt()) + " (max seen: " + formatMarginProfit(maxDebt) + ")",
754  
      "Investment used: " + marginCoin + " " + formatMarginPrice(maxInvestment()),
755  
      strategyJuicer == null ?: "Strategy juicer: " + strategyJuicer,
756  
      //"Drift: " + cryptoCoin + " " + plusMinusFix(formatCryptoAmount(drift())),
757  
    );
758  
  }
759  
  
760  
  S renderStepSince() {
761  
    if (stepSince == 0) ret "";
762  
    ret "since " + (active()
763  
      ? formatHoursMinutesColonSeconds(currentTime()-stepSince)
764  
      : formatLocalDateWithSeconds(stepSince));
765  
  }
766  
  
767  
  LS fullStatus() {
768  
    ret listCombine(
769  
      tradingAccount,
770  
      status(),
771  
      "",
772  
      n2(openPositions, "open position") + ":",
773  
      reversed(openPositions),
774  
      "",
775  
      n2(closedPositions, "closed position")
776  
        + " (" + (showClosedPositionsBackwards ? "latest first" : "oldest first") + "):",
777  
      showClosedPositionsBackwards ? reversed(closedPositions) : closedPositions,
778  
    );
779  
  }
780  
  
781  
  void feed(PricePoint pricePoint) {
782  
    if (!active()) ret;
783  
    setTime(pricePoint.timestamp);
784  
    price(pricePoint.price);
785  
  }
786  
  
787  
  void feed(TickerSequence ts) {
788  
    if (!active()) ret;
789  
    if (ts == null) ret;
790  
    for (pricePoint : ts.pricePoints())
791  
      feed(pricePoint);
792  
  }
793  
  
794  
  public int compareTo(G22TradingStrategy s) {
795  
    ret s == null ? 1 : cmp(coinProfit(), s.coinProfit());
796  
  }
797  
  
798  
  // Returns closed positions
799  
  L<Position> closeAllPositions(O reason default "User close") {
800  
    var positions = openPositions();
801  
    closePositions(positions, reason);
802  
    ret (L) positions;
803  
  }
804  
  
805  
  void closeMyself() {
806  
    closedItself(currentTime());
807  
    closeAllPositionsAndDeactivate();
808  
  }
809  
  
810  
  void closeAllPositionsAndDeactivate {
811  
    deactivate();
812  
    closeAllPositions();
813  
  }
814  
  
815  
  void deactivate {
816  
    market(null);
817  
    if (!active) ret;
818  
    active(false);
819  
    deactivated(currentTime());
820  
    log("Strategy deactivated.");
821  
  }
822  
  
823  
  void reset aka reset_G22TradingStrategy() {
824  
    resetFields(this, fieldsToReset());
825  
    change();
826  
  }
827  
  
828  
  selfType emptyClone aka emptyClone_G22TradingStrategy() {
829  
    var clone = shallowCloneToUnlistedConcept(this);
830  
    clone.reset();
831  
    ret clone;
832  
  }
833  
  
834  
  L<Position> allPositions() {
835  
    ret concatLists(openPositions, closedPositions);
836  
  }
837  
  
838  
  L<Position> sortedPositions() {
839  
    var allPositions = allPositions();
840  
    ret sortedByCalculatedField(allPositions, -> .openingTime());
841  
  }
842  
  
843  
  bool positionsAreNonOverlapping() {
844  
    for (a, b : unpair overlappingPairs(sortedPositions()))
845  
      if (b.openingTime() < a.closingTime())
846  
        false;
847  
    true;
848  
  }
849  
  
850  
  // Profit when applying all positions (somewhat theoretical because you
851  
  // might go below platform limits)
852  
  // Also only works when positions are linear
853  
  double multiplicativeProfit() {
854  
    if (!positionsAreNonOverlapping()) ret Double.NaN;
855  
    double profit = 1;
856  
    for (p : sortedPositions())
857  
      profit *= 1+p.profit()/100;
858  
    ret (profit-1)*100;
859  
  }
860  
  
861  
  bool haveBackData() { ret backDataHoursWanted == 0 | backDataFed; }
862  
  
863  
  bool didRealTrades() {
864  
    ret any(allPositions(), p -> p.openedOnMarket() || p.closedOnMarket());
865  
  }
866  
  
867  
  S formatCellSize(double cellSize) {
868  
    ret formatPercentage(cellSize, 3);
869  
  }
870  
  
871  
  S areaDesc() {
872  
    if (eq(area, "Candidates")) ret "Candidate";
873  
    ret nempty(area) ? area : archived ? "Archived" : "";
874  
  }
875  
  
876  
  selfType setTime aka currentTime(long time) {
877  
    int age = ifloor(ageInHours());
878  
    long lastMod = mod(currentTime()-startTime, hoursToMS(1));
879  
    currentTime = -> time;
880  
    if (ifloor(ageInHours()) > age)
881  
      log("Hourly profit log: " + formatMarginProfit(coinProfit()));
882  
    this;
883  
  }
884  
  
885  
  double ageInHours() { ret startTime == 0 ? 0 : msToHours(currentTime()-startTime); }
886  
  
887  
  // only use near start of strategy, untested otherwise
888  
  selfType changeCellSize(double newCellSize) {
889  
    double oldCellSize = cellSize();
890  
    if (oldCellSize == newCellSize) this;
891  
    cellSize(newCellSize);
892  
    if (digitizer != null)
893  
      digitizer.swapPriceCells(makePriceCells(priceCells().basePrice()));
894  
    log("Changed cell size from " + oldCellSize + " to " + newCellSize);
895  
    this;
896  
  }
897  
  
898  
  bool hasClosedItself() { ret closedItself != 0; }
899  
  
900  
  public double juiceValue() { ret coinProfit(); }
901  
  
902  
  // drift is our cumulative delta (=sum of all signed position
903  
  // quantities)
904  
  double drift() {
905  
    double drift = 0;
906  
    for (p : openPositions())
907  
      drift += p.cryptoAmount()*p.direction();
908  
    ret drift;
909  
  }
910  
  
911  
  S formatCryptoAmount(double amount) {
912  
    ret formatDouble3(amount);
913  
  }
914  
  
915  
  Position openShort() { ret openPosition(-1); }
916  
  Position openLong() { ret openPosition(1); }
917  
  
918  
  Position openPosition(int direction, O openReason default null) {
919  
    new Position p;
920  
    p.marginToUse = marginToUseForNewPosition();
921  
    ret openPosition(p, direction, openReason);
922  
  }
923  
  
924  
  // Open or close positions until the drift (delta) is
925  
  // equal to targetDrift (or as close as the platform's
926  
  // restrictions allow).
927  
  // Returns the list of positions opened or closed
928  
  // (Function is in dev.)
929  
  L<Position> adjustDrift(double targetDrift, O reason default null) {
930  
    new L<Position> changeList;
931  
    
932  
    // target 0? close all
933  
    if (targetDrift == 0)
934  
      ret closeAllPositions(reason);
935  
    
936  
    double drift = drift();
937  
    
938  
    // target already reached? done
939  
    if (drift == targetDrift) ret changeList;
940  
    
941  
    int direction = sign(targetDrift);
942  
    
943  
    // Are we changing direction? Then close everything first.
944  
    if (sign(drift) != direction)
945  
      changeList.addAll(closeAllPositions(reason));
946  
    
947  
    // Now we know targetDrift and drift have the same sign.
948  
    double diff = abs(targetDrift)-abs(drift);
949  
    
950  
    // Round to allow increments
951  
    diff = roundTo(cryptoStep, diff);
952  
    
953  
    if (diff > 0) {
954  
      // We need to open a new position - that's easy
955  
      
956  
      new Position p;
957  
      p.cryptoAmount = diff;
958  
      changeList.add(openPosition(p, direction, reason));
959  
    } else {
960  
      double toClose = -diff;
961  
      
962  
      // We need to close positions - that's a bit more tricky
963  
      
964  
      // Filter by direction to be sure in case there is
965  
      // hedging (you shouldn't do hedging, really, though)
966  
      var positions = filter(openPositions(), p -> p.direction() == direction);
967  
      
968  
      // Let's look for an exact size match first.
969  
      // We rounded to cryptoStep, so using == is ok.
970  
      var _toClose = toClose;
971  
      var exactMatch = firstThat(positions, p -> p.cryptoAmount == _toClose);
972  
      
973  
      if (exactMatch != null) {
974  
        exactMatch.close(reason);
975  
        changeList.add(exactMatch);
976  
        ret changeList;
977  
      }
978  
      
979  
      // No exact match. Go through positions starting with
980  
      // the oldest one.
981  
      
982  
      for (p : positions) {
983  
        toClose = roundTo(cryptoStep, toClose);
984  
        if (toClose == 0) break;
985  
        if (toClose >= p.cryptoAmount) {
986  
          // Need to close the whole position
987  
          toClose -= p.cryptoAmount;
988  
          p.close(reason);
989  
          changeList.add(p);
990  
        } else {
991  
          // Need a partial close.
992  
          
993  
          changeList.add(p);
994  
          var remainderPosition = p.partialClose(toClose, reason);
995  
          changeList.add(remainderPosition);
996  
        }
997  
      }
998  
    }
999  
    
1000  
    ret changeList;
1001  
  }
1002  
  
1003  
  L<Position> winners() { ret filter(closedPositions(), p -> p.coinProfit() > 0); }
1004  
  L<Position> losers() { ret filter(closedPositions(), p -> p.coinProfit() < 0); }
1005  
  
1006  
  L<? extends Position> openPositions() { ret cloneList(openPositions); }
1007  
  
1008  
  double winRate() {
1009  
    ret l(winners()) * 100.0 / l(closedPositions);
1010  
  }
1011  
  
1012  
  bool usingCells() { true; }
1013  
  
1014  
  double positionSize() {
1015  
    ret marginToUseForNewPosition()*leverage;
1016  
  }
1017  
  
1018  
  double marginToUseForNewPosition() {
1019  
    ret scaleMargin(marginPerPosition);
1020  
  }
1021  
  
1022  
  // to simulate compounding in backtest
1023  
  double scaleMargin(double margin) {
1024  
    // Live? Then no scaling.
1025  
    if (usingLiveData) ret margin;
1026  
    
1027  
    ret margin*compoundingFactor();
1028  
  }
1029  
  
1030  
  double compoundingFactor() {
1031  
    // No compounding selected?
1032  
    if (compoundingBaseEquity == 0) ret 1;
1033  
    
1034  
    ret max(0, remainingEquity()/compoundingBaseEquity);
1035  
  }
1036  
  
1037  
  // only for backtesting
1038  
  double remainingEquity() {
1039  
    ret compoundingBaseEquity+realizedCoinProfit();
1040  
  }
1041  
  
1042  
  class RiskToMargin {
1043  
    // in percent
1044  
    settable double riskForTrade = riskPerTrade;
1045  
    
1046  
    settable double stopLossPercent;
1047  
    settable double price = currentPrice();
1048  
    settable double fullEquity;
1049  
    
1050  
    double riskedEquity() {
1051  
      ret fullEquity*riskForTrade/100;
1052  
    }
1053  
    
1054  
    double qty() {
1055  
      ret riskedEquity()/(price*stopLossPercent/100);
1056  
    }
1057  
    
1058  
    double margin aka get() {
1059  
      ret qty()*price/leverage();
1060  
    }
1061  
  }
1062  
  
1063  
  void fixRealizedStats {
1064  
    realizedProfit(0);
1065  
    realizedCoinProfit(0);
1066  
    realizedWins(0);
1067  
    realizedCoinWins(0);
1068  
    realizedLosses(0);
1069  
    realizedCoinLosses(0);
1070  
    for (p : closedPositions()) p.addToRealizedStats();
1071  
  }
1072  
  
1073  
  // p must not be open
1074  
  bool deletePosition(Position p) {
1075  
    if (closedPositions.remove(p) || positionsThatFailedToOpen.remove(p)) {
1076  
      fixRealizedStats();
1077  
      true;
1078  
    }
1079  
    false;
1080  
  }
1081  
  
1082  
  void deletePositionsThatFailedToOpen() {
1083  
    for (p : cloneList(positionsThatFailedToOpen))
1084  
      deletePosition(p);
1085  
  }
1086  
  
1087  
  void deleteAllPositions() {
1088  
    for (p : cloneList(closedPositions))
1089  
      deletePosition(p);
1090  
  }
1091  
  
1092  
  bool hasRiskPerTrade() { ret riskPerTrade != 0; }
1093  
  
1094  
  Position addFakePosition(double coinProfit) {
1095  
    new Position p;
1096  
    p.makeFake(coinProfit);
1097  
    ret p;
1098  
  }
1099  
  
1100  
  // If there is exactly one open position:
1101  
  // Open another position in the same direction
1102  
  // with same size
1103  
  Position pyramid(O openReason default "Pyramiding") {
1104  
    ret extendedPyramid(2, openReason);
1105  
  }
1106  
1107  
  // level: pyramid level to be reached
1108  
  // (must be one higher than current number of positions
1109  
  // to trigger - no double adding at once)
1110  
  Position extendedPyramid(int level, O openReason default "Pyramiding") {
1111  
    if (l(openPositions()) != level-1)
1112  
      null;
1113  
      
1114  
    var p = first(openPositions);
1115  
    new Position p2;
1116  
    p2.cryptoAmount(p.cryptoAmount);
1117  
    ret openPosition(p2, sign(p.direction), openReason);
1118  
  }
1119  
  
1120  
  Position newPosition() {
1121  
    ret new Position;
1122  
  }
1123  
}

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): elmgxqgtpvxh, iveijnkanddl, mqqgnosmbjvj, wnsclhtenguj

Comments [hide]

ID Author/Program Comment Date
2144 ubataecj 555 2024-05-01 03:52:27
2142 ubataecj @@yT0NO 2024-05-01 03:38:41
2141 ubataecj 555????%2527%2522\'\" 2024-05-01 03:38:41
2140 ubataecj 555'" 2024-05-01 03:38:41
2139 ubataecj 555'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||' 2024-05-01 03:38:41
2135 ubataecj 555*DBMS_PIPE.RECEIVE_MESSAGE(CHR(99)||CHR(99)||CHR(99),15) 2024-05-01 03:38:37
2133 ubataecj 555v1xDKDb2')) OR 528=(SELECT 528 FROM PG_SLEEP(15))-- 2024-05-01 03:38:34
2131 ubataecj 555t5BAWglH') OR 402=(SELECT 402 FROM PG_SLEEP(15))-- 2024-05-01 03:38:28
2129 ubataecj 5557t8zcvB8' OR 234=(SELECT 234 FROM PG_SLEEP(15))-- 2024-05-01 03:38:25
2127 ubataecj 555-1)) OR 88=(SELECT 88 FROM PG_SLEEP(15))-- 2024-05-01 03:38:21
2125 ubataecj 555-1) OR 510=(SELECT 510 FROM PG_SLEEP(15))-- 2024-05-01 03:38:18
2123 ubataecj 555-1 OR 510=(SELECT 510 FROM PG_SLEEP(15))-- 2024-05-01 03:38:13
2121 ubataecj 555bz5UAASv'; waitfor delay '0:0:15' -- 2024-05-01 03:38:09
2119 ubataecj 555-1 waitfor delay '0:0:15' -- 2024-05-01 03:38:07
2117 ubataecj 555-1); waitfor delay '0:0:15' -- 2024-05-01 03:38:05
2115 ubataecj 555-1; waitfor delay '0:0:15' -- 2024-05-01 03:38:00
2113 ubataecj (select(0)from(select(sleep(15)))v)/*'+(select(0)from(select(sleep(15)))v)+'"+(select(0)from(select(sleep(15)))v)+"*/ 2024-05-01 03:37:57
2109 ubataecj 5550"XOR(555*if(now()=sysdate(),sleep(15),0))XOR"Z 2024-05-01 03:37:53
2106 ubataecj 5550'XOR(555*if(now()=sysdate(),sleep(15),0))XOR'Z 2024-05-01 03:37:51
2103 ubataecj 555*if(now()=sysdate(),sleep(15),0) 2024-05-01 03:37:49
2100 ubataecj -1" OR 2+540-540-1=0+0+0+1 -- 2024-05-01 03:37:46
2099 ubataecj -1' OR 2+480-480-1=0+0+0+1 or 'b3MLjESj'=' 2024-05-01 03:37:46
2098 ubataecj -1' OR 2+65-65-1=0+0+0+1 -- 2024-05-01 03:37:46
2097 ubataecj -1 OR 2+107-107-1=0+0+0+1 2024-05-01 03:37:46
2096 ubataecj -1 OR 2+606-606-1=0+0+0+1 -- 2024-05-01 03:37:46
2095 ubataecj 555 2024-05-01 03:37:46
2087 ubataecj 555 2024-05-01 03:37:42
2084 ubataecj 555 2024-05-01 03:37:39
2083 ubataecj 555 2024-05-01 03:37:39
2082 ubataecj 555 2024-05-01 03:37:39
2081 ubataecj 555 2024-05-01 03:37:39
2075 ubataecj 555 2024-05-01 03:37:37
2072 ubataecj 555 2024-05-01 03:37:33
2069 ubataecj 555 2024-05-01 03:37:31
2066 ubataecj 555 2024-05-01 03:37:28
2063 ubataecj 555 2024-05-01 03:37:25
2060 ubataecj 555 2024-05-01 03:37:22
2058 ubataecj 555 2024-05-01 03:37:19
2055 ubataecj 555 2024-05-01 03:37:15
2052 ubataecj 555 2024-05-01 03:37:12
2049 ubataecj 555 2024-05-01 03:37:10
2048 ubataecj 555 2024-05-01 03:37:09
2047 ubataecj 555 2024-05-01 03:37:09
2046 ubataecj 555 2024-05-01 03:37:09
2045 ubataecj 555 2024-05-01 03:37:09
2044 ubataecj 555 2024-05-01 03:37:08
2036 ubataecj 555 2024-05-01 03:37:01
1938 ubataecj 555 2024-05-01 03:36:05

add comment

Snippet ID: #1036209
Snippet name: G22TradingStrategy
Eternal ID of this version: #1036209/283
Text MD5: 9540869df39e35db32189a07d0ae1685
Transpilation MD5: 3b11026bdf947e8a6f1741e38a182559
Author: stefan
Category: javax / trading
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2024-07-07 06:27:05
Source code size: 35291 bytes / 1123 lines
Pitched / IR pitched: No / No
Views / Downloads: 1150 / 2404
Version history: 282 change(s)
Referenced in: [show references]