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

342
LINES

< > BotCompany Repo | #1036164 // MPM3 - Trading Bot v3

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

Transpiled version (13269L) is out of date.

1  
set flag Reparse.
2  
replace real with double.
3  
replace Real with Double.
4  
replace price with float.
5  
6  
// Persistence:
7  
// MPM3.BotInAction is persistable which in turn means
8  
// MPM3, Position, Juicer and other classes are also persistable.
9  
// We don't persist Position.ticker for space reasons though.
10  
11  
persistable sclass MPM3 {
12  
  // MARKET (trading platform)
13  
  
14  
  // How much percentual slippage we have for each position on
15  
  // average plus the fees for the position.
16  
  // Obviously dependent on the market (trading site) we are using.
17  
  settable real marketAdversity = 0.12 + 0.08;
18  
  
19  
  // SELF-IMPOSED LIMITS
20  
  
21  
  // How long a position can be held at the longest
22  
  // - only for backtesting!
23  
  gettable real maxPositionDuration = minutesToMS(60);
24  
  
25  
  real minimalMaxDuration() { ret minutesToMS(1); }
26  
  
27  
  // correct user if they are stupid
28  
  selfType maxPositionDuration(real whatUserWants) {
29  
    maxPositionDuration = max(whatUserWants, minimalMaxDuration());
30  
    this;
31  
  }
32  
  
33  
  // POSITIONS
34  
35  
  // An open(ed) position  
36  
  persistable record Position(transient TickerSequence ticker, real openingTime, real direction) {
37  
  
38  
    // Set this to a non-null value if this is a real position on a real market
39  
    settable O irlInfo;
40  
    
41  
    // Eye that opened this position
42  
    settable Eye eye;
43  
    
44  
    real openingPrice() { ret ticker.priceAtTimestamp(openingTime); }
45  
    
46  
    real profitAtTime(real time) {
47  
      ret (ticker.priceAtTimestamp(time)/openingPrice()*100-100)*direction - marketAdversity;
48  
    }
49  
    
50  
    real openingTime() { ret openingTime; }
51  
    
52  
    S directionString() { ret direction > 0 ? "long" : direction  < 0 ? "short" : ""; }
53  
    
54  
    void restoreTicker(TickerSequence ticker) { this.ticker = ticker; }
55  
    
56  
    TickerPoint openingTickerPoint() { ret new TickerPoint(ticker, lround(openingTime)); }
57  
  }
58  
  
59  
  macro forwardToPosition {
60  
    TickerSequence ticker() { ret p.ticker; }
61  
    void restoreTicker(TickerSequence ticker) { p.restoreTicker(ticker); }
62  
    real direction() { ret p.direction; }
63  
    S directionString() { ret p.directionString(); }
64  
    real openingTime() { ret p.openingTime; }
65  
    real openingPrice() { ret p.openingPrice(); }
66  
    Eye eye() { ret p.eye(); }
67  
  }
68  
  
69  
  persistable record LivePosition(Position p) {
70  
    settable Juicer juicer;
71  
    
72  
    // highest profit this position has reached
73  
    settable real crest = -infinity();
74  
    
75  
    settable real time;   // time we last updated this position
76  
    settable real profit; // expected profit at that time
77  
    
78  
    forwardToPosition
79  
    
80  
    // return true if any change
81  
    bool update(real time) {
82  
      if (time <= this.time) false;
83  
      this.time = time;
84  
      profit = p.profitAtTime(time);
85  
      crest = max(crest, profit);
86  
      true;
87  
    }
88  
    
89  
    transient simplyCached real maxClosingTime() {
90  
      ret p.ticker.live() ? infinity()
91  
        : min(p.openingTime+maxPositionDuration, p.ticker.endTime());
92  
    }
93  
    
94  
    bool shouldClose() {
95  
      ret time >= maxClosingTime()
96  
        || profit < (profit < 0 ? -juicer.lossTolerance : crest-juicer.pullback);
97  
    }
98  
    
99  
    // >= 1 to close
100  
    // otherwise keep open
101  
    real closeSignal() {
102  
      CloseSignal strongestSignal = closeSignalWithDescription();
103  
      ret strongestSignal == null ? 0 : strongestSignal.trigger;
104  
    }
105  
    
106  
    CloseSignal closeSignalWithDescription() {
107  
      ret highestBy(closeSignals(), signal -> signal.trigger);
108  
    }
109  
    
110  
    // trigger = 0 to 1
111  
    record noeq CloseSignal(S reason, real trigger) {}
112  
113  
    L<CloseSignal> closeSignals() {
114  
      new L<CloseSignal> signals;
115  
      
116  
      // How close are we to the max closing time?
117  
      if (maxClosingTime() < infinity())
118  
        signals.add(new CloseSignal("Age", transformToZeroToOne(time, p.openingTime(), maxClosingTime())));
119  
        
120  
      // How close are we to our loss limit?
121  
      if (profit < 0)
122  
        signals.add(new CloseSignal("Loss", doubleRatio(-profit, juicer.lossTolerance));
123  
      else
124  
        // How close are we to the pullback limit?
125  
        signals.add(new CloseSignal("Profit", transformToZeroToOne(profit,
126  
          crest, crest-juicer.pullback));
127  
          
128  
      ret signals;
129  
    }
130  
  }
131  
  
132  
  record ClosedPosition(Position p, real closingTime) {
133  
    real profit = Double.NaN;
134  
    
135  
    MPM3 mpm() { ret MPM3.this; }
136  
137  
    forwardToPosition
138  
    real closingTime() { ret closingTime; }
139  
    real closingPrice() { ret ticker().priceAtTimestamp(closingTime); }
140  
    
141  
    real duration() { ret closingTime()-openingTime(); }
142  
    
143  
    real profit() {
144  
      if (isNaN(profit))
145  
        profit = p.profitAtTime(closingTime);
146  
      ret profit;
147  
    }
148  
  }
149  
  
150  
  // Wrappers for L<LivePosition> and L<ClosedPosition>
151  
  // (maybe not needed?)
152  
  
153  
  sclass LivePositions {
154  
    L<LivePosition> positions = syncL();
155  
  }
156  
  
157  
  sclass ClosedPositions {
158  
    L<ClosedPosition> positions = syncL();
159  
  }
160  
  
161  
  // BOT (Eye + Juicer)
162  
  
163  
  persistable srecord Juicer(real lossTolerance, real pullback) {}
164  
165  
  abstract sclass Eye {
166  
    // returns -1 to open short, 1 to open long
167  
    // and anything in between to do nothing
168  
    abstract real adviseDirection(TickerPoint tickerPoint);
169  
  }
170  
  
171  
  // time = ms to look back
172  
  srecord SimpleEye(long lookbackTime, real minMove) extends Eye {
173  
    // 0 to 1
174  
    settable real relativeVerificationTime/* = 0.33*/;
175  
    
176  
    // 0 to 1 (roughly)
177  
    settable real verificationThreshold/* = 0.5*/;
178  
    
179  
    // in ms
180  
    real verificationTime() {
181  
      ret lookbackTime*relativeVerificationTime;
182  
    }
183  
    
184  
    // in price percent
185  
    real verificationMoveThreshold() {
186  
      ret minMove*relativeVerificationTime*verificationThreshold;
187  
    }
188  
    
189  
    // in price percent
190  
    real verificationMove(TickerPoint tickerPoint) {
191  
      ret calculateMove(tickerPoint, verificationTime());
192  
    }
193  
    
194  
    // in price percent, scaled up to lookbackTime
195  
    real relativeVerificationMove(TickerPoint tickerPoint) {
196  
      ret doubleRatio(verificationMove(tickerPoint)*lookbackTime, verificationTime());
197  
    }
198  
    
199  
    real verificationSignal(TickerPoint tickerPoint) {
200  
      ret doubleRatio(verificationMove(tickerPoint), verificationMoveThreshold());
201  
    }
202  
    
203  
    bool verificationEnabled() {
204  
      ret relativeVerificationTime != 0;
205  
    }
206  
    
207  
    real adviseDirection(TickerPoint tickerPoint) {
208  
      real move = calculateMove(tickerPoint);
209  
      if (isNaN(move)) ret 0;
210  
      real relativeMove = doubleRatio(move, minMove);
211  
      int sign = sign(relativeMove);
212  
      real absRelativeMove = abs(relativeMove);
213  
      
214  
      if (verificationEnabled()) {
215  
        real verification = max(0, sign*verificationSignal(tickerPoint));
216  
        absRelativeMove = min(absRelativeMove, verification);
217  
      }
218  
      
219  
      // No more clamping - allow trade signals > 100%
220  
      ret sign*absRelativeMove;
221  
    }
222  
    
223  
    real calculateMove(TickerPoint tickerPoint, real lookbackTime default this.lookbackTime) {
224  
      if (!tickerPoint.canLookback(lround(lookbackTime))) ret Double.NaN;
225  
      real currentPrice = tickerPoint.currentPrice();
226  
      real before = tickerPoint.lookback(lround(lookbackTime));
227  
      ret currentPrice/before*100-100;
228  
    }
229  
  }
230  
  
231  
  /*persistable srecord TwoEyes extends Eye {
232  
    settable Eye eye1;
233  
    settable Eye eye2;
234  
    
235  
    *(Eye *eye1, Eye *eye2) {}
236  
    
237  
    real adviseDirection(TickerPoint tickerPoint) {
238  
    }
239  
  }*/
240  
  
241  
  srecord TradingBot(Eye eye, Juicer juicer) {
242  
    *(real lookbackMinutes, real minMove, real maxLoss, real pullback) {
243  
      eye = new SimpleEye(minutesToMS(lookbackMinutes), minMove);
244  
      juicer = new Juicer(maxLoss, pullback);
245  
    }
246  
  }
247  
  
248  
  class BotInAction extends MetaWithChangeListeners {
249  
    settable TradingBot botConfiguration;
250  
    transient TickerSequence ticker;
251  
    
252  
    // Both open and closed positions may be purely simulated or
253  
    // mirrors of positions actually made IRL (see Position.irlInfo).
254  
    
255  
    new ListWithChangeListeners<LivePosition> openPositions;
256  
    new ListWithChangeListeners<ClosedPosition> closedPositions;
257  
    
258  
    LivePosition openPosition(TickerPoint tickerPoint, real direction) {
259  
      var p = new Position(tickerPoint.ticker(), tickerPoint.time(), direction);
260  
      var livePosition = new LivePosition(p).juicer(botConfiguration.juicer);
261  
      livePosition.update(tickerPoint.time());
262  
      openPositions.add(livePosition);
263  
      change();
264  
      ret livePosition;
265  
    }
266  
    
267  
    void closePosition(LivePosition position) {
268  
      if (openPositions.remove(position)) {
269  
        // TODO: closedPositions.add...
270  
        change();
271  
      }
272  
    }
273  
    
274  
    void restoreTicker(TickerSequence ticker) {
275  
      if (ticker == this.ticker) ret;
276  
      this.ticker = ticker;
277  
      ticker.onPricePointAdded(time -> updatePositions(time));
278  
      for (p : openPositions) p.restoreTicker(ticker);
279  
      for (p : closedPositions) p.restoreTicker(ticker);
280  
    }
281  
    
282  
    void updatePositions(real time) {
283  
      bool anyChange;
284  
      for (p : openPositions) anyChange |= p.update(time);
285  
      if (anyChange) change();
286  
    }
287  
  }
288  
289  
  ClosedPosition runJuicer(Position p, Juicer j) {
290  
    TickerSequence ticker = p.ticker;
291  
    
292  
    long time = lround(p.openingTime);
293  
    double maxClosingTime = min(time+maxPositionDuration, ticker.endTime());
294  
    real crest = -infinity();
295  
    
296  
    while (time < maxClosingTime) {
297  
      time = ticker.nextTimestamp(time);
298  
      real profit = p.profitAtTime(time);
299  
      crest = max(crest, profit);
300  
      if (profit < (profit < 0 ? -j.lossTolerance : crest-j.pullback))
301  
        break;
302  
    }
303  
    
304  
    ret new ClosedPosition(p, time);
305  
  }
306  
307  
  record noeq Backtest(TickerSequence ticker, TradingBot bot) extends Convergent {
308  
    new L<ClosedPosition> closedPositions;
309  
  
310  
    long time;
311  
    
312  
    double latestAllowedOpeningTime() {
313  
      ret ticker.endTime()-maxPositionDuration;
314  
    }
315  
    
316  
    void step {
317  
      if (time == 0) time = ticker.startTime();
318  
      if (time > latestAllowedOpeningTime())
319  
        ret with done = true;
320  
      
321  
      TickerPoint tickerPoint = new(ticker, time);
322  
      real direction = bot.eye.adviseDirection(tickerPoint);
323  
      if (abs(direction) < 1)
324  
        // No position to open, move to next timestamp
325  
        time = ticker.nextTimestamp(time);
326  
      else {
327  
        // We have a position to open
328  
        var position = new Position(ticker, time, direction)
329  
          .eye(bot.eye);
330  
        
331  
        // Ask juicer when to close position
332  
        var closedPosition = runJuicer(position, bot.juicer);
333  
        
334  
        // Add to record of positions made
335  
        closedPositions.add(closedPosition);
336  
        
337  
        // Move on to next timestamp
338  
        time = ticker.nextTimestamp(closedPosition.closingTime);
339  
      }
340  
    }
341  
  }
342  
}

Author comment

Began life as a copy of #1036148

download  show line numbers  debug dex  old transpilations   

Travelled to 3 computer(s): elmgxqgtpvxh, mqqgnosmbjvj, wnsclhtenguj

No comments. add comment

Snippet ID: #1036164
Snippet name: MPM3 - Trading Bot v3
Eternal ID of this version: #1036164/95
Text MD5: 60307496b119b8815b3b635d9ce80fc8
Author: stefan
Category: javax / trading bot
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-10-30 18:05:14
Source code size: 10946 bytes / 342 lines
Pitched / IR pitched: No / No
Views / Downloads: 281 / 703
Version history: 94 change(s)
Referenced in: [show references]