1 | concept CleanMoveStrategy extends G22TradingStrategy { |
2 | // How clean the move must be |
3 | settable double minCleanness = 2; |
4 | |
5 | // How profitable the move must be |
6 | settable double minProfit = 0.5; |
7 | |
8 | // Factor for turning pullback into callback |
9 | settable double callbackFactor = 1.5; |
10 | |
11 | // Max pullback of move |
12 | settable double maxPullback = 1; |
13 | |
14 | // How often to start a new move analysis (seconds) |
15 | settable double newMoveInterval = 30; |
16 | |
17 | Set<LivePullbackToProfitAnalysis> ongoingMoves = syncLinkedHashSet(); |
18 | |
19 | settable long lastMoveStarted; |
20 | |
21 | S fieldsToReset() { |
22 | ret lines(ll(super.fieldsToReset(), [[ongoingMoves lastMoveStarted]])); |
23 | } |
24 | |
25 | persistable class Position extends G22TradingStrategy.Position { |
26 | settable CallbackJuicer juicer; |
27 | } |
28 | |
29 | srecord ProfitBeforeLeverageJuiceable(Position p) is Juiceable { |
30 | public double juiceValue() { ret p.profitBeforeLeverage(); } |
31 | } |
32 | |
33 | void price(double price) { |
34 | if (currentPrice == price) ret; |
35 | currentPrice = price; |
36 | afterwards { |
37 | afterStep(); |
38 | change(); |
39 | } |
40 | |
41 | if (startTime == 0) startTime = currentTime(); |
42 | |
43 | // Close positions |
44 | |
45 | for (p : openPositions()) { |
46 | if (p.juicer != null) { |
47 | var signals = p.juicer.calculateCloseSignals(); |
48 | var strongest = highestBy(signals, s -> s.strength()); |
49 | if (strongest != null && strongest.isTrigger()) |
50 | p.close(strongest); |
51 | } |
52 | } |
53 | |
54 | // Start new move calculator |
55 | var time = currentTime(); |
56 | if (time >= lastMoveStarted+fromSeconds(newMoveInterval)) { |
57 | lastMoveStarted = time; |
58 | for (isShort : ll(false, true)) |
59 | ongoingMoves.add(new LivePullbackToProfitAnalysis().isShort(isShort)); |
60 | } |
61 | |
62 | // Update moves |
63 | for (move : cloneList(ongoingMoves)) { |
64 | move.feed(new PricePoint(time, price)); |
65 | if (move.done()) |
66 | ongoingMoves.remove(move); |
67 | } |
68 | |
69 | // Check if we should start a position |
70 | if (empty(openPositions())) { |
71 | bool shouldShort = false, shouldLong = false; |
72 | PullbackToProfit openReason = null; |
73 | |
74 | for (move : ongoingMoves) |
75 | if (move.cleanness() >= minCleanness && move.profit() >= minProfit) { |
76 | openReason = move.ptp(); |
77 | if (openReason.isShort) |
78 | shouldShort = true; |
79 | else |
80 | shouldLong = true; |
81 | } |
82 | |
83 | if (shouldShort && shouldLong) |
84 | print("Conflicting signal (long+short at once)"); |
85 | else if (shouldShort || shouldLong) { |
86 | int direction = shouldShort ? -1 : 1; |
87 | new Position p; |
88 | p.marginToUse = marginPerPosition; |
89 | var callbackRate = openReason.pullback*callbackFactor; |
90 | p.juicer = new CallbackJuicer(callbackRate); |
91 | p.juicer.juiceable(new ProfitBeforeLeverageJuiceable(p)); |
92 | openPosition(p, direction, openReason); |
93 | } |
94 | } |
95 | } |
96 | |
97 | L<? extends Position> openPositions() { ret (L) super.openPositions(); } |
98 | |
99 | LS status() { |
100 | ret listCombine( |
101 | "Ongoing moves: " + n2(ongoingMoves), |
102 | super.status() |
103 | ); |
104 | } |
105 | } |
Began life as a copy of #1036518
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): elmgxqgtpvxh, mqqgnosmbjvj, wnsclhtenguj
No comments. add comment
Snippet ID: | #1036520 |
Snippet name: | CleanMoveStrategy (backup using forward moves) |
Eternal ID of this version: | #1036520/4 |
Text MD5: | b822e49297abec6a625db95813539457 |
Author: | stefan |
Category: | javax / trading |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2023-01-22 13:40:35 |
Source code size: | 3157 bytes / 105 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 165 / 160 |
Version history: | 3 change(s) |
Referenced in: | [show references] |