@vuu-ui/vuu-data-test 4.0.0-alpha.3 → 4.0.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/package.json +10 -9
- package/src/TickingArrayDataSource.js +7 -2
- package/src/basket/BasketModule.js +105 -40
- package/src/basket/basket-schemas.js +16 -0
- package/src/basket/reference-data/constituent-data.js +2 -0
- package/src/basket/reference-data/constituents.js +6 -74
- package/src/basket/reference-data/prices.js +9 -3
- package/src/core/filter/PermissionFilter.js +60 -0
- package/src/core/filter/PermissionFilteredTable.js +59 -0
- package/src/core/module/VuuModule.js +30 -4
- package/src/core/table/TableContainer.js +51 -8
- package/src/core/user/CurrentUser.js +13 -0
- package/src/index.js +4 -0
- package/src/local-datasource-provider/LocalDatasourceProvider.js +64 -44
- package/src/module-admin/ModuleAdminModule.js +84 -20
- package/src/module-admin/module-admin-schemas.js +5 -9
- package/src/module-admin/snapshot-projection.js +3 -31
- package/src/notifications/NotificationModule.js +131 -0
- package/src/notifications/NotificationsSchema.js +52 -0
- package/src/notifications/index.js +6 -0
- package/src/notifications/simulated/DismissNotificationRpcHandler.js +36 -0
- package/src/notifications/simulated/SimulatedNotificationsModule.js +18 -0
- package/src/notifications/simulated/SimulatedNotificationsProvider.js +151 -0
- package/src/notifications/simulated/notificationsModule.js +3 -0
- package/src/user-admin/UserAdminModule.js +163 -8
- package/src/user-admin/initialSnapshot.js +49 -65
- package/types/TickingArrayDataSource.d.ts +1 -1
- package/types/basket/BasketModule.d.ts +2 -0
- package/types/basket/reference-data/constituents.d.ts +3 -3
- package/types/basket/reference-data/prices.d.ts +2 -1
- package/types/core/filter/PermissionFilter.d.ts +41 -0
- package/types/core/filter/PermissionFilteredTable.d.ts +24 -0
- package/types/core/module/VuuModule.d.ts +19 -0
- package/types/core/table/TableContainer.d.ts +1 -1
- package/types/core/user/CurrentUser.d.ts +14 -0
- package/types/index.d.ts +4 -0
- package/types/local-datasource-provider/LocalDatasourceProvider.d.ts +18 -2
- package/types/module-admin/ModuleAdminModule.d.ts +12 -3
- package/types/module-admin/index.d.ts +1 -1
- package/types/module-admin/snapshot-projection.d.ts +3 -4
- package/types/notifications/NotificationModule.d.ts +60 -0
- package/types/notifications/NotificationsSchema.d.ts +20 -0
- package/types/notifications/index.d.ts +6 -0
- package/types/notifications/simulated/DismissNotificationRpcHandler.d.ts +8 -0
- package/types/notifications/simulated/SimulatedNotificationsModule.d.ts +9 -0
- package/types/notifications/simulated/SimulatedNotificationsProvider.d.ts +21 -0
- package/types/notifications/simulated/notificationsModule.d.ts +5 -0
- package/types/user-admin/UserAdminModule.d.ts +5 -1
- package/src/basket/reference-data/ftse100.js +0 -686
- package/src/basket/reference-data/hsi.js +0 -1047
- package/src/basket/reference-data/nasdaq100.js +0 -812
- package/src/basket/reference-data/sp500.js +0 -315
package/README.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# `@vuu-ui/vuu-data-test`
|
|
2
2
|
|
|
3
|
+
## Local basket trading
|
|
4
|
+
|
|
5
|
+
Constituent reference data is generated from the server's four CSV files in
|
|
6
|
+
`example/main/src/main/resources/static/`. From `vuu-ui`, run
|
|
7
|
+
`npm run generate:basket-data` after editing those files and commit the resulting
|
|
8
|
+
`src/basket/reference-data/constituent-data.json`. Use
|
|
9
|
+
`npm run generate:basket-data -- --check` to verify it is up to date.
|
|
10
|
+
The converter preserves source weights (without normalizing their units),
|
|
11
|
+
maps the differing price/change column names, and rejects invalid CSV records.
|
|
12
|
+
The browser loads only the generated JSON, not the CSV parser.
|
|
13
|
+
The JSON and its `constituents.ts` adapter use different basenames so their
|
|
14
|
+
JavaScript build outputs cannot collide.
|
|
15
|
+
|
|
16
|
+
The `BASKET` module provides its own simulated constituent prices, including
|
|
17
|
+
updates for all basket instruments. Its constituent join retains
|
|
18
|
+
`instanceIdRic` as the row key, so multiple trading baskets can contain the
|
|
19
|
+
same instrument. Direct edits through the join update the underlying
|
|
20
|
+
constituent table; subsequent price ticks preserve those edits.
|
|
21
|
+
Changing the basket side flips each constituent's current side, preserving
|
|
22
|
+
mixed BUY/SELL positions, as on the Scala server. Unchanged side edits do
|
|
23
|
+
nothing, and other basket instances are unaffected.
|
|
24
|
+
Editing basket units recalculates its constituent quantities as
|
|
25
|
+
`weighting * units`, truncated to whole units, matching the Scala service.
|
|
26
|
+
|
|
27
|
+
New baskets retain the `basketTradeName` supplied by the creation dialog.
|
|
28
|
+
Constituents default to the Limit price strategy (`priceStrategyId = 2`),
|
|
29
|
+
matching the Scala server.
|
|
30
|
+
Dropping an instrument from the Add constituent context panel adds it to the
|
|
31
|
+
requested basket instance. Manually added constituents start with weight `0.1`,
|
|
32
|
+
BUY side and Limit strategy; quantity is calculated from the current basket
|
|
33
|
+
units. Unknown instruments, duplicates, missing baskets and on-market baskets
|
|
34
|
+
are rejected without changing existing rows.
|
|
35
|
+
The Add constituent list excludes RICs already in the active basket using a
|
|
36
|
+
separate full-membership subscription, including rows outside the visible grid.
|
|
37
|
+
Sending a basket to market and taking it off market update its constituent
|
|
38
|
+
statuses; live rows include `orderStatus` and an initially zero `filledQty`.
|
|
39
|
+
Execution fills are not simulated.
|
|
40
|
+
|
|
3
41
|
## Admin test module dependencies
|
|
4
42
|
|
|
5
43
|
The `USER_ADMIN` and `MODULE_DISCOVERY` test modules use the published
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.0.0-alpha.
|
|
2
|
+
"version": "4.0.0-alpha.30",
|
|
3
3
|
"main": "./src/index.js",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "heswell",
|
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
"type-defs": "node ../../scripts/build-type-defs.mjs"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@heswell/module-admin": "1.
|
|
13
|
+
"@heswell/module-admin": "1.1.0",
|
|
14
14
|
"@heswell/user-admin": "1.0.1",
|
|
15
|
-
"@vuu-ui/vuu-data-editing": "4.0.0-alpha.
|
|
16
|
-
"@vuu-ui/vuu-data-local": "4.0.0-alpha.
|
|
17
|
-
"@vuu-ui/vuu-utils": "4.0.0-alpha.
|
|
15
|
+
"@vuu-ui/vuu-data-editing": "4.0.0-alpha.30",
|
|
16
|
+
"@vuu-ui/vuu-data-local": "4.0.0-alpha.30",
|
|
17
|
+
"@vuu-ui/vuu-utils": "4.0.0-alpha.30"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@vuu-ui/vuu-
|
|
21
|
-
"@vuu-ui/vuu-
|
|
22
|
-
"@vuu-ui/vuu-table-types": "4.0.0-alpha.
|
|
20
|
+
"@vuu-ui/vuu-protocol-types": "4.0.0-alpha.30",
|
|
21
|
+
"@vuu-ui/vuu-data-types": "4.0.0-alpha.30",
|
|
22
|
+
"@vuu-ui/vuu-table-types": "4.0.0-alpha.30"
|
|
23
23
|
},
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"files": [
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
],
|
|
30
30
|
"exports": {
|
|
31
31
|
".": {
|
|
32
|
+
"types": "./types/index.d.ts",
|
|
32
33
|
"import": "./src/index.js",
|
|
33
|
-
"
|
|
34
|
+
"default": "./src/index.js"
|
|
34
35
|
}
|
|
35
36
|
},
|
|
36
37
|
"module": "./src/index.js",
|
|
@@ -181,14 +181,19 @@ class TickingArrayDataSource extends ArrayDataSource {
|
|
|
181
181
|
get columns() {
|
|
182
182
|
return super.columns;
|
|
183
183
|
}
|
|
184
|
-
async endEditSession(saveChanges = false) {
|
|
184
|
+
async endEditSession(saveChanges = false, force = false) {
|
|
185
|
+
if ("unsubscribed" === this.status || !this.viewport) {
|
|
186
|
+
if (!saveChanges) return;
|
|
187
|
+
throw Error("Cannot save changes: datasource is unsubscribed");
|
|
188
|
+
}
|
|
185
189
|
const type = "RPC_REQUEST";
|
|
186
190
|
const rpcName = "endEditSession";
|
|
187
191
|
const rpcResponse = await this.rpcRequest(saveChanges ? {
|
|
188
192
|
type,
|
|
189
193
|
rpcName,
|
|
190
194
|
params: {
|
|
191
|
-
save: true
|
|
195
|
+
save: true,
|
|
196
|
+
force
|
|
192
197
|
}
|
|
193
198
|
} : {
|
|
194
199
|
type,
|
|
@@ -2,6 +2,7 @@ import { VuuModule } from "../core/module/VuuModule.js";
|
|
|
2
2
|
import TableContainer from "../core/table/TableContainer.js";
|
|
3
3
|
import { schemas } from "./basket-schemas.js";
|
|
4
4
|
import reference_data_constituents from "./reference-data/constituents.js";
|
|
5
|
+
import prices from "./reference-data/prices.js";
|
|
5
6
|
const { createTable: createTable } = TableContainer;
|
|
6
7
|
let basketIncrement = 1;
|
|
7
8
|
const createBasketTradingRow = (basketId, basketName, side = "BUY", status = "OFF MARKET")=>[
|
|
@@ -98,10 +99,7 @@ class BasketModule extends VuuModule {
|
|
|
98
99
|
}, {
|
|
99
100
|
module: "BASKET",
|
|
100
101
|
table: "basketTradingConstituent"
|
|
101
|
-
},
|
|
102
|
-
module: "SIMUL",
|
|
103
|
-
table: "prices"
|
|
104
|
-
}, "ric"),
|
|
102
|
+
}, prices, "ric"),
|
|
105
103
|
priceStrategyType: createTable(schemas.priceStrategyType, [
|
|
106
104
|
[
|
|
107
105
|
"Peg to Near Touch",
|
|
@@ -123,6 +121,22 @@ class BasketModule extends VuuModule {
|
|
|
123
121
|
};
|
|
124
122
|
constructor(){
|
|
125
123
|
super("BASKET");
|
|
124
|
+
const table = this.tables.basketTrading;
|
|
125
|
+
const update = table.update.bind(table);
|
|
126
|
+
table.update = (key, column, value)=>{
|
|
127
|
+
if ("side" !== column && "units" !== column) return update(key, column, value);
|
|
128
|
+
const basket = table.findByKey(key);
|
|
129
|
+
if (!basket) throw Error(`[BasketModule] basket ${key} not found`);
|
|
130
|
+
if (basket[table.map[column]] === value) return;
|
|
131
|
+
if ("units" === column && ("number" != typeof value || !Number.isInteger(value))) throw Error("[BasketModule] units must be an integer");
|
|
132
|
+
update(key, column, value);
|
|
133
|
+
const constituents = this.tables.basketTradingConstituent;
|
|
134
|
+
for (const row of constituents.data)if (row[constituents.map.instanceId] === key) {
|
|
135
|
+
const constituentKey = String(row[constituents.map.instanceIdRic]);
|
|
136
|
+
if ("side" === column) constituents.update(constituentKey, "side", "BUY" === row[constituents.map.side] ? "SELL" : "BUY");
|
|
137
|
+
else if ("number" == typeof value) constituents.update(constituentKey, "quantity", Math.trunc(Number(row[constituents.map.weighting]) * value));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
126
140
|
}
|
|
127
141
|
get menus() {
|
|
128
142
|
return {
|
|
@@ -196,45 +210,55 @@ class BasketModule extends VuuModule {
|
|
|
196
210
|
const { basketId: key } = buildDataColumnMap("basketConstituent");
|
|
197
211
|
const constituents = this.tables.basketConstituent.data.filter((c)=>c[key] === basketId);
|
|
198
212
|
const { instanceId } = tableMaps.basketTrading;
|
|
199
|
-
constituents.forEach(([, , description, , ric, , ,
|
|
200
|
-
|
|
201
|
-
const algoParams = "";
|
|
202
|
-
const limitPrice = 95;
|
|
203
|
-
const notionalLocal = 0;
|
|
204
|
-
const notionalUsd = 0;
|
|
205
|
-
const pctFilled = 0;
|
|
206
|
-
const priceSpread = 0;
|
|
207
|
-
const priceStrategyId = "";
|
|
208
|
-
const side = "BUY";
|
|
209
|
-
const venue = "venue";
|
|
210
|
-
const basketInstanceId = basketTradingRow[instanceId];
|
|
211
|
-
const basketTradingConstituentRow = [
|
|
212
|
-
algo,
|
|
213
|
-
algoParams,
|
|
214
|
-
basketId,
|
|
215
|
-
description,
|
|
216
|
-
basketInstanceId,
|
|
217
|
-
`${basketInstanceId}-${ric}`,
|
|
218
|
-
limitPrice,
|
|
219
|
-
notionalLocal,
|
|
220
|
-
notionalUsd,
|
|
221
|
-
pctFilled,
|
|
222
|
-
priceSpread,
|
|
223
|
-
priceStrategyId,
|
|
224
|
-
quantity,
|
|
225
|
-
ric,
|
|
226
|
-
side,
|
|
227
|
-
venue,
|
|
228
|
-
weighting
|
|
229
|
-
];
|
|
230
|
-
basketTradingConstituent.insert(basketTradingConstituentRow);
|
|
213
|
+
constituents.forEach(([, , description, , ric, , , , weighting])=>{
|
|
214
|
+
this.insertTradingConstituent(basketTradingRow, String(ric), String(description), Number(weighting));
|
|
231
215
|
});
|
|
232
216
|
return basketTradingRow[instanceId];
|
|
233
217
|
}
|
|
218
|
+
insertTradingConstituent(basketTradingRow, ric, description, weighting) {
|
|
219
|
+
const { basketId, instanceId } = tableMaps.basketTrading;
|
|
220
|
+
const quantity = Math.trunc(Number(weighting) * Number(basketTradingRow[tableMaps.basketTrading.units]));
|
|
221
|
+
const algo = "";
|
|
222
|
+
const algoParams = "";
|
|
223
|
+
const limitPrice = 95;
|
|
224
|
+
const notionalLocal = 0;
|
|
225
|
+
const notionalUsd = 0;
|
|
226
|
+
const pctFilled = 0;
|
|
227
|
+
const priceSpread = 0;
|
|
228
|
+
const priceStrategyId = 2;
|
|
229
|
+
const side = "BUY";
|
|
230
|
+
const status = "OFF-MARKET";
|
|
231
|
+
const venue = "venue";
|
|
232
|
+
const basketInstanceId = basketTradingRow[instanceId];
|
|
233
|
+
const basketTradingConstituentRow = [
|
|
234
|
+
algo,
|
|
235
|
+
algoParams,
|
|
236
|
+
basketTradingRow[basketId],
|
|
237
|
+
description,
|
|
238
|
+
basketInstanceId,
|
|
239
|
+
`${basketInstanceId}-${ric}`,
|
|
240
|
+
limitPrice,
|
|
241
|
+
notionalLocal,
|
|
242
|
+
notionalUsd,
|
|
243
|
+
pctFilled,
|
|
244
|
+
priceSpread,
|
|
245
|
+
priceStrategyId,
|
|
246
|
+
quantity,
|
|
247
|
+
ric,
|
|
248
|
+
side,
|
|
249
|
+
status,
|
|
250
|
+
venue,
|
|
251
|
+
weighting,
|
|
252
|
+
"PENDING",
|
|
253
|
+
0
|
|
254
|
+
];
|
|
255
|
+
basketTradingConstituent.insert(basketTradingConstituentRow);
|
|
256
|
+
return `${basketInstanceId}-${ric}`;
|
|
257
|
+
}
|
|
234
258
|
createNewBasket = async (rpcRequest)=>{
|
|
235
259
|
if ("VIEWPORT_CONTEXT" === rpcRequest.context.type) {
|
|
236
|
-
const { sourceBasketId,
|
|
237
|
-
const key = this.createTradingBasket(sourceBasketId,
|
|
260
|
+
const { sourceBasketId, basketTradeName } = rpcRequest.params;
|
|
261
|
+
const key = this.createTradingBasket(sourceBasketId, basketTradeName);
|
|
238
262
|
return {
|
|
239
263
|
type: "SUCCESS_RESULT",
|
|
240
264
|
data: key
|
|
@@ -246,6 +270,7 @@ class BasketModule extends VuuModule {
|
|
|
246
270
|
if ("VIEWPORT_CONTEXT" === rpcRequest.context.type) {
|
|
247
271
|
const { basketInstanceId } = rpcRequest.params;
|
|
248
272
|
this.tables.basketTrading.update(basketInstanceId, "status", "ON_MARKET");
|
|
273
|
+
this.updateConstituentStatus(basketInstanceId, "ON_MARKET", "ACKED");
|
|
249
274
|
return {
|
|
250
275
|
type: "SUCCESS_RESULT",
|
|
251
276
|
data: void 0
|
|
@@ -257,6 +282,7 @@ class BasketModule extends VuuModule {
|
|
|
257
282
|
if ("VIEWPORT_CONTEXT" === rpcRequest.context.type) {
|
|
258
283
|
const { basketInstanceId } = rpcRequest.params;
|
|
259
284
|
this.tables.basketTrading.update(basketInstanceId, "status", "OFF-MARKET");
|
|
285
|
+
this.updateConstituentStatus(basketInstanceId, "OFF-MARKET", "CANCELLED");
|
|
260
286
|
return {
|
|
261
287
|
type: "SUCCESS_RESULT",
|
|
262
288
|
data: void 0
|
|
@@ -264,9 +290,48 @@ class BasketModule extends VuuModule {
|
|
|
264
290
|
}
|
|
265
291
|
throw Error("[BasketModule] createNewBasket invalid request type");
|
|
266
292
|
};
|
|
267
|
-
addConstituent = async ()=>{
|
|
268
|
-
|
|
293
|
+
addConstituent = async (request)=>{
|
|
294
|
+
const { params } = request;
|
|
295
|
+
const ric = "ric" in params ? params.ric : void 0;
|
|
296
|
+
const basketInstanceId = "basketInstanceId" in params ? params.basketInstanceId : void 0;
|
|
297
|
+
if ("VIEWPORT_CONTEXT" !== request.context.type || "string" != typeof ric || "string" != typeof basketInstanceId) return {
|
|
298
|
+
type: "ERROR_RESULT",
|
|
299
|
+
errorMessage: "Invalid add constituent request"
|
|
300
|
+
};
|
|
301
|
+
const basket = this.tables.basketTrading.findByKey(basketInstanceId);
|
|
302
|
+
if (!basket) return {
|
|
303
|
+
type: "ERROR_RESULT",
|
|
304
|
+
errorMessage: "Basket not found"
|
|
305
|
+
};
|
|
306
|
+
if ("ON_MARKET" === basket[tableMaps.basketTrading.status]) return {
|
|
307
|
+
type: "ERROR_RESULT",
|
|
308
|
+
errorMessage: "Take the basket off market before adding constituents"
|
|
309
|
+
};
|
|
310
|
+
const source = this.tables.basketConstituent;
|
|
311
|
+
const reference = source.data.find((row)=>row[source.map.ric] === ric);
|
|
312
|
+
if (!reference) return {
|
|
313
|
+
type: "ERROR_RESULT",
|
|
314
|
+
errorMessage: `Unknown constituent ${ric}`
|
|
315
|
+
};
|
|
316
|
+
const key = `${basketInstanceId}-${ric}`;
|
|
317
|
+
if (this.tables.basketTradingConstituent.findByKey(key)) return {
|
|
318
|
+
type: "ERROR_RESULT",
|
|
319
|
+
errorMessage: `${ric} is already in this basket`
|
|
320
|
+
};
|
|
321
|
+
this.insertTradingConstituent(basket, ric, String(reference[source.map.description]), 0.1);
|
|
322
|
+
return {
|
|
323
|
+
type: "SUCCESS_RESULT",
|
|
324
|
+
data: key
|
|
325
|
+
};
|
|
269
326
|
};
|
|
327
|
+
updateConstituentStatus(basketInstanceId, status, orderStatus) {
|
|
328
|
+
const table = this.tables.basketTradingConstituent;
|
|
329
|
+
for (const row of table.data)if (row[table.map.instanceId] === basketInstanceId) {
|
|
330
|
+
const key = String(row[table.map.instanceIdRic]);
|
|
331
|
+
table.update(key, "status", status);
|
|
332
|
+
table.update(key, "orderStatus", orderStatus);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
270
335
|
}
|
|
271
336
|
const basketModule = new BasketModule();
|
|
272
337
|
export { BasketModule, basketModule, createBasketTradingRow };
|
|
@@ -216,6 +216,14 @@ const schemas = {
|
|
|
216
216
|
{
|
|
217
217
|
name: "weighting",
|
|
218
218
|
serverDataType: "double"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "orderStatus",
|
|
222
|
+
serverDataType: "string"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "filledQty",
|
|
226
|
+
serverDataType: "long"
|
|
219
227
|
}
|
|
220
228
|
],
|
|
221
229
|
key: "instanceIdRic",
|
|
@@ -334,6 +342,14 @@ const schemas = {
|
|
|
334
342
|
{
|
|
335
343
|
name: "weighting",
|
|
336
344
|
serverDataType: "double"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "orderStatus",
|
|
348
|
+
serverDataType: "string"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "filledQty",
|
|
352
|
+
serverDataType: "long"
|
|
337
353
|
}
|
|
338
354
|
],
|
|
339
355
|
key: "instanceIdRic",
|