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

1102
LINES

< > BotCompany Repo | #1036209 // G22TradingStrategy

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

Transpiled version (30581L) is out of date.

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

download  show line numbers  debug dex  old transpilations   

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

No comments. add comment

Snippet ID: #1036209
Snippet name: G22TradingStrategy
Eternal ID of this version: #1036209/271
Text MD5: fcd57b3e0b9101d7438e547f58618d6a
Author: stefan
Category: javax / trading
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2024-04-11 23:54:31
Source code size: 34719 bytes / 1102 lines
Pitched / IR pitched: No / No
Views / Downloads: 782 / 1913
Version history: 270 change(s)
Referenced in: [show references]