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

282
LINES

< > BotCompany Repo | #1036185 // MPM3 - backup before extensions

JavaX fragment (include)

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  
    real openingPrice() { ret ticker.priceAtTimestamp(openingTime); }
42  
    
43  
    real profitAtTime(real time) {
44  
      ret (ticker.priceAtTimestamp(time)/openingPrice()*100-100)*direction - marketAdversity;
45  
    }
46  
    
47  
    real openingTime() { ret openingTime; }
48  
    
49  
    S directionString() { ret direction > 0 ? "long" : direction  < 0 ? "short" : ""; }
50  
    
51  
    void restoreTicker(TickerSequence ticker) { this.ticker = ticker; }
52  
  }
53  
  
54  
  macro forwardToPosition {
55  
    TickerSequence ticker() { ret p.ticker; }
56  
    void restoreTicker(TickerSequence ticker) { p.restoreTicker(ticker); }
57  
    real direction() { ret p.direction; }
58  
    S directionString() { ret p.directionString(); }
59  
    real openingTime() { ret p.openingTime; }
60  
    real openingPrice() { ret p.openingPrice(); }
61  
  }
62  
  
63  
  persistable record LivePosition(Position p) {
64  
    settable Juicer juicer;
65  
    
66  
    // highest profit this position has reached
67  
    settable real crest = -infinity();
68  
    
69  
    settable real time;   // time we last updated this position
70  
    settable real profit; // expected profit at that time
71  
    
72  
    forwardToPosition
73  
    
74  
    // return true if any change
75  
    bool update(real time) {
76  
      if (time <= this.time) false;
77  
      this.time = time;
78  
      profit = p.profitAtTime(time);
79  
      crest = max(crest, profit);
80  
      true;
81  
    }
82  
    
83  
    transient simplyCached real maxClosingTime() {
84  
      ret p.ticker.openEnded() ? infinity()
85  
        : min(p.openingTime+maxPositionDuration, p.ticker.endTime());
86  
    }
87  
    
88  
    bool shouldClose() {
89  
      ret time >= maxClosingTime()
90  
        || profit < (profit < 0 ? -juicer.lossTolerance : crest-juicer.pullback);
91  
    }
92  
    
93  
    // >= 1 to close
94  
    // otherwise keep open
95  
    real closeSignal() {
96  
      CloseSignal strongestSignal = closeSignalWithDescription();
97  
      ret strongestSignal == null ? 0 : strongestSignal.trigger;
98  
    }
99  
    
100  
    CloseSignal closeSignalWithDescription() {
101  
      ret highestBy(closeSignals(), signal -> signal.trigger);
102  
    }
103  
    
104  
    // trigger = 0 to 1
105  
    record noeq CloseSignal(S reason, real trigger) {}
106  
107  
    L<CloseSignal> closeSignals() {
108  
      new L<CloseSignal> signals;
109  
      
110  
      // How close are we to the max closing time?
111  
      if (maxClosingTime() < infinity())
112  
        signals.add(new CloseSignal("Age", transformToZeroToOne(time, p.openingTime(), maxClosingTime())));
113  
        
114  
      // How close are we to our loss limit?
115  
      if (profit < 0)
116  
        signals.add(new CloseSignal("Loss", doubleRatio(-profit, juicer.lossTolerance));
117  
      else
118  
        // How close are we to the pullback limit?
119  
        signals.add(new CloseSignal("Profit", transformToZeroToOne(profit,
120  
          crest, crest-juicer.pullback));
121  
          
122  
      ret signals;
123  
    }
124  
  }
125  
  
126  
  record ClosedPosition(Position p, real closingTime) {
127  
    real profit = Double.NaN;
128  
    
129  
    MPM3 mpm() { ret MPM3.this; }
130  
131  
    forwardToPosition
132  
    real closingTime() { ret closingTime; }
133  
    real closingPrice() { ret ticker().priceAtTimestamp(closingTime); }
134  
    
135  
    real duration() { ret closingTime()-openingTime(); }
136  
    
137  
    real profit() {
138  
      if (isNaN(profit))
139  
        profit = p.profitAtTime(closingTime);
140  
      ret profit;
141  
    }
142  
  }
143  
  
144  
  // Wrappers for L<LivePosition> and L<ClosedPosition>
145  
  // (maybe not needed?)
146  
  
147  
  sclass LivePositions {
148  
    L<LivePosition> positions = syncL();
149  
  }
150  
  
151  
  sclass ClosedPositions {
152  
    L<ClosedPosition> positions = syncL();
153  
  }
154  
  
155  
  // BOT (Eye + Juicer)
156  
  
157  
  persistable srecord Juicer(real lossTolerance, real pullback) {}
158  
159  
  abstract sclass Eye {
160  
    // returns -1 to open short, 1 to open long
161  
    // and anything in between to do nothing
162  
    abstract real adviseDirection(TickerPoint tickerPoint);
163  
  }
164  
  
165  
  // time = ms to look back
166  
  srecord SimpleEye(long lookbackTime, real minMove) extends Eye {
167  
    real adviseDirection(TickerPoint tickerPoint) {
168  
      real move = calculateMove(tickerPoint);
169  
      if (isNaN(move)) ret 0;
170  
      real relativeMove = doubleRatio(move, minMove);
171  
      ret clamp(relativeMove, -1, 1);
172  
    }
173  
    
174  
    real calculateMove(TickerPoint tickerPoint) {
175  
      if (!tickerPoint.canLookback(lookbackTime)) ret Double.NaN;
176  
      real currentPrice = tickerPoint.currentPrice();
177  
      real before = tickerPoint.lookback(lookbackTime);
178  
      ret currentPrice/before*100-100;
179  
    }
180  
  }
181  
  
182  
  srecord TradingBot(Eye eye, Juicer juicer) {
183  
    *(real lookbackMinutes, real minMove, real maxLoss, real pullback) {
184  
      eye = new SimpleEye(minutesToMS(lookbackMinutes), minMove);
185  
      juicer = new Juicer(maxLoss, pullback);
186  
    }
187  
  }
188  
  
189  
  class BotInAction extends MetaWithChangeListeners {
190  
    settable TradingBot botConfiguration;
191  
    transient TickerSequence ticker;
192  
    
193  
    // Both open and closed positions may be purely simulated or
194  
    // mirrors of positions actually made IRL (see Position.irlInfo).
195  
    
196  
    new ListWithChangeListeners<LivePosition> openPositions;
197  
    new ListWithChangeListeners<ClosedPosition> closedPositions;
198  
    
199  
    LivePosition openPosition(TickerPoint tickerPoint, real direction) {
200  
      var p = new Position(tickerPoint.ticker(), tickerPoint.time(), direction);
201  
      var livePosition = new LivePosition(p).juicer(botConfiguration.juicer);
202  
      livePosition.update(tickerPoint.time());
203  
      openPositions.add(livePosition);
204  
      change();
205  
      ret livePosition;
206  
    }
207  
    
208  
    void closePosition(LivePosition position) {
209  
      if (openPositions.remove(position)) {
210  
        // TODO: closedPositions.add...
211  
        change();
212  
      }
213  
    }
214  
    
215  
    void restoreTicker(TickerSequence ticker) {
216  
      if (ticker == this.ticker) ret;
217  
      this.ticker = ticker;
218  
      ticker.onPricePointAdded(time -> updatePositions(time));
219  
      for (p : openPositions) p.restoreTicker(ticker);
220  
      for (p : closedPositions) p.restoreTicker(ticker);
221  
    }
222  
    
223  
    void updatePositions(real time) {
224  
      bool anyChange;
225  
      for (p : openPositions) anyChange |= p.update(time);
226  
      if (anyChange) change();
227  
    }
228  
  }
229  
230  
  ClosedPosition runJuicer(Position p, Juicer j) {
231  
    TickerSequence ticker = p.ticker;
232  
    
233  
    long time = lround(p.openingTime);
234  
    double maxClosingTime = min(time+maxPositionDuration, ticker.endTime());
235  
    real crest = -infinity();
236  
    
237  
    while (time < maxClosingTime) {
238  
      time = ticker.nextTimestamp(time);
239  
      real profit = p.profitAtTime(time);
240  
      crest = max(crest, profit);
241  
      if (profit < (profit < 0 ? -j.lossTolerance : crest-j.pullback))
242  
        break;
243  
    }
244  
    
245  
    ret new ClosedPosition(p, time);
246  
  }
247  
248  
  record noeq Backtest(TickerSequence ticker, TradingBot bot) extends Convergent {
249  
    new L<ClosedPosition> closedPositions;
250  
  
251  
    long time;
252  
    
253  
    double latestAllowedOpeningTime() {
254  
      ret ticker.endTime()-maxPositionDuration;
255  
    }
256  
    
257  
    void step {
258  
      if (time == 0) time = ticker.startTime();
259  
      if (time > latestAllowedOpeningTime())
260  
        ret with done = true;
261  
      
262  
      TickerPoint tickerPoint = new(ticker, time);
263  
      real direction = bot.eye.adviseDirection(tickerPoint);
264  
      if (abs(direction) < 1)
265  
        // No position to open, move to next timestamp
266  
        time = ticker.nextTimestamp(time);
267  
      else {
268  
        // We have a position to open
269  
        var position = new Position(ticker, time, direction);
270  
        
271  
        // Ask juicer when to close position
272  
        var closedPosition = runJuicer(position, bot.juicer);
273  
        
274  
        // Add to record of positions made
275  
        closedPositions.add(closedPosition);
276  
        
277  
        // Move on to next timestamp
278  
        time = ticker.nextTimestamp(closedPosition.closingTime);
279  
      }
280  
    }
281  
  }
282  
}

Author comment

Began life as a copy of #1036164

download  show line numbers  debug dex  old transpilations   

Travelled to 1 computer(s): mqqgnosmbjvj

No comments. add comment

Snippet ID: #1036185
Snippet name: MPM3 - backup before extensions
Eternal ID of this version: #1036185/1
Text MD5: 5aaf46284f2bf0769f160f52001b4b95
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-10 15:30:12
Source code size: 9101 bytes / 282 lines
Pitched / IR pitched: No / No
Views / Downloads: 68 / 71
Referenced in: [show references]