-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathDemoGeneratorReplayProvider.java
More file actions
285 lines (245 loc) · 11.7 KB
/
DemoGeneratorReplayProvider.java
File metadata and controls
285 lines (245 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package velox.api.layer0.replay;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import velox.api.layer0.annotations.Layer0ReplayModule;
import velox.api.layer0.data.FileEndReachedUserMessage;
import velox.api.layer0.data.FileNotSupportedUserMessage;
import velox.api.layer0.data.IndicatorDefinitionUserMessage;
import velox.api.layer0.data.IndicatorPointUserMessage;
import velox.api.layer0.data.OrderQueuePositionUserMessage;
import velox.api.layer0.data.ReadFileLoginData;
import velox.api.layer0.live.DemoExternalRealtimeTradingProvider;
import velox.api.layer1.annotations.Layer1ApiVersion;
import velox.api.layer1.annotations.Layer1ApiVersionValue;
import velox.api.layer1.data.ExecutionInfo;
import velox.api.layer1.data.InstrumentInfo;
import velox.api.layer1.data.LoginData;
import velox.api.layer1.data.OrderDuration;
import velox.api.layer1.data.OrderInfoBuilder;
import velox.api.layer1.data.OrderStatus;
import velox.api.layer1.data.OrderType;
import velox.api.layer1.data.TradeInfo;
/**
* <p>
* Instead of actually reading the file generates data, so you can select any
* file with this one loaded.
* </p>
* <p>
* Illustrates how to manipulate order queue position and display legacy API
* indicators.
* </p>
* <p>
* This should simplify transition for those who used "Recorder API". API used
* in this example will be removed in the future in favor of L2 API based
* solution.
* </p>
* <p>
* It's newer version of BookmapRecorderDemo. Main differences are:
* <ul>
* <li>Depth update and trade update prices are divided by pips now</li>
* <li>Indicator updates and order updates are now sent differently.</li>
* </ul>
* </p>
* <p>
* For more details on working with orders see
* {@link DemoExternalRealtimeTradingProvider}
* </p>
*/
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
@Layer0ReplayModule
public class DemoGeneratorReplayProvider extends ExternalReaderBaseProvider {
/**
* Some point in time, just for convenience (nanoseconds)
*/
private static final long INITIAL_TIME = 1400000000_000000000L;
/**
* Number of nanoseconds in one second
*/
private static final long NS_IN_SEC = 1_000_000_000L;
private Thread readerThread;
private long currentTime = 0;
@Override
public void login(LoginData loginData) {
// We are not going to really read the file - just launch the reader thread.
ReadFileLoginData fileData = (ReadFileLoginData) loginData;
try {
// For demo purposes let's just check the extension.
// Usually you will want to take a look at file content here to
// ensure it's expected file format
if (!fileData.file.getName().endsWith(".simpleformat2.txt")) {
throw new IOException("File extension not supported");
} else {
readerThread = new Thread(this::read);
readerThread.start();
currentTime = INITIAL_TIME;
}
} catch (@SuppressWarnings("unused") IOException e) {
adminListeners.forEach(listener -> listener.onUserMessage(new FileNotSupportedUserMessage()));
}
}
/**
*
*/
private void read() {
// instrument1 defined 1 second after feed is started
currentTime += 1 * NS_IN_SEC;
InstrumentInfo instrument1 = new InstrumentInfo("Test instrument", null, null, 25, 1, "Test instrument - full name", false);
instrumentListeners.forEach(l -> l.onInstrumentAdded("Test instrument", instrument1));
// And the second instrument is defined at the same point in time
InstrumentInfo instrument2 = new InstrumentInfo("Test instrument 2", null, null, 10, 1, "Test instrument 2 - full name", false);
instrumentListeners.forEach(l -> l.onInstrumentAdded("Test instrument 2", instrument2));
currentTime += NS_IN_SEC;
// Let's generate 10 bid + 10 ask levels for 1'st instrument, and 5+5 for second.
for (int i = 1; i <= 10; ++i) {
// Defining final version of i to allow using it inside lambda
final int q = i;
final int sizeBid = i * 22;
dataListeners.forEach(l -> l.onDepth("Test instrument", true, 40 - q, sizeBid));
final int sizeAsk = i * 15;
dataListeners.forEach(l -> l.onDepth("Test instrument", false, 40 + q, sizeAsk));
}
for (int i = 1; i <= 5; ++i) {
// Defining final version of i to allow using it inside lambda
final int q = i;
final int sizeBid = i * 2;
dataListeners.forEach(l -> l.onDepth("Test instrument 2", true, 500 - q, sizeBid));
final int sizeAsk = i * 1;
dataListeners.forEach(l -> l.onDepth("Test instrument 2", false, 500 + q, sizeAsk));
}
// Advance time 1 sec forward.
currentTime += NS_IN_SEC;
// Now let's start changing the data (for both instruments)
for (int i = 0; i <= 50; ++i) {
// Defining final version of i to allow using it inside lambda
final int q = i;
// Remove old level
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument", false, 40 + (q + 1), 0));
// Add new level
final int sizeBid1 = q * 5 + 100;
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument", false, 40 + (q + 1 + 10), sizeBid1));
// Remove old level
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument", true, 40 + (q - 1 - 10), 0));
// Add new level
final int sizeAsk1 = q * 10 + 100;
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument", true, 40 + (q - 1), sizeAsk1));
// Remove old level
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument 2", false, 500 + (-q + 1 + 5), 0));
// Add new level
final int sizeBid2 = q * 5 + 100;
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument 2", false, 500 + (-q + 1), sizeBid2));
// Remove old level
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument 2", true, 500 + (-q - 1), 0));
// Add new level
final int sizeAsk2 = q * 10 + 100;
currentTime += NS_IN_SEC / 20;
dataListeners.forEach(l -> l.onDepth("Test instrument 2", true, 500 + (-q - 1 - 5), sizeAsk2));
}
BufferedImage icon;
try {
icon = ImageIO.read(DemoGeneratorReplayProvider.class.getResourceAsStream("/icon_accept.gif"));
} catch (IOException e) {
throw new RuntimeException("failed to load icon", e);
}
// Line and icons
currentTime += NS_IN_SEC / 10;
IndicatorDefinitionUserMessage indicatorDefinitionMessage = new IndicatorDefinitionUserMessage(
1, "Test instrument 2", "Indicator 1",
(short)0xFFFF, (short)1, 1, Color.ORANGE,
(short)0xFF08, (short)1, 2,
icon, -icon.getWidth() / 2, -icon.getHeight() / 2, true);
// No line, only icons
// IndicatorDefinitionUserMessage indicatorDefinitionMessage = new IndicatorDefinitionUserMessage(
// 1, "Test instrument 2",
// (short)0x0000, (short)1, 1, Color.ORANGE,
// (short)0x0000, (short)1, 2, Color.GREEN,
// icon, -icon.getWidth() / 2, -icon.getHeight() / 2);
// No icon, different line style
// IndicatorDefinitionUserMessage indicatorDefinitionMessage = new IndicatorDefinitionUserMessage(
// 1, "Test instrument 2",
// (short)0x5555, (short)20, 5, Color.ORANGE,
// (short)0x5555, (short)40, 10, Color.GREEN,
// null, 0, 0);
adminListeners.forEach(l -> l.onUserMessage(indicatorDefinitionMessage));
currentTime += NS_IN_SEC / 10;
adminListeners.forEach(l -> l.onUserMessage(new IndicatorPointUserMessage(1, 4440.0)));
currentTime += NS_IN_SEC;
adminListeners.forEach(l -> l.onUserMessage(new IndicatorPointUserMessage(1, 4450.0)));
currentTime += NS_IN_SEC;
adminListeners.forEach(l -> l.onUserMessage(new IndicatorPointUserMessage(1, Double.NaN)));
currentTime += NS_IN_SEC;
adminListeners.forEach(l -> l.onUserMessage(new IndicatorPointUserMessage(1, 4450.0)));
// Let's create a trade for the 2'nd instrument. We won't update depth data for simplicity.
// Price is 4500.0 (pips is 10), size is 150, agressor is bid (last parameter of TradeInfo).
dataListeners.forEach(l -> l.onTrade("Test instrument 2", 4500.0 / 10, 150, new TradeInfo(false, true)));
// Let's create an order
currentTime += NS_IN_SEC;
OrderInfoBuilder order = new OrderInfoBuilder("Test instrument 2", "order1", false, OrderType.LMT, "client-id-1", false);
order
.setLimitPrice(4580)
.setUnfilled(5)
.setDuration(OrderDuration.GTC)
.setStatus(OrderStatus.PENDING_SUBMIT);
tradingListeners.forEach(l -> l.onOrderUpdated(order.build()));
order.markAllUnchanged();
order.setStatus(OrderStatus.WORKING);
tradingListeners.forEach(l -> l.onOrderUpdated(order.build()));
order.markAllUnchanged();
// Let's record order position data. If you comment this out BookMap will compute position using built-in algorithms
for (int position = 310 /* 315 is the size on the order's price level, order size is 5, so initially there are 310 shares before our order*/;
position > 100; --position) {
// Decreasing order position - will look like it advances to the head of the queue
currentTime += NS_IN_SEC / 30;
final OrderQueuePositionUserMessage positionMessage = new OrderQueuePositionUserMessage("order1", position);
adminListeners.forEach(l -> l.onUserMessage(positionMessage));
}
// Let's decrease the price
currentTime += NS_IN_SEC;
order.setLimitPrice(4480);
tradingListeners.forEach(l -> l.onOrderUpdated(order.build()));
order.markAllUnchanged();
// Let's execute the order
currentTime += NS_IN_SEC;
ExecutionInfo executionInfo = new ExecutionInfo(
order.getOrderId(),
5,
4480,
"execution-id-1",
// Execution time in milliseconds. Used only in account information.
currentTime / 1000_000);
tradingListeners.forEach(l -> l.onOrderExecuted(executionInfo));
// And mark it as filled
order.setFilled(5);
order.setUnfilled(0);
order.setStatus(OrderStatus.FILLED);
tradingListeners.forEach(l -> l.onOrderUpdated(order.build()));
order.markAllUnchanged();
// Report file end
reportFileEnd();
}
public void reportFileEnd() {
adminListeners.forEach(listener -> listener.onUserMessage(new FileEndReachedUserMessage()));
}
@Override
public long getCurrentTime() {
return currentTime;
}
@Override
public String getSource() {
// String identifying where data came from.
// For example you can use that later in your indicator.
return "generated example data";
}
@Override
public void close() {
readerThread.interrupt();
}
}