Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
843f42c3e3 | ||
|
|
3afc3d64b6 | ||
|
|
44f2cdd7c0 | ||
|
|
3f36f54781 | ||
|
|
59e66cf1ef | ||
|
|
dec178f133 | ||
|
|
e9c9a3bad6 | ||
|
|
46a4aa99df | ||
|
|
1097bbcd60 | ||
|
|
840ba7e0b3 | ||
|
|
7e7e88cedc | ||
|
|
727a3d5b97 | ||
|
|
3044d5d5ec | ||
|
|
506084242a | ||
|
|
067946127a | ||
|
|
297848bcdb | ||
|
|
376d64cfbe | ||
|
|
bdf3ae7c92 | ||
|
|
97d0637d88 | ||
|
|
b4d85cdadf | ||
|
|
2b0cb84069 | ||
|
|
0412c45ed3 | ||
|
|
b984613b8a | ||
|
|
e712433d6f | ||
|
|
4d8b20d084 | ||
|
|
df86187457 | ||
|
|
620ea4b4af | ||
|
|
e2951b4436 | ||
|
|
0ebc3cf111 | ||
|
|
fd9b2990ae | ||
|
|
15d24f96c2 | ||
|
|
b1bbc6ab0d | ||
|
|
39be9e41c8 | ||
|
|
fd91b78742 | ||
|
|
95a5dd0ceb | ||
|
|
a9aa76deed | ||
|
|
a80a4f327e | ||
|
|
7e9a853a50 | ||
|
|
0e0d4412b8 | ||
|
|
b3a19eab05 | ||
|
|
fdb1826e01 | ||
|
|
e2d6bc1c45 | ||
|
|
d5a06cc3e7 | ||
|
|
3e7c2e3c18 | ||
|
|
ea6eaf6e1d | ||
|
|
dea2358c51 | ||
|
|
491d0c0b7a | ||
|
|
f593b457a5 | ||
|
|
10a3ba52f4 | ||
|
|
56bc7f216a | ||
|
|
a57b99b2e8 | ||
|
|
d3a0595934 | ||
|
|
a659fbc7cd | ||
|
|
542b8e946d | ||
|
|
d4d1f8f533 | ||
|
|
624a432c44 | ||
|
|
68f09c2e27 | ||
|
|
3d22a58bac | ||
|
|
33e46b2fb1 | ||
|
|
cb357440ed | ||
|
|
a88670b95d | ||
|
|
bb70db70b5 | ||
|
|
2c3e4b7d4d | ||
|
|
843c4f447f | ||
|
|
d41c76e6a3 | ||
|
|
805722cdc7 | ||
|
|
ef64d3c25e | ||
|
|
0b0cf744a6 | ||
|
|
c3410db0aa | ||
|
|
c5a89b0bb6 | ||
|
|
89f5f94588 | ||
|
|
c8ac56b0a1 | ||
|
|
b152b2b853 | ||
|
|
354390ed11 | ||
|
|
08839aadb2 | ||
|
|
b7cb17500c | ||
|
|
01b50e1138 | ||
|
|
8fdb7485ba | ||
|
|
293d51038f | ||
|
|
8db81fa248 | ||
|
|
47628089ac | ||
|
|
8b2c5a60aa | ||
|
|
f1df673e61 | ||
|
|
9faee77360 | ||
|
|
0fd45ba92a | ||
|
|
4e440fc218 | ||
|
|
41c8351709 | ||
|
|
1238648c2b | ||
|
|
03ac88683f | ||
|
|
fe4c057b20 | ||
|
|
e6c6997e46 | ||
|
|
ddd8778bab | ||
|
|
f95d258210 | ||
|
|
a2e1cf8dd5 | ||
|
|
0b8b68e6e6 | ||
|
|
8658a11d1f | ||
|
|
d0aa90131a | ||
|
|
ffb30177b1 | ||
|
|
297093f9ce | ||
|
|
5ba8440877 | ||
|
|
228e49d152 | ||
|
|
a9e983a717 | ||
|
|
25445d58d0 | ||
|
|
0b313a8aa8 | ||
|
|
54f954fd4b | ||
|
|
980f38ba03 | ||
|
|
198086e639 | ||
|
|
9eada2724b | ||
|
|
b5f53ee918 | ||
|
|
a3a8eb14ad | ||
|
|
333acf0822 | ||
|
|
efc175f654 | ||
|
|
6074afd999 | ||
|
|
219dff4055 | ||
|
|
9a3b07b622 | ||
|
|
1af49376b6 | ||
|
|
f90f6cfb33 | ||
|
|
6433f9ba98 | ||
|
|
cc1882ea1a | ||
|
|
56e83a1e69 | ||
|
|
2688281a10 | ||
|
|
69d429fa4e | ||
|
|
e8c9e70a4a | ||
|
|
6c78850902 | ||
|
|
adab377e44 | ||
|
|
7bdaa35768 | ||
|
|
1c7f79f293 | ||
|
|
3a7246c5b0 | ||
|
|
17a1190e34 | ||
|
|
3bd95de839 | ||
|
|
576d3836b3 | ||
|
|
f1788b172c | ||
|
|
82834dc9e6 | ||
|
|
ae5912d41d | ||
|
|
adbab4057f | ||
|
|
ca4a639b86 | ||
|
|
a539a06f45 | ||
|
|
2ff96e3528 | ||
|
|
1a87544ccb | ||
|
|
b97450297e | ||
|
|
6a1040a339 | ||
|
|
7fe20129d2 | ||
|
|
4f59c1bb47 | ||
|
|
3ba17f1c87 | ||
|
|
ef874dda7e | ||
|
|
6e966a6229 | ||
|
|
ed3b890c67 | ||
|
|
2a46fd6227 | ||
|
|
74f1bf2480 | ||
|
|
23e13846c6 | ||
|
|
39e4549972 | ||
|
|
d924962546 | ||
|
|
0f1c81eb48 | ||
|
|
5e6fe7282d | ||
|
|
4e48bcf6b5 | ||
|
|
a47dad1ce4 | ||
|
|
6fd829df49 | ||
|
|
1809c778e2 | ||
|
|
2178e16d3c | ||
|
|
deadbc6cf2 | ||
|
|
445d77223f | ||
|
|
09b24e33fc | ||
|
|
84553d11c4 | ||
|
|
eb1bab5878 | ||
|
|
a7a58038b3 | ||
|
|
1b9af8f010 | ||
|
|
113c4fe539 | ||
|
|
c33662dc2c | ||
|
|
0dcb3d3dfd | ||
|
|
8817b22376 | ||
|
|
212f2a3d36 | ||
|
|
b48da514e5 | ||
|
|
649225ef6c | ||
|
|
59cba2456b | ||
|
|
839c3d430b | ||
|
|
35495dc7cf | ||
|
|
5b1f2522e1 | ||
|
|
8ee78c83d2 | ||
|
|
6c3ccdf856 | ||
|
|
157d834cbb | ||
|
|
b4beb7c0db | ||
|
|
4f2e9b19cc | ||
|
|
fed7ca1ff3 | ||
|
|
f84517c15c | ||
|
|
075354a975 | ||
|
|
73cfa17791 | ||
|
|
474a53ba92 | ||
|
|
69612eaee7 | ||
|
|
b7ad777de4 | ||
|
|
d6a70d852e | ||
|
|
4450476a40 | ||
|
|
33f12edce5 | ||
|
|
ab2e9fe6bd | ||
|
|
8bcffea686 | ||
|
|
941472c5c0 | ||
|
|
d2a5e03976 | ||
|
|
97e3bce327 | ||
|
|
e766667584 | ||
|
|
a93c110c29 | ||
|
|
3b0bbe6908 | ||
|
|
1147edcbb9 | ||
|
|
e1c7b89408 | ||
|
|
c50133ba02 | ||
|
|
f1dbacb989 | ||
|
|
ad3ce72a39 | ||
|
|
57f48472d3 | ||
|
|
0535eedbbf | ||
|
|
45ae6ac585 | ||
|
|
2740de913c | ||
|
|
5a90676a25 | ||
|
|
8536f782ce | ||
|
|
5f45ddf210 | ||
|
|
67c83c7d68 | ||
|
|
6f6e1a9e55 | ||
|
|
c80451bc5e | ||
|
|
ba53543e43 | ||
|
|
d6a533ca5c | ||
|
|
e0112eff89 | ||
|
|
38b29f0afb | ||
|
|
8b9d689a5f | ||
|
|
097edb2726 | ||
|
|
12e3e9a222 | ||
|
|
ce19ba1419 | ||
|
|
c81a22e55d | ||
|
|
bd04f49ff5 | ||
|
|
3a9e8970d6 | ||
|
|
9837645d16 | ||
|
|
1789c02295 | ||
|
|
3280376df3 | ||
|
|
d251593f04 | ||
|
|
9afeb29c63 | ||
|
|
e4dc07ce3f | ||
|
|
2bbf22a153 | ||
|
|
a598ae8bd2 | ||
|
|
4d72b9d2e7 | ||
|
|
e000b1d0c0 | ||
|
|
ef4261fe7f | ||
|
|
c3b75aa742 | ||
|
|
2518be52fb | ||
|
|
f3ee6c0e30 | ||
|
|
94363767ab | ||
|
|
9dd6e39665 | ||
|
|
b62d9751cf | ||
|
|
26b5fe5699 | ||
|
|
2fccceb91d | ||
|
|
fa49a0de07 | ||
|
|
bd28a0fa54 | ||
|
|
14ebe5ef51 | ||
|
|
a9b4049307 | ||
|
|
234d499731 | ||
|
|
324b9c1cc7 | ||
|
|
722ae68cf1 | ||
|
|
6aad329e09 | ||
|
|
0641cf45d9 | ||
|
|
1fe0efe5c3 | ||
|
|
93938b6ef2 | ||
|
|
818806a4e1 | ||
|
|
261bf3e475 | ||
|
|
dbe2194ed3 | ||
|
|
e39313ecf6 | ||
|
|
8095100bee | ||
|
|
45e10ca377 | ||
|
|
c3299232f1 | ||
|
|
4de77ce339 | ||
|
|
96f71290a4 | ||
|
|
a76415a52f | ||
|
|
e2b80942df | ||
|
|
51fb068469 | ||
|
|
06305ee4ac | ||
|
|
81b36edfe3 | ||
|
|
90976ffeba | ||
|
|
43aad8b53f | ||
|
|
5434647ecb | ||
|
|
7c4aa11c69 | ||
|
|
176c1f8d92 | ||
|
|
8b0c9e7722 | ||
|
|
70e87151bc | ||
|
|
d1299f8a78 | ||
|
|
20e58dc80b | ||
|
|
a5c2e8b287 | ||
|
|
24e766ba59 | ||
|
|
ddd81ea93a | ||
|
|
5481cce34c | ||
|
|
58d39c4833 | ||
|
|
367d9be724 | ||
|
|
99f2ab7c8a | ||
|
|
8b091b39eb | ||
|
|
9f7b0e5898 | ||
|
|
5e073c2330 | ||
|
|
9d1f0e5538 | ||
|
|
02ade1e0c9 | ||
|
|
296749bae3 | ||
|
|
49ec9885eb | ||
|
|
04264e70b9 | ||
|
|
1b425d9a7c | ||
|
|
3546b65c91 | ||
|
|
eaadc74242 | ||
|
|
6760ca1898 | ||
|
|
758da29e25 | ||
|
|
60e246036b | ||
|
|
4b34f959e6 | ||
|
|
8abbd24cbe | ||
|
|
932f852d6a | ||
|
|
5773945791 | ||
|
|
bda1bd8f96 | ||
|
|
5f2d3c7c26 | ||
|
|
326532ed5c | ||
|
|
5cfb4a65e9 | ||
|
|
c3632e02d3 | ||
|
|
f955fef680 | ||
|
|
2214988442 | ||
|
|
e80ab05969 | ||
|
|
92bdbd460a | ||
|
|
a357d978ca | ||
|
|
dfb673bd63 | ||
|
|
24f366491b | ||
|
|
55134bf172 | ||
|
|
b6b33f14ef | ||
|
|
12d532a17f | ||
|
|
09679ff494 | ||
|
|
781e7c98a0 | ||
|
|
4189b05665 | ||
|
|
1b959b09c8 | ||
|
|
ff5b04f8ee | ||
|
|
f55637ea08 | ||
|
|
0272df9974 | ||
|
|
0e02428b79 | ||
|
|
721c889235 | ||
|
|
9957af655f | ||
|
|
eab92fb486 | ||
|
|
9f1cd47cfa | ||
|
|
90c3edbbe7 | ||
|
|
4180b9a8ec | ||
|
|
adee8b584c | ||
|
|
329ad323e0 | ||
|
|
a8473f9340 | ||
|
|
1c0115f8cf | ||
|
|
a6ef802d3f | ||
|
|
37b5c37015 | ||
|
|
1e28e214a4 | ||
|
|
c713b97441 | ||
|
|
4e8525f015 | ||
|
|
eb79264b9b | ||
|
|
9c359ddb33 | ||
|
|
67a16d5085 | ||
|
|
5ba244c982 | ||
|
|
4029a6a58a | ||
|
|
c03d05285b | ||
|
|
6049cc4b43 | ||
|
|
d916180132 | ||
|
|
ad9f28f782 | ||
|
|
2890e4ebcc | ||
|
|
6638e4c765 | ||
|
|
78323d09e8 | ||
|
|
f2621da423 | ||
|
|
a251a8f411 | ||
|
|
e7790679ca | ||
|
|
2a5e777c15 | ||
|
|
fdbbce9ff3 | ||
|
|
cf2bc03dd1 | ||
|
|
d47ac768c4 | ||
|
|
4867f4fa53 | ||
|
|
902a99e7d0 | ||
|
|
faf5f664dc | ||
|
|
88e17743ef | ||
|
|
4af94aaaa8 | ||
|
|
950073703f | ||
|
|
2bea0a0a11 | ||
|
|
b55067be91 | ||
|
|
7ea20dbbff | ||
|
|
2dcf1424ea | ||
|
|
caedb1ed64 | ||
|
|
504d09eb6c | ||
|
|
c2ea5aa36f | ||
|
|
d39856a93a | ||
|
|
c367488844 | ||
|
|
f8c7cc87b9 | ||
|
|
ee9a9bd721 | ||
|
|
7520d3516a | ||
|
|
a2a9df86fc | ||
|
|
aa31aa7a3e | ||
|
|
950b8686f2 | ||
|
|
b6ab217a11 | ||
|
|
3b8176d326 | ||
|
|
e9ec07cb2b | ||
|
|
78659b5d20 | ||
|
|
9e26a2c651 | ||
|
|
3243ce1a45 | ||
|
|
475b6ae25b | ||
|
|
b853af59d6 | ||
|
|
ded1ad42e7 | ||
|
|
f3541b6c7f | ||
|
|
3c40c84c02 | ||
|
|
d50420edb9 | ||
|
|
e0794f0725 | ||
|
|
fb6282ae9e | ||
|
|
23995e5cc6 | ||
|
|
161545b864 | ||
|
|
285250dded | ||
|
|
bbc3e03e48 | ||
|
|
fb19dd8ce5 | ||
|
|
c0a2c6a24a | ||
|
|
6b8d0d203c | ||
|
|
7cbd5adccc | ||
|
|
3b8a4f89cc | ||
|
|
c0bec8cfa5 | ||
|
|
19b0d8bf56 | ||
|
|
ede14ea7b3 | ||
|
|
48ae7c4c33 | ||
|
|
fc217fd904 | ||
|
|
b692a3e9d2 | ||
|
|
48f5e47335 | ||
|
|
a080a0ccaa | ||
|
|
90321045f6 | ||
|
|
8ad9e24415 | ||
|
|
2008786c4e | ||
|
|
57f8036796 | ||
|
|
21a304261e | ||
|
|
cefb2b2524 | ||
|
|
e26bdb228b | ||
|
|
1621ad03bf | ||
|
|
174acf1597 | ||
|
|
bf32c6133a | ||
|
|
fe73874b19 | ||
|
|
0eb82a0d5c | ||
|
|
04f165b1a3 | ||
|
|
fc2165d85d | ||
|
|
893aa7a370 | ||
|
|
245b5d0f99 | ||
|
|
a368b2ea16 | ||
|
|
6294ef6db2 | ||
|
|
d5225aa75b | ||
|
|
48e6e1e139 | ||
|
|
a9cca80c6d | ||
|
|
1ebd0be57c | ||
|
|
28779d333c | ||
|
|
c9387a92be | ||
|
|
aee9066ccf | ||
|
|
ea3c130738 | ||
|
|
193f248f06 | ||
|
|
5212fb272e | ||
|
|
6772c77e01 | ||
|
|
95c589f594 | ||
|
|
699c7c5bfe | ||
|
|
7de82b5685 | ||
|
|
a8e9d0d7d8 | ||
|
|
d078e73179 | ||
|
|
4351d6e66a | ||
|
|
9850d6bde0 | ||
|
|
d23a072ad6 | ||
|
|
6ef0808f49 | ||
|
|
e1c2d8aaaf | ||
|
|
719fd03063 | ||
|
|
d436dae7cd | ||
|
|
f66004b465 | ||
|
|
aa8a56787b | ||
|
|
35a69396f1 | ||
|
|
ddfb6d1105 | ||
|
|
005c0f9a5d | ||
|
|
cf62eae51b | ||
|
|
c7e49e8d21 | ||
|
|
8bc420190b | ||
|
|
62711a144b | ||
|
|
333273b48a | ||
|
|
dd194f8ac0 | ||
|
|
5992f4814d | ||
|
|
d67f15b3fb | ||
|
|
be8635788a | ||
|
|
d96355e6c4 | ||
|
|
4ef2c8a2da | ||
|
|
058fbe1aee | ||
|
|
bc0f9f574a | ||
|
|
f575b82ce0 | ||
|
|
050ea18a02 | ||
|
|
18829279d2 | ||
|
|
75703d54ea | ||
|
|
0a3fc1bca0 | ||
|
|
bc688ef44d | ||
|
|
57af89b628 | ||
|
|
4fcc70bcf0 | ||
|
|
cad6f2c1eb | ||
|
|
c16b898d86 | ||
|
|
4f98244c3a | ||
|
|
8770f98fb1 | ||
|
|
bbcf78517f | ||
|
|
b9a940b4e3 | ||
|
|
d8a4c1aaf7 | ||
|
|
9803cf31c8 | ||
|
|
4a36177992 | ||
|
|
421614198a | ||
|
|
51e227e921 | ||
|
|
74948d11c5 | ||
|
|
f88e1b5903 | ||
|
|
59115ce886 | ||
|
|
4b18d15421 | ||
|
|
9be591c634 | ||
|
|
462af2f1f3 | ||
|
|
ab35c943fd | ||
|
|
aa36b24165 | ||
|
|
547cdda3db | ||
|
|
3b77fd1a6c | ||
|
|
a8110c9a8d | ||
|
|
b6f80d6a4a | ||
|
|
18489e64a2 | ||
|
|
a3d2df9ba2 | ||
|
|
314700e2e7 | ||
|
|
461b6c15d6 | ||
|
|
d23ac7e8ef | ||
|
|
68c0179465 | ||
|
|
8bc1455813 | ||
|
|
ecaa04ecf8 | ||
|
|
44e3070544 | ||
|
|
1815950835 | ||
|
|
8762fbf7ce | ||
|
|
0459171d6e | ||
|
|
fcdf72c5d9 | ||
|
|
f5caf0b7ba | ||
|
|
a0f2673b3a | ||
|
|
ba3439b049 | ||
|
|
ae4d7f719b | ||
|
|
64916f6bfc | ||
|
|
1a9dd1c37a | ||
|
|
d77925c568 | ||
|
|
f1f1b68ffb | ||
|
|
835f453f03 | ||
|
|
0eb9518485 | ||
|
|
b5c70fc34e | ||
|
|
a1dba1d7d0 | ||
|
|
efdea5256f | ||
|
|
5df1e5f383 | ||
|
|
99750da31d | ||
|
|
49247dba3f | ||
|
|
a25257cb33 | ||
|
|
b1c180d663 | ||
|
|
6afb16abcb | ||
|
|
995fa3df35 | ||
|
|
18eb28dfd2 | ||
|
|
99cdfadf8d | ||
|
|
feab3cbb89 | ||
|
|
1f7c1be194 | ||
|
|
f78f18b3b4 | ||
|
|
6e7fa91718 | ||
|
|
8cac406547 | ||
|
|
0d83f28cbc | ||
|
|
941f4bf837 | ||
|
|
8bbe9cfd98 | ||
|
|
ba633532eb | ||
|
|
7bf50415ca | ||
|
|
edbaa9c435 | ||
|
|
7789f39c9d | ||
|
|
6e188b4f73 | ||
|
|
33e8ff58c5 | ||
|
|
24ae03a9f8 | ||
|
|
5fe0335e1a | ||
|
|
6cbf707094 | ||
|
|
7e4ad55d9d | ||
|
|
35a0074014 | ||
|
|
b8dd5c119d | ||
|
|
8ada73aa64 | ||
|
|
a16ff30873 |
No files matched your search
@@ -1,5 +1,7 @@
|
||||
nohup.out
|
||||
build/
|
||||
build
|
||||
cme_data/
|
||||
cmake-build-debug/
|
||||
.idea/
|
||||
.dir-locals.el
|
||||
@@ -34,3 +36,5 @@ trace_data/
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
/.ignore
|
||||
/build
|
||||
/analysis_data
|
||||
@@ -46,7 +46,7 @@
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "(gdb) Launch",
|
||||
"type": "cppdbg",
|
||||
@@ -98,6 +98,46 @@
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "sparkcmd Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"historical",
|
||||
"--rstart", "11/20/2019",
|
||||
"--rend", "11/29/2019",
|
||||
"--rsymbol", "NG",
|
||||
"--rsecuritytype", "Synthetic",
|
||||
"--post_process_db", "NG_201911_W4",
|
||||
"--post_process_db_host", "bigblue",
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "sparkcmd simulation",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"historical",
|
||||
"--rstart", "11/26/2019",
|
||||
"--rend", "11/26/2019",
|
||||
"--rsymbol", "ZN",
|
||||
"--rsecuritytype", "Synthetic",
|
||||
"--algo_security", "ZNH0;ZNZ9-ZNH0;ZNZ9",
|
||||
"--algo_type", "RoundTrip",
|
||||
"--algo_params", "size=10;tick_size=0.015625,0.0078125,0.015625;hedge_factors=1,1,-1",
|
||||
"--skip_preanalysis", "true",
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Excel Launch",
|
||||
"type": "clr",
|
||||
@@ -131,6 +171,18 @@
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": ".NET Framework Attach",
|
||||
"type": "clr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": "Spreader Tests",
|
||||
"type": "cppdbg",
|
||||
@@ -342,15 +394,19 @@
|
||||
"justMyCode": true
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Remote Launch",
|
||||
"name": "sparkcmd vsdbg (c#)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "/usr/bin/dotnet",
|
||||
"program": "sparkcmd",
|
||||
"args": [
|
||||
"sparkcmd.dll",
|
||||
"test",
|
||||
"test_join_create_and_takeout_market"
|
||||
"historical",
|
||||
"--rstart", "11/20/2019",
|
||||
"--rend", "11/20/2019",
|
||||
"--rsymbol", "CL",
|
||||
"--rsecuritytype", "Spread",
|
||||
"--post_process_db", "mbo_history",
|
||||
],
|
||||
"env": {"LD_LIBRARY_PATH" : "."},
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server",
|
||||
"pipeTransport": {
|
||||
@@ -361,11 +417,77 @@
|
||||
"debuggerPath": "/home/bts/vsdbg/vsdbg",
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"quoteArgs": true
|
||||
},
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
},
|
||||
"justMyCode": false
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "sparkcmd gdb (c++)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkcmd",
|
||||
"args": [
|
||||
"historical",
|
||||
"--rstart", "10/21/2019",
|
||||
"--rend", "10/21/2019",
|
||||
"--rsymbol", "CL",
|
||||
"--rsecuritytype", "Future",
|
||||
"--algo_security", "CLX9",
|
||||
"--algo_type", "TWAP",
|
||||
"--algo_params", "'size=100;profit_ticks=1;tick_size=1,1,1;;hedge_factors=1,1,-1'",
|
||||
"--skip_preanalysis", "true",
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"logging": {
|
||||
"engineLogging": false
|
||||
},
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Skip STL",
|
||||
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "LD LIBRARY",
|
||||
"text": "set env LD_LIBRARY_PATH=.",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Jupyter notebook",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "jupyter",
|
||||
"args": ["notebook", "--notebook-dir=c:\\code\\bts\\spark\\src\\client\\spark_historical\\Notebooks"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"stopAtEntry": false,
|
||||
"console": "internalConsole",
|
||||
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
@@ -48,6 +48,7 @@ else ()
|
||||
add_subdirectory(doc/user)
|
||||
add_subdirectory(src/sdk)
|
||||
add_subdirectory(src/data_files)
|
||||
add_subdirectory(src/client)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
||||
@@ -58,9 +59,17 @@ else ()
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/order_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
DEPENDS sparkd data_files_dir spark_client_dir)
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
|
||||
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||
add_custom_command(TARGET server_layout POST_BUILD
|
||||
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
|
||||
else()
|
||||
add_custom_command(TARGET server_layout POST_BUILD
|
||||
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/publish/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
|
||||
endif()
|
||||
|
||||
endif()
|
||||
@@ -0,0 +1 @@
|
||||
public
|
||||
@@ -0,0 +1,15 @@
|
||||
# The URL the site will be built for
|
||||
base_url = "https://api.bluetradesys.com"
|
||||
|
||||
title = "BTS Spark API"
|
||||
description = "Documentation for the BTS Spark API"
|
||||
|
||||
compile_sass = true
|
||||
|
||||
highlight_code = true
|
||||
highlight_theme = "kronuz"
|
||||
insert_anchor_links = true
|
||||
build_search_index = true
|
||||
|
||||
[extra]
|
||||
author = "Blue Trading Systems"
|
||||
@@ -0,0 +1,5 @@
|
||||
+++
|
||||
title = "BTS Spark API"
|
||||
+++
|
||||
|
||||
This is index
|
||||
@@ -0,0 +1,7 @@
|
||||
+++
|
||||
title="Client API"
|
||||
template="client.html"
|
||||
sort_by="weight"
|
||||
+++
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
+++
|
||||
title="Getting Started"
|
||||
template="client.html"
|
||||
sort_by="weight"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
+++
|
||||
title = "Install Prerequisites"
|
||||
template="client.html"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
## Coming Soon
|
||||
|
||||
* Install .net core
|
||||
@@ -0,0 +1,25 @@
|
||||
+++
|
||||
title="Algo API"
|
||||
template="documentation.html"
|
||||
redirect_to = "server/getting-started/overview/"
|
||||
sort_by="weight"
|
||||
+++
|
||||
|
||||
Getting Started
|
||||
- Install (docker, editor)
|
||||
- Downloading
|
||||
- Write your first algo
|
||||
- Testing
|
||||
|
||||
Reference
|
||||
- Subscriptions
|
||||
- Child Orders
|
||||
- Detaching
|
||||
- Attaching
|
||||
- ...
|
||||
|
||||
Samples
|
||||
- Iceberg
|
||||
- Pro-rata
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
+++
|
||||
title="Getting Started"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,138 @@
|
||||
+++
|
||||
title = "Development"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
# Visual Studio Code
|
||||
|
||||
In this section we're going to use the Remote - Containers feature of <a href="https://code.visualstudio.com/" target="_blank">Visual Studio Code<i class="fa fa-fw fa-window-restore" aria-hidden="true"></i></a> to simplify the development experience. If you are using a different editor skip to the <a href="#other-editors">other editor</a> section below.
|
||||
|
||||
## Remote - Containers
|
||||
|
||||
The Visual Studio Code Remote containers extension allows it to run a complete development environment inside the Spark SDK Docker container. The editor UI runs as a thin client on your desktop and connects to a headless instance running inside the Spark SDK Docker environment. This configuration allows us to ship a pre-configured set of [Tasks](https://code.visualstudio.com/Docs/editor/tasks) for building and testing your code.
|
||||
|
||||
Start Visual Studio Code and click on the puzzle piece icon on the left-hand side to open the extensions panel. Use the search box at the top to find the "Remote - Containers" extension and install it.
|
||||
|
||||
<img src="../vscode_remote_containers.png" width="640" />
|
||||
|
||||
## Into the Code
|
||||
|
||||
Open the examples/algo_template directory using the File->Open Folder menu option. If you opted to add it to your path during install, you could run `code .` from your command prompt.
|
||||
|
||||
When the workspace opens, you should see a notification offering to let you re-open the workspace inside the container.
|
||||
|
||||
<img src="../vscode_notification.png" width="480" />
|
||||
|
||||
The first time you open the workspace inside the container it will take a few seconds to configure the spark_sdk docker container. While the workspace is open in Visual Studio Code the docker image will also be running in the background. The container will stop automatically when you close the editor.
|
||||
|
||||
Open algo_template.cpp from the file browser on the left or by pressing ctrl-p and typing the name. Each Algo is a standard C++ class which implements **IAlgoOrder**. Placing your cursor on IAlgoOrder and pressing F12 will take you to the IAlgoOrder.h header file where you can browse the API. To learn more about how an Algo is instantiated see the [Algo Initialization](/server/ref/initialization).
|
||||
|
||||
## Compiling
|
||||
|
||||
To build press "Ctrl+Shift+B". You should see the build output at the bottom of your screen. If the build was successful the resulting algo_template.so will be automatically copied to the plugins directory where it will be loaded by the Spark server process on start.
|
||||
|
||||
<img src="../compiled.png" width="640" />
|
||||
|
||||
## Debugging
|
||||
|
||||
To debug your Algo set a breakpoint (F9) and open the Debug panel (Ctrl+Shift+D) and choose "(gdb) Launch". This will launch the Spark Server inside the container with the debugger attached.
|
||||
|
||||
<img src="../gdb_launch.png" width="380" />
|
||||
|
||||
With sparkd running you can launch the Spark Client (spark_client.cmd) and it will connect to the running server. Log in with the user "bts" and no password and open a ladder. You can now select your Algo on the order type dropdown and activate it by clicking on a price on the ladder.
|
||||
|
||||
<img src="../myalgo.png" width="150" />
|
||||
|
||||
While sparkd is running you can also access the Spark Server Admin interface on [http://localhost:9000](http://localhost:9000). Use the same "bts" login to access the admin interface. Here you can manage user and account permissions and risk limits.
|
||||
|
||||
## Starting Spark Server
|
||||
|
||||
To start the Spark Server without attaching a debugger press "Ctrl+Shift+P" and select "Tasks: Run Task" and then "Start Sparkd" and "Never scan task output" This will open a new terminal where you can see any messages logged with the **Sdk** subsystem as well as any warnings or errors. To stop the Spark server simply kill the terminal (trash can icon) or run the "Stop Sparkd" task.
|
||||
|
||||
## Next Steps
|
||||
|
||||
If you would like to understand more about how Docker is working behind the scenes continue to the [next section](#other-editors). Otherwise, continue to [Algo Registration](/server/ref/registration) to learn how Algos are registered with the Spark.
|
||||
|
||||
<hr>
|
||||
|
||||
# Other Editors
|
||||
|
||||
In this section you will be more exposed to the details of how Docker works and how to compile and test your Algo using the command line.
|
||||
|
||||
## Docker
|
||||
|
||||
Spark Server is built and runs only on Linux so you need a Linux development environment to effectively develop plug-ins for it. Docker lets you take the spark_sdk.img disk image and run it inside a virtual Linux machine running on your Windows desktop.
|
||||
|
||||
First, some terminology. A Docker "image" is a disk image containing a complete filesystem. The spark_sdk.img file you imported earlier is an image. A Docker "container" is what you get when you get when you attach this image to a running Linux system. You can connect to a running container and use it to run commands like the C++ compiler or the Spark server. Directories from your local filesystem can be mapped into the Docker container when it starts so you can easily share files.
|
||||
|
||||
### Start
|
||||
|
||||
The first step is to start the spark_sdk Docker container with the examples directory mapped so we can compile the examples inside the container. In your command prompt cd to the *examples* directory that you unzipped from the spark_sdk.zip file. Then run the following command to start the container.
|
||||
|
||||
Windows Command Prompt:
|
||||
{% clipable() %}
|
||||
docker run --rm -it -v %CD%:/examples -u bts -w /examples --shm-size 1024m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
{% end %}
|
||||
|
||||
**Powershell** / Linux / Mac:
|
||||
{% clipable() %}
|
||||
docker run --rm -it -v ${PWD}:/examples -u bts -w /examples --shm-size 1024m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
{% end %}
|
||||
|
||||
### Attach
|
||||
|
||||
The container will remain as long as this prompt is open. You can attach an additional to the running container with the following command:
|
||||
{% clipable() %}
|
||||
docker exec -it spark_sdk bash
|
||||
{% end %}
|
||||
|
||||
## Compiling
|
||||
|
||||
Let's look at the algo_template sample first. All of the samples are laid out similarly with a `build.sh` script for compiling. As long as you are running build.sh inside the spark Docker container, it automatically copies the build output to /sparkbin/plugins/ where it needs to be to get loaded by the Spark server.
|
||||
|
||||
```
|
||||
bts@spark_sdk:/examples$ cd algo_template/
|
||||
bts@spark_sdk:/examples/algo_template$ ./build.sh
|
||||
algo_template.so copied to /sparkbin/plugins/
|
||||
Restart sparkd to see changes.
|
||||
```
|
||||
|
||||
## Starting Spark Server
|
||||
|
||||
To start the Spark server `cd /sparkbin/` and run `./sparkd run`. Press Ctrl+C to stop it.
|
||||
```
|
||||
bts@spark_sdk:/sparkbin$ ./sparkd run
|
||||
2019-10-25 16:21:09.383626|262552152803600|Gen|Warn |1 |main|132 |Run.cpp:53 |dev|
|
||||
------------------------------------ Sparkd launched --------------------------------
|
||||
```
|
||||
|
||||
You will see any Sdk log messages as well as any log messages at or above Warning level from other parts of the system. If you need additional diagnostic information more details logs are stored in /sparkdata/data/logging/log. These logs will be automatically deleted when you stop the Docker container.
|
||||
|
||||
With sparkd running you can launch the Spark Client (spark_client.cmd) and it will connect to the running server. Log in with the user "bts" and no password and open a ladder. You can now select your Algo on the order type dropdown and activate it by clicking on a price on the ladder.
|
||||
|
||||
<img src="../myalgo.png" width="150" />
|
||||
|
||||
While sparkd is running you can also access the Spark Server Admin interface on [http://localhost:9000](http://localhost:9000). Use the same "bts" login to access the admin interface. Here you can manage user and account permissions and risk limits.
|
||||
|
||||
## Debugging
|
||||
|
||||
You can debug your algo using gdb from the bash command prompt.
|
||||
|
||||
```bts@spark_sdk:/sparkbin$ gdb --args ./sparkd run
|
||||
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
|
||||
Copyright (C) 2018 Free Software Foundation, Inc.
|
||||
...
|
||||
Reading symbols from ./sparkd...done.
|
||||
(gdb) break algo_template.cpp:45
|
||||
No source file named algo_template.cpp.
|
||||
Make breakpoint pending on future shared library load? (y or [n]) y
|
||||
Breakpoint 1 (algo_template.cpp:45) pending.
|
||||
(gdb) run
|
||||
Starting program: /sparkbin/sparkd run
|
||||
```
|
||||
|
||||
If your editor supports debugging over the ssh protocol you can start sshd from inside the container.
|
||||
|
||||
## Next
|
||||
|
||||
Continue to [Algo Registration](/server/ref/registration) to learn how Algos are registered with the Spark.
|
||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,79 @@
|
||||
+++
|
||||
title = "Download SDK"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
## Request Access 🔑
|
||||
|
||||
To start using the Server SDK you will need to request access though our sales department. Please email <a href="mailto:info@bluetradesys.com">info@bluetradesys.com</a> or call <a href="tel:919-913-0850">(919) 913-0850</a> and we will provide download credentials.
|
||||
|
||||
## Download
|
||||
|
||||
Download the latest SDK image here: [spark_sdk.zip](https://api.bluetradesys.com/download/spark_sdk.zip)
|
||||
|
||||
Unzip that folder into a working directory. Inside you should see something like this:
|
||||
|
||||
```
|
||||
spark_sdk.img
|
||||
client/
|
||||
spark_client.cmd
|
||||
examples/
|
||||
```
|
||||
|
||||
## Import
|
||||
|
||||
Import the spark_sdk image into docker using the `docker load` command.
|
||||
{% clipable() %}
|
||||
docker load < spark_sdk.img
|
||||
{% end %}
|
||||
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
<strong>If you see this error it means Docker Desktop is not running:</strong>
|
||||
|
||||
|
||||
error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.40/images/json: open //./pipe/docker_engine: The system cannot find the file specified. In the default daemon configuration on Windows, the docker client must be run elevated to connect. This error may also indicate that the docker daemon is not running.
|
||||
</div>
|
||||
|
||||
Confirm that the import worked by running `docker images` which should list the new image.
|
||||
```
|
||||
c:\bts> docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
bluetradingsystems/spark_sdk latest e2be89436e73 4 hours ago 3.89GB
|
||||
```
|
||||
|
||||
<!--
|
||||
## Run
|
||||
|
||||
Start the docker container by running
|
||||
```
|
||||
docker run -d -v %CD%:/work --name spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8000:8000 -p 8001:8001 -p 8008:8008 bluetradingsytems/spark_sdk
|
||||
```
|
||||
<div class="linux">
|
||||
<i class="fa fa-linux fa-fw" aria-hidden="true"></i>
|
||||
If you are using Linux or Mac replace <strong>%CD%</strong> with <strong>$PWD</strong>.
|
||||
</div>
|
||||
|
||||
The first time you invoke `docker run` you may be prompted to "share" your filesystem with Docker. This usually works fine but if you run into trouble, see <a href="">Docker Desktop for Windows tips</a> on avoiding common problems.
|
||||
|
||||
## Success 🎉
|
||||
Docker is now running the spark SDK image. If you are on Windows or Mac it is running inside a virtual Linux machine. You can see that is is running by typing `docker ps` to list running docker containers. If you restart the Docker Desktop application you will need to start the spark_sdk image again using `docker start spark_sdk`.
|
||||
|
||||
You should now be able to access an admin interface at http://localhost:8008/ which will let you stop and start the spark server process.
|
||||
-->
|
||||
|
||||
## Update
|
||||
|
||||
To update your Spark SDK simply download an unzip a new [spark_sdk.zip](https://api.bluetradesys.com/download/spark_sdk.zip) and use `docker load < spark_sdk.img` to overwrite the old image with the new one.
|
||||
|
||||
To preserve the old image so you can go back to it later run `docker tag spark_sdk spark_sdk:your_custom_tag` before loading the new SDK image.
|
||||
|
||||
<!--
|
||||
Then stop, remove and restart spark_sdk.
|
||||
```
|
||||
c:\bts> docker stop spark_sdk
|
||||
c:\bts> docker rm spark_sdk
|
||||
c:\bts> docker run -d -v %cd%:/sdk --name spark_sdk bluetradingsytems/spark_sdk
|
||||
|
||||
``` -->
|
||||
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
@@ -0,0 +1,57 @@
|
||||
+++
|
||||
title = "Install Prerequisites"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
These instructions assume you are using Microsoft Windows but you should be able to follow them on a Linux or Mac with minor modifications.
|
||||
|
||||
{% linux() %}
|
||||
When there is a significant difference on non-Windows platforms it will be called out in a box like this one.
|
||||
{% end %}
|
||||
{% info() %}
|
||||
Tips or solutions to common problems will be highlighted in a box like this.
|
||||
{% end %}
|
||||
|
||||
The minimum hardware requirements for using the SDK are:
|
||||
- **12 GB** RAM (preferably 16 GB or more)
|
||||
- 4 CPU cores
|
||||
- 20 GB free hard disk space
|
||||
|
||||
The Spark SDK uses a tool called Docker to distribute all of the tools you need to build and test your trading algorithm ("Algo"). Once you have Docker installed you can download and import the Spark SDK image and be ready to start developing your Algo in a few minutes.
|
||||
|
||||
## Docker
|
||||
|
||||
Download and install <a href="https://www.docker.com/products/docker-desktop" target="_blank">Docker Desktop for Windows/Mac. <i class="fa fa-fw fa-window-restore" aria-hidden="true"></i></a>
|
||||
|
||||
<div class="linux">
|
||||
<i class="fa fa-linux fa-fw" aria-hidden="true"></i>
|
||||
If you are using Linux follow the <a href="https://docs.docker.com/install/#supported-platforms">official install instructions for Docker for your distribution.</a>
|
||||
</div>
|
||||
|
||||
During the install Docker may ask you to enable virtualization support and reboot.
|
||||
|
||||
Once Docker Desktop is installed and running it will create an icon in your system tray area that looks like the Docker logo.
|
||||

|
||||
|
||||
Right click on the tray icon and choose 'Settings' and choose the 'Advanced' tab.
|
||||
|
||||
Ensure that the docker image has 8192 MB of memory and at least 2 cores. Memory that you assign to the docker image will not be available to Windows/Mac while Docker is running. CPU cores are shared with the host OS. Since you are going to use this image to compile your Algo it makes sense to allow Docker to use all available cores.
|
||||
|
||||
<img src="../docker_config.png" width="640" >
|
||||
|
||||
## Sharing
|
||||
|
||||
You need to give Docker access to the Windows drive where your source code is kept.
|
||||
|
||||
Open Settings -> Shared Drives and check the drive where you intend to keep your Algo source code.
|
||||
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
If you run into trouble with drive sharing, see <a href="https://code.visualstudio.com/docs/remote/troubleshooting#_docker-desktop-for-windows-tips">Docker Desktop for Windows tips</a> on avoiding common problems.
|
||||
</div>
|
||||
|
||||
## Editor
|
||||
|
||||
The Server SDK includes a complete configuration for C++ development with the <a href="https://code.visualstudio.com/" target="_blank">Visual Studio Code <i class="fa fa-fw fa-window-restore" aria-hidden="true"></i></a> editor. If you are a new to C++ development on a Linux system we recommend you give this a try as it's the easiest way to get up and running quickly. Of course, If you have a different editor that you prefer we fully support that as well.
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,44 @@
|
||||
+++
|
||||
title = "Overview"
|
||||
weight = 0
|
||||
+++
|
||||
|
||||
Using the **Spark Server SDK**, users can create new order types ("Algos"). In this case, users implement the new functionality in C++, producing a shared library that is loaded at run time by our Spark Server.
|
||||
|
||||
If you are interested in accessing Spark server remotely, please see the [Spark Client SDK](/client/).
|
||||
|
||||
## Speed
|
||||
|
||||
The Spark platform offers a sub-10 microsecond socket to socket response times for algorithmic trading. With the **Spark Server SDK**, you can create your order types in high-performance C++ and load them into the Spark server.
|
||||
|
||||
Since your order is running inside the Spark Server, there is virtually no overhead allowing your algorithm to take full advantage of the speed Spark offers. Hosting a high-performance language like C++ directly in the Spark server process allows it to respond to the market thousands of times faster than an Algo built using a proprietary drag and drop tool.
|
||||
|
||||
|
||||
## Easy to Get Started
|
||||
|
||||
If you are not actively developing with C++, it can be time-consuming to set up a complete development environment from scratch. That is why we provide a full development environment out of the box with [Docker](https://docker.io) so you can download and try the Server SDK quickly and easily.
|
||||
|
||||
Docker works across Windows, macOS, and Linux, so you don't need to change your development environment to try the Spark API. Docker also ensures that all of the dependencies you need are packaged together in one image, so you know it will work out of the box, no matter the platform.
|
||||
|
||||
## Implementation
|
||||
|
||||
Creating a new Algo begins with implementing a standard C++ class derived from IAlgoOrder. Start from the [algo_template](/server/samples/template) example to get going quickly.
|
||||
|
||||
The first call your new Algo receives is [do_before_launch](/server/ref/initialization#before-launch), which is an opportunity to validate parameters and allocate any long-lived resources needed during execution. Next, it receives either [do_launch](/server/ref/initialization#launch-launch-attached) in the case of a new order or [do_launch_attached](/server/ref/initialization#launch-launch-attached) if the order is attaching to an existing order.
|
||||
|
||||
The Algo receives a reference to [IAlgoServices](/server/ref/subscriptions/), which is how it calls out to the Spark system. It will use [IAlgoServices](/server/ref/subscriptions/) to subscribe to market events, launch and control child orders, and communicate its state to the larger Spark system.
|
||||
|
||||
## Fully Integrated
|
||||
|
||||
When a Spark Server plugin is loaded, it registers its exposed Algos with the Spark Server though the [init_spark_plugin](/server/ref/registration) callback. This callback allows the Algo to describe the parameters it expects so they can be automatically entered in the [Spark Client](https://www.bluetradingsystems.com/bts-spark/).
|
||||
|
||||
<img src="../myalgo.png" width="150" />
|
||||
<img src="../paramsui.png" width="460" />
|
||||
|
||||
Compose your Algo with out of the box Algos in the [Spark Algo Studio](https://www.bluetradingsystems.com/news/155-bts-algo-studio-provides-algo-library-with-customization-functionality) to create new functionality on the fly.
|
||||
|
||||
<img src="../algostudio.png" width="548" /><br />
|
||||
<img src="../iced_algo.png" width="460" />
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 93 KiB |
@@ -0,0 +1,7 @@
|
||||
+++
|
||||
title="Reference"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
+++
|
||||
title = "Questions"
|
||||
weight = 40
|
||||
+++
|
||||
|
||||
## I need to make a call that might take a few seconds. What is the best way to do this?
|
||||
|
||||
Create a background thread in [do_before_launch](/server/ref/initialization#before-launch) or [do_launch](/server/ref/initialization#launch-launch-attached) to do the work. Use [on_timer](/server/ref/subscriptions#timer) to poll until the result is available.
|
||||
@@ -0,0 +1,97 @@
|
||||
+++
|
||||
title = "Initialization"
|
||||
weight = 10
|
||||
+++
|
||||
|
||||
Each Algo derives from IAlgoOrder and overrides virtual methods to receive callbacks from the Spark system.
|
||||
|
||||
|
||||
## Before Launch
|
||||
|
||||
When an order is activated by a user or by another Algo, the first call it receives is do_before_launch. This call happens outside of the latency-sensitive path, so it's the place to validate parameters and set up data structures that the Algo might need.
|
||||
|
||||
This call returns an OrderOpResult, which allows it to fail and return an error message to its creator. If a user created this order, this message is displayed in the [Activity Log](https://www.bluetradingsystems.com/docs/bts_spark/Help/#activity-log) and across the bottom of the ladder.
|
||||
|
||||
Every SDK Algo is a "Dynamic" Algo since it's parameters are defined during [registration](/server/ref/registration/). Use the `get_if<T>` helper to retrieve a named parameter from the `order_params` collection as a `std::option<T>`. If a parameter has a valid default value you can use `std::option::value_or(...)` to set default values for missing parameters. If a parameter *must* exist or must be within a certain valid range, return an error to the caller.
|
||||
|
||||
```cpp
|
||||
OrderOpResult MyAlgo::do_before_launch(IBeforeLaunchAlgoServices& serv) {
|
||||
auto& def = serv.definition();
|
||||
auto& params = def.custom_def().dynamic().order_params();
|
||||
auto mqi = get_if<uint32_t>(params, "Min Quantity");
|
||||
if (!mqi || mqi.value() == 0)
|
||||
return OrderOpResult{
|
||||
ORDER_ERROR_INVALID_ORDER_DEFINITON,
|
||||
"Min Quantity parameter must be defined and non zero"};
|
||||
}
|
||||
```
|
||||
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
The parameters are defined during <a href="/server/ref/registration/">registration</a> by the <a href="/server/ref/registration/#dynamicorderdescriptor">DynamicOrderDescriptor</a>.
|
||||
</div>
|
||||
|
||||
### IBeforeLaunchAlgoServices
|
||||
|
||||
IBeforeLaunchAlgoServices is passed to `do_before_launch` and allows the Algo to query its environment.
|
||||
|
||||
```cpp
|
||||
class IBeforeLaunchAlgoServices {
|
||||
public:
|
||||
// The OrderDefinition for this algo
|
||||
virtual api::orderman::OrderDefinition const &definition() const = 0;
|
||||
|
||||
// The security this algo is paramterized with (also available from
|
||||
// definition())
|
||||
virtual api::Security const &security() const = 0;
|
||||
|
||||
// The unique order key for this algo
|
||||
virtual OrderKey order_key() const = 0;
|
||||
|
||||
// The parent key for this algo's parent or null_order_key() if the
|
||||
// algo does not have a parent
|
||||
virtual OrderKey parent_order_key() const = 0;
|
||||
|
||||
// The security identified by k or nullptr if no such security exists
|
||||
virtual api::Security const *lookup_security(SecurityKey k) const = 0;
|
||||
|
||||
// Wheter this algo is being attached to an existing order.
|
||||
// If an algo does not support attaching it can return an error
|
||||
// if is_attaching() == true from do_before_launch.
|
||||
virtual bool is_attaching() const = 0;
|
||||
|
||||
// Build a child template that can later can be used to quickly
|
||||
// launch child orders. The reserve parameter indicates how many
|
||||
// orders should be created ahead of time, before they are
|
||||
// launched. This is usefull to remove the relatively expensive
|
||||
// order creation operation to a background thread.
|
||||
virtual ChildTemplate &
|
||||
make_child_template(api::orderman::OrderDefinition const &child_def,
|
||||
size_t reserve = 1) = 0;
|
||||
|
||||
// Get the current time
|
||||
virtual TimeStamp get_time() const;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Launch / Launch Attached
|
||||
|
||||
Depending on the state of `is_attaching()` the next call will either be `do_launch` or `do_launch_attached`. The only difference is that in `do_launch_attached` the existing OrderKey and ChildState are provided.
|
||||
|
||||
`do_launch` and `do_launch_attached` are in the latency-sensitive path and should not do more work than needed. These functions get a reference to `IAlgoServices`, which is a superset of the functionality available on `IBeforeLaunchAlgoServices`. Most Algos should store a pointer or reference to this object for managing child orders (it has a stable address).
|
||||
|
||||
Now is the time to use `IAlgoServices` to subscribe to market events that are relevant to your Algo and create any child orders that need to exist at the start of your Algo. You can safely call IAlgoServices from any `IAlgoOrder` callback.
|
||||
|
||||
To learn more about subscribing to events see [subscriptions](/server/ref/subscriptions).
|
||||
|
||||
### Things to do in the launch callback
|
||||
☑ Store a reference to IAlgoServices
|
||||
☑ Subscribe to relevant market events
|
||||
☑ Launch a child order?
|
||||
|
||||
|
||||
## Update
|
||||
|
||||
If the user or parent Algo changes the order parameters (price, size, or the custom parameters defined during registration), Spark calls the `do_update` callback with the new parameters. It is up to the Algo to decide how to apply the new parameters to update the running state. It can do this by canceling, changing, or adding new child orders.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
+++
|
||||
title = "Child Orders"
|
||||
weight = 30
|
||||
+++
|
||||
|
||||
Every Algo must, at some point, launch a child order to be useful. Each child order can be another Algo or regular exchange order. When the child order is updated or filled, those changes are also sent to the parent.
|
||||
|
||||
|
||||
# Templates
|
||||
|
||||
Since launching a child order is one of the most latency-sensitive things an Algo can do, the Server SDK enables you to create a child template. An order template prepares an order based on a known OrderDefinition and gets it ready to send on a background thread. When the Algo determines it is time to send the order, it only needs to provide the price and quantity to complete and send the order. If an Algo expects to send multiple child orders, it can reserve more than one. Preparing a child template is typically done in [do_before_launch](http://127.0.0.1:1111/server/ref/initialization/#before-launch) callback.
|
||||
|
||||
```cpp
|
||||
virtual ChildTemplate&
|
||||
IBeforeLaunchAlgoServices::make_child_template(
|
||||
api::orderman::OrderDefinition const &child_def,
|
||||
size_t reserve = 1);
|
||||
```
|
||||
|
||||
# Launch
|
||||
|
||||
IAlgoServices provides two overloads for launching child orders. By default, launching a child order automatically subscribes the Algo to its state changes.
|
||||
|
||||
Launch a child order from a template if you have one prepared. This is the fastest option.
|
||||
```cpp
|
||||
OrderKey IAlgoServices::launch_child(ChildTemplate&, Price, Quantity, bool subscribe = true);
|
||||
```
|
||||
Launch a child order by constructing an OrderDefinition as needed.
|
||||
```cpp
|
||||
OrderKey launch_child(OrderDefinition const &, Price, Quantity, bool subscribe = true);
|
||||
```
|
||||
|
||||
When a child is launched its `OrderKey` is returned to the caller. This ID can be used to update, cancel, or detach the child order.
|
||||
|
||||
## Update
|
||||
|
||||
To update an existing child order, call update_child and pass an Update. The Update type lets you change the price, size or the entire OrderDefinition. You only need to specify the fields that you intend to update so only set the price() field if that is what you are changing.
|
||||
|
||||
```cpp
|
||||
void IAlgoServices::update_child(OrderKey child, api::orderman::Update const &);
|
||||
```
|
||||
|
||||
## Cancel
|
||||
|
||||
To cancel an existing child order call `cancel_child`. When the order is canceled you will receive an update though the `on_child_change` callback.
|
||||
|
||||
```cpp
|
||||
void IAlgoServices::cancel_child(OrderKey child);
|
||||
```
|
||||
|
||||
## Detach
|
||||
|
||||
The lifetime of a child order is tied to its parent. If a parent is canceled or completed child orders are automatically stopped as well. If the Algo needs to launch an order that will continue after the Algo ends use `detach_child` to sever the connection. This order hierarchy can be viewed in the Spark [Order Book](https://www.bluetradingsystems.com/docs/bts_spark/Trading/#order-book).
|
||||
|
||||
```cpp
|
||||
void IAlgoServices::detach_child(OrderKey child);
|
||||
```
|
||||
To detach all children at once use `detach_all_children`.
|
||||
|
||||
```cpp
|
||||
void IAlgoServices::detach_all_children();
|
||||
```
|
||||
|
||||
# Child Updated
|
||||
|
||||
When a child order is updated the parent receives the `on_child_change` callback with an updated ChildState object.
|
||||
|
||||
```cpp
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
|
||||
struct ChildState {
|
||||
Price price;
|
||||
Quantity quantity;
|
||||
Quantity total_fills;
|
||||
Price avg_price;
|
||||
Quantity last_fills;
|
||||
Price last_price;
|
||||
ChildStatus status;
|
||||
Quantity fills_at_attach_time;
|
||||
};
|
||||
```
|
||||
|
||||
<!--
|
||||
To talk about:
|
||||
- updating fills (update_fill_state)
|
||||
- handling child finished (unexpected cancel)
|
||||
-->
|
||||
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,92 @@
|
||||
+++
|
||||
title = "Registration"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
When Spark Server starts, it loads all plugins listed in the /sparkbin/plugins directory and calls an exported function named `init_spark_plugin` which must have the following signature.
|
||||
|
||||
```cpp
|
||||
extern "C" void init_spark_plugin(bts::SparkServices &serv) { }
|
||||
```
|
||||
|
||||
The `init_spark_plugin` function registers the Algo with a DynamicOrderDescriptor. Once registered, the Algo appears in the [Spark ladder](https://www.bluetradingsystems.com/docs/bts_spark/Trading/#ladder) and the [Algo Studio](https://www.bluetradingsystems.com/docs/bts_spark/Trading/#algo-studio).
|
||||
|
||||
To register an Algo call `register_dynamic_algo_order` on the provided bts::SparkServices instance. This function takes the DynamicOrderDescriptor and a function that is called to construct the IAlgoOrder. A single init_spark_plugin can call register_dynamic_algo_order multiple times to register multiple Algos.
|
||||
|
||||
The DynamicOrderDescriptor must have a dynamic_order_type (name), or the registration will fail.
|
||||
|
||||
```cpp
|
||||
DynamicOrderDescriptor desc;
|
||||
desc.set_dynamic_order_type("My Algo");
|
||||
auto result =
|
||||
serv.register_dynamic_algo_order(
|
||||
desc, []() { return std::make_unique<MyAlgo>(); });
|
||||
if (result != bts::AlgoRegistrationResult::SUCCESS) {
|
||||
// Algo failed to register
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## DynamicOrderDescriptor
|
||||
|
||||
The DynamicOrderDescriptor describes the type of parameters that the Algo accepts. These inputs can come either from the user manually entering them or from a parent Algo. Spark Algos are composable, so one Algo can activate another. See [Child Orders](/server/ref/orders) for more on how to activate custom order types as child orders.
|
||||
|
||||
Add parameters to the DynamicOrderDescriptor by calling `add_param_desc()` and setting the name and parameter type.
|
||||
|
||||
```cpp
|
||||
*desc.add_param_desc() = param_bool("Enable Feature");
|
||||
*desc.add_param_desc() = param_uint32("Feature Quantity");
|
||||
```
|
||||
|
||||
The above parameters look like this when rendered in the Spark ladder.
|
||||
|
||||
<img src="../paramsui.png" width="460" />
|
||||
|
||||
And like this when viewed in the Spark order book.
|
||||
|
||||
<img src="../orderbook_truncated.png" width="988" />
|
||||
|
||||
Each descriptor can be one of the following types:
|
||||
```
|
||||
// Primitive
|
||||
int32
|
||||
uint32
|
||||
double
|
||||
string
|
||||
bool
|
||||
// Complex
|
||||
Order
|
||||
Array
|
||||
Struct
|
||||
```
|
||||
|
||||
**Currently the Spark UI only renders parameters with primitive types.** The complex types can only be provided by a parent order.
|
||||
|
||||
### Order
|
||||
|
||||
This parameter type indicates that another order type parameterizes this Algo. When the Algo needs to create a child order, it can use this parameter type to create a custom order type instead of defaulting to a limit order.
|
||||
|
||||
For example, an Algo might be designed to wait for a specific market condition before putting in an order. Instead of placing a limit order when the condition is met, the user can specify that it should an IceBerg order. The Algo doesn't need special logic to handle different child order types.
|
||||
|
||||
### Array
|
||||
|
||||
An array parameter is a collection of the same type of parameter. If your Algo needed to behave differently depending on a table of prices, you could send that list in though an array parameter.
|
||||
```cpp
|
||||
*desc.add_param_desc() =
|
||||
param_array("Price Points",
|
||||
param_double("unused").param_type());
|
||||
```
|
||||
|
||||
### Struct
|
||||
|
||||
A struct parameter is a set of named parameter types similar to the entire DynamicOrderDescriptor. The struct parameter type can be combined with the array type to handle tabular data.
|
||||
|
||||
```cpp
|
||||
*desc.add_param_desc() =
|
||||
param_array("Step Table",
|
||||
param_struct("", {
|
||||
param_uint32("Market Quantity"),
|
||||
param_uint32("Order Quantity")
|
||||
}).param_type());
|
||||
```
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
+++
|
||||
title = "Subscriptions"
|
||||
weight = 20
|
||||
+++
|
||||
|
||||
An Algo exists to observe changes in the market and respond to them. When the Algo is [launched](1/server/ref/initialization/#launch-launch-attached) it uses IAlgoServices to subscribe to the events it needs to operate. Each subscribe call is paired with a matching unsubscribe call, which takes the same argument to unsubscribe.
|
||||
|
||||
# Book
|
||||
|
||||
Subscribe to book updates for the specified security call use subscribe_book and unsubscribe book.
|
||||
```cpp
|
||||
// Subscribe to book updates for the specified security
|
||||
void IAlgoServices::subscribe_book(SecurityKey);
|
||||
void IAlgoServices::unsubscribe_book(SecurityKey);
|
||||
|
||||
// You implement
|
||||
virtual void on_book(SecurityKey, Book const &) override;
|
||||
```
|
||||
Get the `SecurityKey` of the current security using:
|
||||
```cpp
|
||||
services.subscribe_book(SecurityKey(algo_services().security().id()));
|
||||
```
|
||||
If your Algo needs to subscribe to other securities, their keys must be passed in as parameters.
|
||||
|
||||
|
||||
To get the details of a security use
|
||||
```cpp
|
||||
api::Security const* IBeforeLaunchAlgoServices::lookup_security(SecurityKey k) const = 0;
|
||||
```
|
||||
|
||||
# Timer
|
||||
To handle time-based logic, an Algo can subscribe to a common timer event that is invoked once every 10 milliseconds. Use the TimeStamp class from `bts_chrono.h` to efficiently handle time calculations.
|
||||
```cpp
|
||||
// Subscribe to a periodic 10ms timer
|
||||
void IAlgoServices::subscribe_timer();
|
||||
// You implement
|
||||
virtual void on_timer() override;
|
||||
```
|
||||
|
||||
# Ticker
|
||||
The status callback is called when the market for this security opens, closes, or has some other state transition.
|
||||
```cpp
|
||||
// Subscribe to trade ticker
|
||||
void IAlgoServices::subscribe_ticker(SecurityKey);
|
||||
// You implement
|
||||
void on_ticker(SecurityKey, TradeVector const&) override;
|
||||
```
|
||||
|
||||
# Trading Status
|
||||
|
||||
The status callback is called when the market for this security opens, closes, or has some other state transition.
|
||||
```cpp
|
||||
// Subscribe to security trading status
|
||||
void IAlgoServices::subscribe_status(SecurityKey);
|
||||
// You implement
|
||||
virtual void on_status(SecurityKey, co2::TradingStatus) override;
|
||||
```
|
||||
|
||||
# Child Orders
|
||||
|
||||
When an Algo launches a child order, it is subscribed to the changes on that child automatically. If the Algo unsubscribes from those changes, it can re-subscribe using this method.
|
||||
|
||||
```cpp
|
||||
// Subscribe to child order updates for the specified child order.
|
||||
void IAlgoServices::subscribe_child_change(OrderKey);
|
||||
// You implement
|
||||
virtual void on_child_change(OrderKey, ChildState const &) override;
|
||||
```
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
+++
|
||||
title="Samples"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
+++
|
||||
title = "Join"
|
||||
weight = 10
|
||||
+++
|
||||
|
||||
|
||||
Join starts working N ticks away from the market (usually) and then watches for the opposite inside to become same side inside market; if there is then enough size on that market, it will join.
|
||||
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
This sample is part of the Server SDK examples included in the <a href="/server/getting-started/download/">spark_sdk.zip download</a>.
|
||||
</div>
|
||||
|
||||
```cpp
|
||||
#include "spark/lib/config/config.h"
|
||||
#include "spark/lib/config/protos/system.pb.h"
|
||||
#include "spark/lib/domain_model/Security.h"
|
||||
#include "spark/lib/orderman/orders/server/IAlgoOrder.h"
|
||||
#include "spark/lib/orderman/orders/server/IAlgoServices.h"
|
||||
#include "spark/lib/plugin/SparkServices.h"
|
||||
|
||||
#include "spark/lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "spark/lib/orderman/orderman_utils/Book.h"
|
||||
#include "spark/lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "spark/lib/utils/PBUtils.h"
|
||||
|
||||
using namespace bts::api::orderman;
|
||||
using namespace bts::orderman;
|
||||
|
||||
namespace bts {
|
||||
namespace order_example {
|
||||
|
||||
// Join starts working N ticks away from the market (usually) and then
|
||||
// watches for the opposite inside to become same side inside market;
|
||||
// if there is then enough size on that market, it will join.
|
||||
class DynamicJoin : public IAlgoOrder {
|
||||
public:
|
||||
DynamicJoin();
|
||||
~DynamicJoin();
|
||||
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
|
||||
private:
|
||||
enum class Status;
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me;
|
||||
|
||||
IAlgoServices &algo_services() const;
|
||||
void try_to_join();
|
||||
};
|
||||
|
||||
// The three stages our algo goes through. We start by waiting to
|
||||
// observe the book for our security (WAITING_FOR_MD). Once we have a
|
||||
// book we use it to derive our target price and we continue to
|
||||
// monitor the book to see if we should join the market
|
||||
// (WAITING_TO_JOIN). Once the size and price constraints are met we
|
||||
// jump to the target price (JOINED). We remain in the JOINED state
|
||||
// until our order is filled or canceled.
|
||||
enum class DynamicJoin::Status { WAITING_FOR_MD, WAITING_TO_JOIN, JOINED };
|
||||
|
||||
// All state information needed for this algo
|
||||
struct DynamicJoin::PImpl {
|
||||
IAlgoServices *algo_services;
|
||||
Quantity min_quantity_to_join;
|
||||
Status status = Status::WAITING_FOR_MD;
|
||||
OrderKey child_key = null_order_key();
|
||||
ChildTemplate *child_template = nullptr;
|
||||
Book book_at_inception;
|
||||
Book last_book;
|
||||
};
|
||||
|
||||
DynamicJoin::DynamicJoin() : me(new PImpl()) {}
|
||||
DynamicJoin::~DynamicJoin() {}
|
||||
IAlgoServices &DynamicJoin::algo_services() const { return *me->algo_services; }
|
||||
|
||||
// This is called outside of the latency sensitive path
|
||||
// once. Expensive initialization should be done here, rather than
|
||||
// in do_launch()
|
||||
OrderOpResult DynamicJoin::do_before_launch(IBeforeLaunchAlgoServices &serv) {
|
||||
auto &def = serv.definition();
|
||||
bts_assert(def.has_custom_def());
|
||||
bts_assert(def.custom_def().has_dynamic());
|
||||
|
||||
// Validate our parameters, returning an error if we find a
|
||||
// problem. If an error is returned here the order will be
|
||||
// abandoned.
|
||||
auto ¶ms = def.custom_def().dynamic().order_params();
|
||||
auto mqi = get_if<uint32_t>(params, "Min Quantity");
|
||||
if (!mqi || mqi.value() == 0) {
|
||||
return OrderOpResult{
|
||||
ORDER_ERROR_INVALID_ORDER_DEFINITON,
|
||||
"Min Quantity parameter must be defined and non zero"};
|
||||
}
|
||||
|
||||
me->min_quantity_to_join = Quantity(mqi.value());
|
||||
|
||||
if (!serv.is_attaching()) {
|
||||
// If we are not attaching, create a child template so we can
|
||||
// quickly launch our child when the time comes.
|
||||
|
||||
// We will be sending a limit order with the same side,
|
||||
// security, trader, account, etc, as this algo order, so just
|
||||
// copy our definition to the child definition to start with.
|
||||
auto child_def = def;
|
||||
// Now add a limit order component to mark our child as a limit
|
||||
// order. Limit orders do not require any extra parameters
|
||||
// beyond those already inherited from this algo order
|
||||
// (i.e. beyond what we obtained through serv.definition()) so
|
||||
// we can just use a default constructed LimitOrderDefinition.
|
||||
*child_def.mutable_custom_def()->mutable_limit() = LimitOrderDefinition{};
|
||||
// Now just build the child template and cache it for future use
|
||||
me->child_template = &(serv.make_child_template(child_def));
|
||||
} else {
|
||||
// If we are attaching to an existing order then we do not need
|
||||
// to spawn a new child, so there is no need to setup a child
|
||||
// template
|
||||
}
|
||||
|
||||
// All is good, return a default constructed OrderOpResult which
|
||||
// indicates success.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Called in the latency sensitive path, once, when the algo is first
|
||||
// launched.
|
||||
void DynamicJoin::do_launch(IAlgoServices &services) {
|
||||
// Cache our algo services
|
||||
me->algo_services = &services;
|
||||
|
||||
LOG(OrderMan, Diag, "Lauching DynamicJoin k: {} ",
|
||||
algo_services().order_key().value());
|
||||
|
||||
// Launch our child order
|
||||
me->child_key =
|
||||
services.launch_child(*me->child_template, algo_services().price(),
|
||||
algo_services().quantity());
|
||||
|
||||
// Subscribe to market data
|
||||
services.subscribe_book(SecurityKey(algo_services().security().id()));
|
||||
}
|
||||
|
||||
// Called in the latency sensitive path, once, when the algo is first
|
||||
// launched. In this case the algo is provided with an existing order
|
||||
// it is now responsible for managing.
|
||||
void DynamicJoin::do_launch_attached(IAlgoServices &services,
|
||||
OrderKey attached_order, ChildState st) {
|
||||
// The order was launched and attached to an existing order, with
|
||||
// key 'attached_order' and current state 'st'
|
||||
|
||||
// Cache our algo services
|
||||
me->algo_services = &services;
|
||||
|
||||
LOG(OrderMan, Diag,
|
||||
"Lauching DynamicJoin k: {} Attached to existing order: {}",
|
||||
algo_services().order_key().value(), attached_order.value());
|
||||
|
||||
// Cache the key of the attached order we will be managing
|
||||
me->child_key = attached_order;
|
||||
|
||||
// Subscribe to market data
|
||||
services.subscribe_book(SecurityKey(services.security().id()));
|
||||
|
||||
// Simulate a child change so we can update our own state to
|
||||
// reflect that of the child order we attached to. From this point
|
||||
// forward there is no distinction between an algo instance that
|
||||
// spawned a new child order and one that attached to an existing
|
||||
// order.
|
||||
on_child_change(attached_order, st);
|
||||
}
|
||||
|
||||
// Called when a human (or a supervising algo) updates a parameter for
|
||||
// this algo. The implementation validates the update and depending on
|
||||
// the validation outcome either applies the update or terminates the
|
||||
// algo.
|
||||
void DynamicJoin::do_update(api::orderman::Update const &update) {
|
||||
LOG(OrderMan, Diag, "DynamicJoin k: {} receiving update: {}",
|
||||
algo_services().order_key().value(), utils::pb::to_string(update));
|
||||
|
||||
// Validate update
|
||||
if (update.quantity() == 0) {
|
||||
// An invalid update has been detected so we terminate the
|
||||
// algo, signaling an error. Any child orders launched by
|
||||
// this algo will also be terminated.
|
||||
algo_services().terminate_error("Invalid update");
|
||||
return;
|
||||
}
|
||||
|
||||
// Updates have two parts, a standard part consisting of price and
|
||||
// size and present in all updates, and an optional part that is
|
||||
// specific to the algo in question. Below we check for the
|
||||
// existance of this optional part and validate if present.
|
||||
bool has_new_min_qty = false;
|
||||
if (update.has_custom()) {
|
||||
auto ¶ms = update.custom().dynamic().order_params();
|
||||
auto mqi = params.find("Min Quantity");
|
||||
|
||||
if (mqi == params.end() || mqi->second.as_uint32() == 0) {
|
||||
algo_services().terminate_error(
|
||||
"Minimum quantity to join must be greater than 0");
|
||||
return;
|
||||
}
|
||||
has_new_min_qty = true;
|
||||
me->min_quantity_to_join = Quantity(mqi->second.as_uint32());
|
||||
}
|
||||
|
||||
// Update is good, forward it to our child
|
||||
Update child_update;
|
||||
child_update.set_price(update.price());
|
||||
child_update.set_quantity(update.quantity());
|
||||
algo_services().update_child(me->child_key, child_update);
|
||||
|
||||
// Try to join again if conditions changed
|
||||
if (me->status == Status::WAITING_TO_JOIN && has_new_min_qty)
|
||||
try_to_join();
|
||||
}
|
||||
|
||||
// Called when the book for the specified security has changed
|
||||
void DynamicJoin::on_book(SecurityKey, Book const &b) {
|
||||
|
||||
// Cache the book, so we can reffer to it in the event of a
|
||||
// parameter change that might allow us to join
|
||||
me->last_book = b;
|
||||
|
||||
LOG(OrderMan, Diag, "DynamicJoin order {} on book. BID: {}@{} ASK: {}@{}",
|
||||
algo_services().order_key().value(), b.side(api::SIDE_BUY)[0].size(),
|
||||
b.side(api::SIDE_BUY)[0].price(), b.side(api::SIDE_SELL)[0].size(),
|
||||
b.side(api::SIDE_SELL)[0].price());
|
||||
|
||||
switch (me->status) {
|
||||
case Status::WAITING_FOR_MD: {
|
||||
|
||||
auto other_side =
|
||||
securities::other_side(algo_services().definition().side());
|
||||
auto &other_side_top = me->last_book.side(other_side)[0];
|
||||
|
||||
if (other_side_top.size() > 0) {
|
||||
// First time we get the opposite side market, we will use
|
||||
// this price as the target price for the join
|
||||
me->status = Status::WAITING_TO_JOIN;
|
||||
me->book_at_inception = b;
|
||||
LOG(OrderMan, Diag,
|
||||
"JoinOrder order {} receiving first book: {} Target: {}",
|
||||
algo_services().order_key().value(),
|
||||
me->min_quantity_to_join.value(), other_side_top.price());
|
||||
try_to_join();
|
||||
}
|
||||
} break;
|
||||
case Status::JOINED:
|
||||
// Nothing to do at this point
|
||||
break;
|
||||
case Status::WAITING_TO_JOIN:
|
||||
// Check if we reached the target and join if so
|
||||
try_to_join();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Given the current book, our target price and our min quantity
|
||||
// parameter, join if appropriate
|
||||
void DynamicJoin::try_to_join() {
|
||||
bts_assert(me->status == Status::WAITING_TO_JOIN);
|
||||
|
||||
auto my_side = algo_services().definition().side();
|
||||
auto other_side = securities::other_side(my_side);
|
||||
auto &my_side_psz = me->last_book.side(my_side)[0];
|
||||
auto &opposite_at_inception_psz = me->book_at_inception.side(other_side)[0];
|
||||
|
||||
if (my_side_psz.size() &&
|
||||
my_side_psz.size() >= me->min_quantity_to_join.value() &&
|
||||
securities::is_equal_or_better(
|
||||
my_side, Price(my_side_psz.price()),
|
||||
Price(opposite_at_inception_psz.price()))) {
|
||||
|
||||
// Time to update our child to join the market
|
||||
Update child_update;
|
||||
child_update.set_price(my_side_psz.price());
|
||||
child_update.set_quantity(algo_services().quantity().value());
|
||||
algo_services().update_child(me->child_key, child_update);
|
||||
me->status = Status::JOINED;
|
||||
|
||||
LOG(OrderMan, Diag, "DynamicJoin order {} joined: {}@{}",
|
||||
algo_services().order_key().value(),
|
||||
algo_services().quantity().value(), my_side_psz.price());
|
||||
|
||||
} else {
|
||||
LOG(OrderMan, Diag,
|
||||
"JoinOrder order {} still waiting to join. Order: {}@{} Top of "
|
||||
"the book: {}@{} Target: {}@{}",
|
||||
algo_services().order_key().value(),
|
||||
algo_services().quantity().value(), algo_services().price().value(),
|
||||
my_side_psz.size(), my_side_psz.price(),
|
||||
me->min_quantity_to_join.value(), opposite_at_inception_psz.price());
|
||||
}
|
||||
}
|
||||
|
||||
// Our child reported a change:
|
||||
void DynamicJoin::on_child_change(OrderKey, ChildState const &st) {
|
||||
// Our fill quantity and price is always a reflection of our
|
||||
// child's state
|
||||
algo_services().update_fill_state(st.total_fills, st.last_price);
|
||||
|
||||
switch (st.status) {
|
||||
case ChildStatus::WORKING:
|
||||
// Child order is still working, nothing to do
|
||||
return;
|
||||
case ChildStatus::COMPLETED:
|
||||
// Child order completed, we should also complete
|
||||
algo_services().complete();
|
||||
return;
|
||||
case ChildStatus::CANCELED:
|
||||
// Child order unexpectedly canceled, signal an error and
|
||||
// terminate
|
||||
algo_services().terminate_error("Unexpected child cancel");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This is called by the spark daemon at startup. In this call we
|
||||
// describe our algo's parameters and register a factory function to
|
||||
// create an instance of the algo.
|
||||
extern "C" void init_spark_plugin(bts::SparkServices &serv) {
|
||||
using namespace bts;
|
||||
|
||||
DynamicOrderDescriptor join_desc;
|
||||
|
||||
// Start by naming our order type
|
||||
join_desc.set_dynamic_order_type("dyn join");
|
||||
|
||||
// Now add a single unsigned int parameter, Min Quantity, that will
|
||||
// determine the minimum quantity that must exist in the book in
|
||||
// order for us to join the market.
|
||||
*join_desc.add_param_desc() = param_uint32("Min Quantity");
|
||||
|
||||
// Associate the descriptor with a builder function and register
|
||||
// that with the Spark server.
|
||||
auto result =
|
||||
serv.register_dynamic_algo_order(
|
||||
join_desc, []() { return std::make_unique<DynamicJoin>(); });
|
||||
|
||||
if (result != bts::AlgoRegistrationResult::SUCCESS) {
|
||||
// The join_desc has errors
|
||||
// Check the error log for more details
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace order_example
|
||||
} // namespace bts
|
||||
|
||||
```
|
||||
@@ -0,0 +1,146 @@
|
||||
+++
|
||||
title = "Template"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
A mostly blank Algo template that can be used as a jump-start for developing a new Algo.
|
||||
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
This sample is part of the Server SDK examples included in the <a href="/server/getting-started/download/">spark_sdk.zip download</a>.
|
||||
</div>
|
||||
|
||||
```cpp
|
||||
#include "spark/lib/config/config.h"
|
||||
#include "spark/lib/config/protos/system.pb.h"
|
||||
#include "spark/lib/domain_model/Security.h"
|
||||
#include "spark/lib/orderman/orders/algo/IAlgoOrder.h"
|
||||
#include "spark/lib/orderman/orders/algo/IAlgoServices.h"
|
||||
#include "spark/lib/plugin/SparkServices.h"
|
||||
|
||||
#include "spark/lib/orderman/order_factory/IOrderFactory.h"
|
||||
#include "spark/lib/orderman/orderman_utils/Book.h"
|
||||
#include "spark/lib/orderman/orderman_utils/orderman_utils.h"
|
||||
#include "spark/lib/utils/PBUtils.h"
|
||||
|
||||
using namespace bts::api::orderman;
|
||||
using namespace bts::orderman;
|
||||
|
||||
namespace bts {
|
||||
namespace order_example {
|
||||
|
||||
// MyAlgo is a blank IAlgoOrder which you can copy when starting a new Algo
|
||||
class MyAlgo : public IAlgoOrder {
|
||||
public:
|
||||
MyAlgo();
|
||||
~MyAlgo();
|
||||
|
||||
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
|
||||
void do_launch(IAlgoServices &) override;
|
||||
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
|
||||
void do_update(api::orderman::Update const &) override;
|
||||
void on_book(SecurityKey, Book const &) override;
|
||||
void on_child_change(OrderKey, ChildState const &) override;
|
||||
|
||||
private:
|
||||
IAlgoServices* algo_services;
|
||||
ChildTemplate *child_template = nullptr;
|
||||
OrderKey child_key = null_order_key();
|
||||
};
|
||||
|
||||
|
||||
MyAlgo::MyAlgo() : algo_services(nullptr) {}
|
||||
MyAlgo::~MyAlgo() {}
|
||||
|
||||
// This is called outside of the latency sensitive path.
|
||||
// Expensive initialization should be done here, rather than in do_launch()
|
||||
OrderOpResult MyAlgo::do_before_launch(IBeforeLaunchAlgoServices &serv) {
|
||||
LOG(Sdk,Info, "MyAlgo: do_before_launch");
|
||||
// If we are not attaching, create a child template so we can
|
||||
// quickly launch our child when the time comes.
|
||||
if (!serv.is_attaching()) {
|
||||
// We will be sending a limit order with the same side,
|
||||
// security, trader, account, etc, as this algo order, so just
|
||||
// copy our definition to the child definition to start with.
|
||||
auto child_def = serv.definition();
|
||||
// Now add a limit order component to mark our child as a limit
|
||||
// order. Limit orders do not require any extra parameters
|
||||
// beyond those already inherited from this algo order
|
||||
// (i.e. beyond what we obtained through serv.definition()) so
|
||||
// we can just use a default constructed LimitOrderDefinition.
|
||||
*child_def.mutable_custom_def()->mutable_limit() = LimitOrderDefinition{};
|
||||
// Now just build the child template and cache it for future use
|
||||
child_template = &(serv.make_child_template(child_def));
|
||||
}
|
||||
// Default constructed result is successful
|
||||
return {};
|
||||
}
|
||||
|
||||
// Called in the latency sensitive path, once, when the algo is first launched.
|
||||
// If the Algo is attaching to an existing order do_launch_attached is called instead.
|
||||
void MyAlgo::do_launch(IAlgoServices &services) {
|
||||
algo_services = &services;
|
||||
// Subscribe to book updates for this security
|
||||
services.subscribe_book(SecurityKey(services.security().id()));
|
||||
// Launch a child limit order
|
||||
child_key =
|
||||
services.launch_child(*child_template, services.price(), services.quantity());
|
||||
LOG(Sdk,Info, "MyAlgo: do_launch");
|
||||
}
|
||||
|
||||
// Called in the latency sensitive path, once, when the algo is first
|
||||
// launched. In this case the algo is provided with an existing order
|
||||
// it is now responsible for managing.
|
||||
void MyAlgo::do_launch_attached(IAlgoServices &services,
|
||||
OrderKey attached_order, ChildState st) {
|
||||
algo_services = &services;
|
||||
// Subscribe to book updates for this security
|
||||
services.subscribe_book(SecurityKey(services.security().id()));
|
||||
// Keep track of the child key
|
||||
child_key = attached_order;
|
||||
LOG(Sdk,Info, "MyAlgo: do_launch_attached");
|
||||
}
|
||||
|
||||
// Called when a human (or a supervising algo) updates a parameter for
|
||||
// this algo. The implementation validates the update and depending on
|
||||
// the validation outcome either applies the update or terminates the algo.
|
||||
void MyAlgo::do_update(api::orderman::Update const &update) {
|
||||
}
|
||||
|
||||
// Called when the book for the specified security has changed
|
||||
void MyAlgo::on_book(SecurityKey, Book const &b) {
|
||||
}
|
||||
|
||||
// Our child reported a change
|
||||
void MyAlgo::on_child_change(OrderKey, ChildState const &st) {
|
||||
}
|
||||
|
||||
// This is called by the spark daemon at startup. In this call we
|
||||
// describe our algo's parameters and register a factory function to
|
||||
// create an instance of the algo.
|
||||
extern "C" void init_spark_plugin(bts::SparkServices &serv) {
|
||||
using namespace bts;
|
||||
|
||||
DynamicOrderDescriptor desc;
|
||||
|
||||
// Start by naming our order type
|
||||
desc.set_dynamic_order_type("My Algo");
|
||||
|
||||
*desc.add_param_desc() = param_bool("Enable Feature");
|
||||
*desc.add_param_desc() = param_uint32("Feature Quantity");
|
||||
|
||||
// Associate the descriptor with a builder function and register
|
||||
// that with the Spark server.
|
||||
auto result =
|
||||
serv.register_dynamic_algo_order(
|
||||
desc, []() { return std::make_unique<MyAlgo>(); });
|
||||
|
||||
if (result != bts::AlgoRegistrationResult::SUCCESS) {
|
||||
// The desc has errors
|
||||
// Check the error log (/sparkdata/data/logging/log) for more details
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace order_example
|
||||
} // namespace bts
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
#/bin/bash
|
||||
|
||||
# ../../externals/linux/bin/zola/zola build
|
||||
zola.exe build
|
||||
|
||||
rsync -avzL public/ bts@api.bluetradesys.com:/data/sdk/public/
|
||||
|
||||
rm -rf ./public/
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
html, body {
|
||||
font-size: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Source Sans Pro", "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
line-height: 1.6;
|
||||
// from http://www.heropatterns.com
|
||||
background-color: #0072a2;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%234b4b4b' fill-opacity='0.4'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||
|
||||
color: $foreground;
|
||||
|
||||
// for sticky footer
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid rgba(0, 124, 188, 0.25);
|
||||
|
||||
&:hover {
|
||||
border-bottom: 1px solid $link-color;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: $link-color;
|
||||
}
|
||||
|
||||
// totally taken from stripe
|
||||
&.button {
|
||||
outline: none;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 14px;
|
||||
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .025em;
|
||||
color: #191919;
|
||||
text-decoration: none;
|
||||
transition: all .15s ease;
|
||||
border-bottom: none;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
text-decoration: none;
|
||||
box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f6f9fc;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
|
||||
&.white {
|
||||
color: white;
|
||||
border-bottom-color: rgba(255, 255, 255, 0.25);
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: white;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
p code, li code {
|
||||
// color: #e6e1dc;
|
||||
// background-color: #383838;
|
||||
color: #191919;
|
||||
background-color: #f5f5f5;
|
||||
white-space: pre-wrap;
|
||||
padding: 3px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85rem;
|
||||
font-family: monospace;
|
||||
// box-shadow: 0 1px 3px rgba(0,0,0,.1), 0 1px 1px rgba(0,0,0,.1), 0 2px 1px -1px rgba(0,0,0,.12);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
.documentation {
|
||||
padding: 3rem;
|
||||
display: flex;
|
||||
|
||||
&__sidebar {
|
||||
margin-right: 2rem;
|
||||
|
||||
ul {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
> ul {
|
||||
> li {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.documentation__sidebar__title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 0.25rem;
|
||||
|
||||
li {
|
||||
border-left: 1px solid $background;
|
||||
padding-left: 0.75rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
li.active a {
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid $link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 60%;
|
||||
// Same as the anchor absolute left to not overlap
|
||||
margin-left: 2rem;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
.zola-anchor {
|
||||
font-size: 1.25rem;
|
||||
visibility: hidden;
|
||||
margin-left: -2rem;
|
||||
text-decoration: none;
|
||||
border-bottom-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.zola-anchor {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 10px solid black;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-left: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.documentation {
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
border-top: 1px solid $background;
|
||||
order: 1;
|
||||
margin-right: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
header {
|
||||
.header__logo {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.header__logo {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
header {
|
||||
padding: 1rem 0;
|
||||
|
||||
nav {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header__logo {
|
||||
// to force menu links to be on a line below
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
.inverted-colours {
|
||||
background: $foreground;
|
||||
color: $background;
|
||||
}
|
||||
|
||||
.hero {
|
||||
width: 60%;
|
||||
margin: 0 auto;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 4rem;
|
||||
text-align: center;
|
||||
|
||||
&__tagline {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.selling-points {
|
||||
padding: 3rem;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.selling-point {
|
||||
// 2 selling points per row on desktop
|
||||
width: 50%;
|
||||
padding: 2rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.selling-header {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
padding-top: 0rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
align-self: center;
|
||||
max-width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
// and 1 on mobile
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.hero {
|
||||
width: 90%;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.selling-points {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.selling-point {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
header {
|
||||
padding: 2rem 3rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.container {
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
|
||||
&--reversed {
|
||||
background: $foreground;
|
||||
color: $background;
|
||||
}
|
||||
}
|
||||
|
||||
.linux {
|
||||
padding: 25px 15px;
|
||||
background: #eff0f1;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #0072a2;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 25px 15px;
|
||||
background: #ffffe0;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #383838;
|
||||
}
|
||||
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem 3rem;
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in
|
||||
* IE on Windows Phone and in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in IE.
|
||||
*/
|
||||
|
||||
figcaption,
|
||||
figure,
|
||||
main { /* 1 */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct margin in IE 8.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Remove the gray background on active links in IE 10.
|
||||
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent; /* 1 */
|
||||
-webkit-text-decoration-skip: objects; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font style in Android 4.3-.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct background and color in IE 9-.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in iOS 4-7.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10-.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overflow in IE.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers (opinionated).
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
|
||||
* controls in Android 4.
|
||||
* 2. Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
html [type="button"], /* 1 */
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct display in IE 9-.
|
||||
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10-.
|
||||
* 2. Remove the padding in IE 10-.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in Edge, IE, and Firefox.
|
||||
*/
|
||||
|
||||
details, /* 1 */
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Scripting
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hidden
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10-.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.search-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 300px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: white;
|
||||
color: black;
|
||||
padding: 1rem;
|
||||
box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.5);
|
||||
max-height: 500px;
|
||||
overflow: auto;
|
||||
width: 150%;
|
||||
right: 0;
|
||||
|
||||
&__items {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 1rem;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 0.9rem;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
a {
|
||||
font-size: 1.2rem;
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
.themes-container {
|
||||
padding: 3rem;
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.themes-container {
|
||||
width: 100%;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.themes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.theme {
|
||||
width: 45%;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: none;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
padding: 10px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.thumb {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07);
|
||||
width: 400px;
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
h1, p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.themes .theme {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.theme-info {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
@charset "utf-8";
|
||||
|
||||
@import "normalize";
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
$background: #191919;
|
||||
$foreground: white;
|
||||
$link-color: #007CBC;
|
||||
|
||||
@import "base";
|
||||
@import "layout";
|
||||
@import "header";
|
||||
@import "index";
|
||||
@import "docs";
|
||||
@import "themes";
|
||||
@import "search";
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 55 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg enable-background="new 0 0 523.438 166.055" version="1.1" viewBox="0 0 523.44 166.06" xmlns="http://www.w3.org/2000/svg"><path d="m297.43 153.29c-18.051-6.698-91.506-40.523-160.87-72.832-69.291-32.28-134.17-62.929-134.17-62.929-2.082-0.983-2.971-3.466-1.987-5.549 0.981-2.079 3.466-2.97 5.548-1.988 0.037 0.02 16.216 7.661 41.09 19.354 24.874 11.695 58.412 27.425 93.046 43.557 69.192 32.242 143.09 66.232 160.25 72.574 20.188 7.443 37.922 12.204 80.113 12.239 30.434 0 73.407-2.559 138.44-8.568 2.288-0.211 4.319 1.474 4.531 3.765 0.208 2.29-1.478 4.324-3.771 4.536-65.152 6.014-108.31 8.602-139.2 8.605h-0.423c-42.393-1e-3 -61.964-5.092-82.59-12.764z" fill="#B3B3B3"/><path d="m45.824 145.9v-138.39h101.28c27.03 0 44.924 12.184 44.924 35.789 0 17.513-8.947 26.27-19.226 29.506 12.564 3.996 21.702 15.039 21.702 31.22 0 25.319-17.516 41.88-44.545 41.88h-104.13zm41.692-60.155v-20.37h59.774c13.707 0 20.179-4.758 20.179-16.751 0-14.658-11.613-16.943-29.507-16.943h-67.96v90.234h70.052c17.325 0 29.889-4.759 29.889-18.276 0-11.423-7.423-17.894-20.939-17.894h-61.488z" fill="#0086BF"/><path d="m149.96 153.4h-104.13v-7.502h7.501-7.501v7.502h-7.504v-153.4h108.78c14.604 0.018 27.473 3.236 37.154 10.583 9.724 7.301 15.384 19.035 15.273 32.71 0.018 10.01-2.623 18.337-7.222 24.554-1.308 1.779-2.755 3.368-4.306 4.774 2.395 1.964 4.557 4.235 6.422 6.792 4.847 6.626 7.594 15.107 7.58 24.606 0.044 14.363-5.105 27.096-14.494 35.896-9.347 8.809-22.486 13.486-37.463 13.486-0.028 1e-3 -0.058 1e-3 -0.088 1e-3zm0-15.007c12.016-0.018 21.114-3.62 27.266-9.402 6.125-5.814 9.736-14.02 9.777-24.973-0.015-6.687-1.835-11.817-4.698-15.757-2.875-3.934-6.891-6.753-11.775-8.315l-22.267-7.085c-0.322 7e-3 -0.645 0.011-0.97 0.012h-52.271v5.367h53.988c7.892 0 15.036 1.876 20.422 6.475 5.429 4.589 8.092 11.651 8.015 18.922 0.016 4.524-1.103 8.88-3.35 12.497-2.236 3.632-5.46 6.38-9.02 8.3-7.172 3.825-15.677 4.938-25.019 4.979h-77.553v-105.24h75.46c6.958 0.014 13.253 0.288 19.035 1.724 5.677 1.383 11.425 4.37 14.807 10.025 2.23 3.702 3.174 8.068 3.171 12.698-5e-3 3.663-0.489 7.129-1.729 10.368-1.045 2.753-2.714 5.301-4.853 7.329l2.154-0.677c3.906-1.243 7.213-3.358 9.68-6.692 2.445-3.351 4.276-8.156 4.297-15.657-0.111-9.936-3.399-16.094-9.377-20.786-6.028-4.645-15.62-7.52-28.046-7.501l-93.773-3e-3v123.39h96.629v-1e-3zm-72.453-23.984h62.551c5.981 0.011 11.112-0.651 14.744-1.85 3.688-1.218 5.575-2.753 6.528-4.306 0.647-1.077 1.099-2.388 1.113-4.618-0.073-4.151-1.124-6.039-2.785-7.541-1.701-1.492-5.034-2.853-10.648-2.853h-61.49v-7.501h7.501-7.501v7.501h-7.504v-35.372h67.277c4.502 0.018 7.602-0.643 9.234-1.431 1.634-0.854 2.056-1.329 2.718-2.838 0.402-1.025 0.728-2.648 0.728-4.979-0.01-2.704-0.518-4.121-1.023-4.967-0.526-0.837-1.154-1.457-2.696-2.209-3.023-1.521-9.667-2.335-18.288-2.268h-60.459v75.232z" fill="#fff"/><path d="M251.22,145.899V31.68h-52.54V7.505h129.256V31.68h-52.539v114.22H251.22z" fill="#0086BF"/><path d="m251.22 153.4v-7.499h7.501-7.501v7.499h-7.504l-2e-3 -114.22h-52.538v-39.175h144.26v39.175h-52.541v114.22h-31.678zm16.673-15.001v-114.22h52.538v-9.171h-114.25v9.171h52.542v114.22h9.171v-1e-3z" fill="#fff"/><path d="m340.11 145.9v-24.176h103.74c12.942 0 20.177-7.233 20.177-18.083 0-11.802-7.234-17.893-20.177-17.893h-63.58c-26.075 0-42.257-16.562-42.257-39.404 0-22.271 15.038-38.831 42.639-38.831h99.365v24.174h-99.365c-11.042 0-17.514 6.473-17.514 16.94 0 10.469 6.661 16.751 17.324 16.751h63.388c27.98 0 43.02 13.134 43.02 39.974 0 23.226-14.089 40.546-43.02 40.546h-103.74z" fill="#383431"/><path d="m443.85 153.4h-103.74v-7.499h7.498-7.498v7.499h-7.505v-39.176h111.25c5.118-0.02 8.116-1.385 9.849-2.984 1.71-1.622 2.774-3.8 2.823-7.597-0.071-4.45-1.162-6.342-2.67-7.727-1.55-1.385-4.646-2.668-10.002-2.668h-63.58c-14.603 0.028-27.235-4.711-36.097-13.218-8.89-8.476-13.698-20.511-13.666-33.685-0.032-12.792 4.428-24.712 13.274-33.177 8.826-8.496 21.605-13.184 36.87-13.155h106.86v39.176h-106.86c-4.11 0.028-6.327 1.125-7.658 2.411-1.313 1.309-2.315 3.29-2.354 7.027 0.043 3.691 1.042 5.592 2.354 6.89 1.338 1.278 3.562 2.338 7.469 2.36h63.388c15.081-7e-3 27.896 3.536 37.065 11.888 9.185 8.367 13.511 20.865 13.455 35.59 0.022 13.078-4.037 25.357-12.824 34.248-8.729 8.882-21.678 13.796-37.497 13.796-0.066 1e-3 -0.135 1e-3 -0.2 1e-3zm-96.246-14.999 96.246-4e-3c13.017-0.037 21.505-3.779 27.018-9.329 5.488-5.576 8.475-13.569 8.494-23.712-0.052-12.118-3.246-19.606-8.572-24.519-5.35-4.923-14.046-7.951-26.939-7.956h-63.388c-6.76 0.022-13.199-2.056-17.858-6.541-4.686-4.459-7.014-10.932-6.972-17.712-0.038-6.729 2.191-13.218 6.88-17.764 4.659-4.564 11.2-6.707 18.14-6.678h91.856v-9.174l-91.856 4e-3c-12.341 0.026-20.88 3.617-26.474 8.971-5.574 5.382-8.632 12.875-8.667 22.358 0.035 9.665 3.317 17.334 9.038 22.85 5.746 5.486 14.244 9.029 25.721 9.049h63.58c7.58 0 14.573 1.767 19.88 6.38 5.34 4.606 7.87 11.665 7.799 19.018 0.048 7.05-2.504 13.916-7.652 18.62-5.117 4.73-12.205 6.982-20.026 6.965l-96.246-6e-3v9.18h-2e-3z" fill="#fff"/><path d="m45.824 145.9v-138.39h101.27c6Line truncated
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,180 @@
|
||||
function debounce(func, wait) {
|
||||
var timeout;
|
||||
|
||||
return function () {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
clearTimeout(timeout);
|
||||
|
||||
timeout = setTimeout(function () {
|
||||
timeout = null;
|
||||
func.apply(context, args);
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Taken from mdbook
|
||||
// The strategy is as follows:
|
||||
// First, assign a value to each word in the document:
|
||||
// Words that correspond to search terms (stemmer aware): 40
|
||||
// Normal words: 2
|
||||
// First word in a sentence: 8
|
||||
// Then use a sliding window with a constant number of words and count the
|
||||
// sum of the values of the words within the window. Then use the window that got the
|
||||
// maximum sum. If there are multiple maximas, then get the last one.
|
||||
// Enclose the terms in <b>.
|
||||
function makeTeaser(body, terms) {
|
||||
var TERM_WEIGHT = 40;
|
||||
var NORMAL_WORD_WEIGHT = 2;
|
||||
var FIRST_WORD_WEIGHT = 8;
|
||||
var TEASER_MAX_WORDS = 30;
|
||||
|
||||
var stemmedTerms = terms.map(function (w) {
|
||||
return elasticlunr.stemmer(w.toLowerCase());
|
||||
});
|
||||
var termFound = false;
|
||||
var index = 0;
|
||||
var weighted = []; // contains elements of ["word", weight, index_in_document]
|
||||
|
||||
// split in sentences, then words
|
||||
var sentences = body.toLowerCase().split(". ");
|
||||
|
||||
for (var i in sentences) {
|
||||
var words = sentences[i].split(" ");
|
||||
var value = FIRST_WORD_WEIGHT;
|
||||
|
||||
for (var j in words) {
|
||||
var word = words[j];
|
||||
|
||||
if (word.length > 0) {
|
||||
for (var k in stemmedTerms) {
|
||||
if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
|
||||
value = TERM_WEIGHT;
|
||||
termFound = true;
|
||||
}
|
||||
}
|
||||
weighted.push([word, value, index]);
|
||||
value = NORMAL_WORD_WEIGHT;
|
||||
}
|
||||
|
||||
index += word.length;
|
||||
index += 1; // ' ' or '.' if last word in sentence
|
||||
}
|
||||
|
||||
index += 1; // because we split at a two-char boundary '. '
|
||||
}
|
||||
|
||||
if (weighted.length === 0) {
|
||||
return body;
|
||||
}
|
||||
|
||||
var windowWeights = [];
|
||||
var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
|
||||
// We add a window with all the weights first
|
||||
var curSum = 0;
|
||||
for (var i = 0; i < windowSize; i++) {
|
||||
curSum += weighted[i][1];
|
||||
}
|
||||
windowWeights.push(curSum);
|
||||
|
||||
for (var i = 0; i < weighted.length - windowSize; i++) {
|
||||
curSum -= weighted[i][1];
|
||||
curSum += weighted[i + windowSize][1];
|
||||
windowWeights.push(curSum);
|
||||
}
|
||||
|
||||
// If we didn't find the term, just pick the first window
|
||||
var maxSumIndex = 0;
|
||||
if (termFound) {
|
||||
var maxFound = 0;
|
||||
// backwards
|
||||
for (var i = windowWeights.length - 1; i >= 0; i--) {
|
||||
if (windowWeights[i] > maxFound) {
|
||||
maxFound = windowWeights[i];
|
||||
maxSumIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var teaser = [];
|
||||
var startIndex = weighted[maxSumIndex][2];
|
||||
for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
|
||||
var word = weighted[i];
|
||||
if (startIndex < word[2]) {
|
||||
// missing text from index to start of `word`
|
||||
teaser.push(body.substring(startIndex, word[2]));
|
||||
startIndex = word[2];
|
||||
}
|
||||
|
||||
// add <em/> around search terms
|
||||
if (word[1] === TERM_WEIGHT) {
|
||||
teaser.push("<b>");
|
||||
}
|
||||
startIndex = word[2] + word[0].length;
|
||||
teaser.push(body.substring(word[2], startIndex));
|
||||
|
||||
if (word[1] === TERM_WEIGHT) {
|
||||
teaser.push("</b>");
|
||||
}
|
||||
}
|
||||
teaser.push("…");
|
||||
return teaser.join("");
|
||||
}
|
||||
|
||||
function formatSearchResultItem(item, terms) {
|
||||
return '<div class="search-results__item">'
|
||||
+ `<a href="${item.ref}">${item.doc.title}</a>`
|
||||
+ `<div>${makeTeaser(item.doc.body, terms)}</div>`
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
function initSearch() {
|
||||
var $searchInput = document.getElementById("search");
|
||||
var $searchResults = document.querySelector(".search-results");
|
||||
var $searchResultsItems = document.querySelector(".search-results__items");
|
||||
var MAX_ITEMS = 10;
|
||||
|
||||
var options = {
|
||||
bool: "AND",
|
||||
fields: {
|
||||
title: {boost: 2},
|
||||
body: {boost: 1},
|
||||
}
|
||||
};
|
||||
var currentTerm = "";
|
||||
var index = elasticlunr.Index.load(window.searchIndex);
|
||||
|
||||
$searchInput.addEventListener("keyup", debounce(function() {
|
||||
var term = $searchInput.value.trim();
|
||||
if (term === currentTerm || !index) {
|
||||
return;
|
||||
}
|
||||
$searchResults.style.display = term === "" ? "none" : "block";
|
||||
$searchResultsItems.innerHTML = "";
|
||||
if (term === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
var results = index.search(term, options);
|
||||
if (results.length === 0) {
|
||||
$searchResults.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
currentTerm = term;
|
||||
for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
|
||||
var item = document.createElement("li");
|
||||
item.innerHTML = formatSearchResultItem(results[i], term.split(" "));
|
||||
$searchResultsItems.appendChild(item);
|
||||
}
|
||||
}, 150));
|
||||
}
|
||||
|
||||
|
||||
if (document.readyState === "complete" ||
|
||||
(document.readyState !== "loading" && !document.documentElement.doScroll)
|
||||
) {
|
||||
initSearch();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", initSearch);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block extra_nav_class %}{% endblock extra_nav_class %}
|
||||
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
|
||||
|
||||
{% block content %}
|
||||
{% set section = get_section(path="client/_index.md") %}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
{% block doc_content %}
|
||||
{% endblock doc_content %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,36 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block extra_nav_class %}{% endblock extra_nav_class %}
|
||||
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
|
||||
|
||||
{% block content %}
|
||||
{%if page.extra.section %}
|
||||
{% set section = get_section(path="{{page.extra.section}}/_index.md") %}
|
||||
{% else %}
|
||||
{% set section = get_section(path="server/_index.md") %}
|
||||
{% endif %}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
{% block doc_content %}
|
||||
{% endblock doc_content %}
|
||||
{% block next_link %} {% endblock next_link %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-gb">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600" rel="stylesheet" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}">
|
||||
<meta name="keywords" content="Futures, Options, Derivatives, Trade, Trading, Professional, options trading, futures trading, Software, API, SDK" />
|
||||
<meta name="rights" content="© Blue Trading Systems" />
|
||||
<meta name="author" content="{{ config.extra.author }}">
|
||||
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
|
||||
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
|
||||
<link rel="icon" href="{{ get_url(path="favicon.ico") }}">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<nav class="{% block extra_nav_class %}container{% endblock extra_nav_class %}">
|
||||
<a class="header__logo white" href="{{ config.base_url }}">
|
||||
<img width="100" src="/images/logo.svg">
|
||||
</a>
|
||||
<a class="white" href="{{ get_url(path="@/server/_index.md") }}" class="nav-link">Server SDK</a>
|
||||
<a class="white" href="{{ get_url(path="@/client/_index.md") }}" class="nav-link">Client SDK</a>
|
||||
<a class="white" href="https://www.bluetradingsystems.com/bts-spark" class="nav-link">About Spark</a>
|
||||
|
||||
<div class="search-container">
|
||||
<input id="search" type="search" placeholder="🔎 Search">
|
||||
|
||||
<div class="search-results">
|
||||
<div class="search-results__items"></div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="content {% block extra_content_class %}{% endblock extra_content_class %}">
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<h1>Spark SDK</h1>
|
||||
<p class="hero__tagline">
|
||||
Spark consists of a client UI
|
||||
(<a class="white" href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
|
||||
a high-performance professional futures front end, connected via our proprietary protocol over TCP to our Spark Server, which in turn connects to execution venues via their native protocols.
|
||||
Use the Spark SDK to extend Spark Server with your custom Algos, then use the Client SDK to launch and control them.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="inverted-colours selling-points">
|
||||
<div class="selling-points__content container">
|
||||
<div class="selling-header">
|
||||
<img src="/images/API.png" width="100%" />
|
||||
</div>
|
||||
<div class="selling-point">
|
||||
<h2>
|
||||
Server SDK
|
||||
<i class="fa fa-tachometer" aria-hidden="true"></i>
|
||||
</h2>
|
||||
<p>
|
||||
Leverage the ultra-low latency Spark C++ API to develop and run your custom Algo. Have your first sub-10 microsecond trading algorithm running in under an hour.
|
||||
</p>
|
||||
<a href="{{ get_url(path="@/server/_index.md") }}" class="button">Get started</a>
|
||||
</div>
|
||||
|
||||
<div class="selling-point disabled">
|
||||
<h2>
|
||||
Client SDK
|
||||
<i class="fa fa-gamepad" aria-hidden="true"></i>
|
||||
</h2>
|
||||
<p>
|
||||
The Spark client SDK allows you to integrate spark with your existing ecosystem. The Spark Client is built entirely on this simple, powerful API.
|
||||
</p>
|
||||
<a href="" class="button" disabled>Get started</a>
|
||||
</div>
|
||||
|
||||
<!-- <div class="selling-point disabled">
|
||||
<h2>
|
||||
Microsoft Excel
|
||||
<i class="fa fa-table" aria-hidden="true"></i>
|
||||
</h2>
|
||||
<p>
|
||||
More of your business is built on excel than probably should be. Continue this worrying trend with the Spark Excel API.
|
||||
</p>
|
||||
<a href="" class="button" disabled>Get started</a>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
</div>
|
||||
<footer>
|
||||
© Blue Trading Systems
|
||||
</footer>
|
||||
|
||||
<script type="text/javascript" src="{{ get_url(path="elasticlunr.min.js") }}"></script>
|
||||
<script type="text/javascript" src="{{ get_url(path="search_index.en.js") }}"></script>
|
||||
<script type="text/javascript" src="{{ get_url(path="search.js") }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
{% extends "documentation.html" %}
|
||||
|
||||
{% block title %}{{ page.title }} | {{ super() }} {% endblock title %}
|
||||
{% block doc_content %}
|
||||
<h1>{{page.title}}</h1>
|
||||
{{page.content | safe}}
|
||||
{% endblock doc_content %}
|
||||
|
||||
{% block next_link %}
|
||||
<p>
|
||||
{% if page.lighter %}
|
||||
<a href="{{ page.lighter.permalink }}">
|
||||
<i class="fa fa-chevron-left fa-fw" aria-hidden="true"></i>
|
||||
Previous
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if page.heavier %}
|
||||
<a href="{{ page.heavier.permalink }}">
|
||||
Next
|
||||
<i class="fa fa-chevron-right fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endblock next_link %}
|
||||
@@ -0,0 +1,15 @@
|
||||
<pre
|
||||
{% if background %}
|
||||
style="background-color:{{background}};"
|
||||
{% else %}
|
||||
style="background-color:#383838;"
|
||||
{% endif %}
|
||||
>
|
||||
<span
|
||||
{% if color %}
|
||||
style="color:{{color}};"
|
||||
{% else %}
|
||||
style="color:#e6e1dc;"
|
||||
{% endif %}
|
||||
><a onclick="navigator.clipboard.writeText('{{body}}')"><i class="fa fa-clipboard fa-underline" style="color: white; text-decoration: none;"></i></a> {{body}}</span>
|
||||
</pre>
|
||||
@@ -0,0 +1,7 @@
|
||||
{% for asset in page.assets -%}
|
||||
{%- if asset is matching("[.](jpg|png)$") -%}
|
||||
<a href="{{ get_url(path=asset) | safe }}" target="_blank">
|
||||
<img src="{{ resize_image(path=asset, width=240, height=180, op="fill", format="auto") | safe }}" />
|
||||
</a>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
{{body}}
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="linux">
|
||||
<i class="fa fa-linux fa-fw" aria-hidden="true"></i>
|
||||
{{body}}
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<img src="{{ resize_image(path=path, width=width, height=height, op=op) }}" />
|
||||
@@ -4,6 +4,17 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.14.0
|
||||
### User Visible Changes
|
||||
- All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder
|
||||
- Added a Max Order Quantity setting to the Safety Checks tab on the Ladder settings. This setting allows for a user-defined maximum order quantity.
|
||||
- Added a Price Display Format setting to the Market Price and Size Display tab on the Ladder settings. This setting allows for price levels in the ladder to be displayed without a decimal separator.
|
||||
- Synthetic instrument creation with 3 legs no longer defaults to butterfly quantites
|
||||
### Bug Fixes
|
||||
- Fixed an issue where market transition were not observed by IAlgoOrder
|
||||
- Fixed a bug in error reporting
|
||||
- Fixed a bug in the PnL window where specifying a certain grouping order would lead to a UI failure
|
||||
|
||||
## 2.13.0
|
||||
### User Visible Changes
|
||||
- Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
2.14.0
|
||||
- Load Algos in libspark.so automatically (no symlink needed)
|
||||
- Specify commands to run when sparkd starts. These will automatically be killed when sparkd terminates if they are still running.
|
||||
|
||||
1.5.7
|
||||
- Fixed Aggressor coloring and ladder high/low coloring in SIM
|
||||
|
||||
|
||||
@@ -23,7 +23,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\sp
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_fix", "src\client\spark_fix\spark_fix.csproj", "{D8031C13-7BEA-430A-BB60-40927D15CFC3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_historical", "src\client\spark_historical\spark_historical.csproj", "{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -219,6 +226,50 @@ Global
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.Build.0 = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
drop table if exists msgs ;
|
||||
create table msgs (
|
||||
MsgID Int4,
|
||||
TradeDate varchar,
|
||||
SeqNum Int4,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
MatchEventIndicator varchar,
|
||||
NoMDEntries Int2,
|
||||
NoOrderIDEntries Int2,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
drop table if exists books ;
|
||||
create table books (
|
||||
MsgID Int4,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
SeqNum Int4,
|
||||
bid_p_0 Real,
|
||||
bid_sz_0 Int4,
|
||||
bid_p_1 Real,
|
||||
bid_sz_1 Int4,
|
||||
bid_p_2 Real,
|
||||
bid_sz_2 Int4,
|
||||
bid_p_3 Real,
|
||||
bid_sz_3 Int4,
|
||||
bid_p_4 Real,
|
||||
bid_sz_4 Int4,
|
||||
bid_p_5 Real,
|
||||
bid_sz_5 Int4,
|
||||
bid_p_6 Real,
|
||||
bid_sz_6 Int4,
|
||||
bid_p_7 Real,
|
||||
bid_sz_7 Int4,
|
||||
bid_p_8 Real,
|
||||
bid_sz_8 Int4,
|
||||
bid_p_9 Real,
|
||||
bid_sz_9 Int4,
|
||||
Symbol Varchar,
|
||||
ask_p_0 Real,
|
||||
ask_sz_0 Int4,
|
||||
ask_p_1 Real,
|
||||
ask_sz_1 Int4,
|
||||
ask_p_2 Real,
|
||||
ask_sz_2 Int4,
|
||||
ask_p_3 Real,
|
||||
ask_sz_3 Int4,
|
||||
ask_p_4 Real,
|
||||
ask_sz_4 Int4,
|
||||
ask_p_5 Real,
|
||||
ask_sz_5 Int4,
|
||||
ask_p_6 Real,
|
||||
ask_sz_6 Int4,
|
||||
ask_p_7 Real,
|
||||
ask_sz_7 Int4,
|
||||
ask_p_8 Real,
|
||||
ask_sz_8 Int4,
|
||||
ask_p_9 Real,
|
||||
ask_sz_9 Int4,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
drop table if exists trades;
|
||||
create table trades (
|
||||
MsgID Int4,
|
||||
Symbol varchar,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
TradeDate varchar,
|
||||
MDEntryPx Real,
|
||||
MDnetrySize Int4,
|
||||
NumberOfOrders Int4,
|
||||
AgressorSide Int4,
|
||||
MDTradeEntryID Int4,
|
||||
AgressorOID varchar,
|
||||
RestingOID varchar,
|
||||
PartialQty Int4,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
drop table if exists orders;
|
||||
create table orders (
|
||||
MsgID Int4,
|
||||
Symbol varchar,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
MDEntryPx Real,
|
||||
OrderID varchar,
|
||||
MDDisplayQty Int4,
|
||||
MDOrderPriority Int8,
|
||||
OrderUpdateAction Int4,
|
||||
Side Int4,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
-- create unique index msg_msg_id on msgs msgid;
|
||||
|
||||
-- create view ranges as (
|
||||
-- select
|
||||
-- sendingtime,
|
||||
-- transacttime,
|
||||
-- bid_p_0 price,
|
||||
-- case when lag(bid_p_0) over (order by SendingTime) = bid_p_0 then null else 1 end seq
|
||||
-- from
|
||||
-- u0v0_books);
|
||||
|
||||
-- create view groups as (
|
||||
-- select
|
||||
-- sendingtime,
|
||||
-- transacttime,
|
||||
-- price,
|
||||
-- sum(seq) over (order by SendingTime) grp
|
||||
-- from
|
||||
-- ranges);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
DB=$1
|
||||
TABLE=$2
|
||||
FILE=$3
|
||||
|
||||
zcat $FILE | psql -U postgres -d $DB -c "copy $TABLE from STDIN with delimiter as ',' CSV HEADER"
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
docker exec -it postgres psql -U postgres "$@"
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
#docker run --net host -e PGADMIN_DEFAULT_EMAIL=bts -e PGADMIN_DEFAULT_PASSWORD=BT5u53r dpage/pgadmin4
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-d \
|
||||
-p 8000:80 \
|
||||
-e PGADMIN_DEFAULT_EMAIL=bts \
|
||||
-e PGADMIN_DEFAULT_PASSWORD=BT5u53r \
|
||||
--name pgadmin \
|
||||
dpage/pgadmin4
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SRC_ROOT=$DIR/../../..
|
||||
DATA_DIR=`readlink -f $SRC_ROOT/analysis_data`
|
||||
DB_DIR=$DATA_DIR/db
|
||||
|
||||
mkdir -p $DB_DIR
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-d \
|
||||
--name postgres \
|
||||
--net host \
|
||||
-v $DB_DIR:/var/lib/postgresql/data \
|
||||
-v /home/$USER:/home/$USER \
|
||||
-v $DATA_DIR:$DATA_DIR \
|
||||
-v /etc/group:/etc/group \
|
||||
-v /run/user/:/run/user/ \
|
||||
-u $UID \
|
||||
-t postgres \
|
||||
postgres
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SRC_ROOT=$DIR/../../
|
||||
DATE=$1
|
||||
DB=md_$DATE
|
||||
|
||||
$DIR/db/psql.sh -c "drop database if exists $DB"
|
||||
$DIR/db/psql.sh -c "create database $DB"
|
||||
$DIR/db/psql.sh -d md_$DATE -f $DIR/db/create_db.sql
|
||||
|
||||
PROCESSED_DATA=$SRC_ROOT/analysis_data/processed/$DATE
|
||||
|
||||
ALL_COMMANDS=""
|
||||
for TABLE in trades orders msgs books
|
||||
do
|
||||
for ITEM in $PROCESSED_DATA/*.$TABLE.gz
|
||||
do
|
||||
ALL_COMMANDS+="docker exec -t postgres /bin/bash -c \""
|
||||
ALL_COMMANDS+="$DIR/db/import_csv.sh $DB $TABLE $ITEM"
|
||||
ALL_COMMANDS+="\"\n"
|
||||
done
|
||||
done
|
||||
|
||||
echo -e $ALL_COMMANDS | parallel --will-cite
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DATE=$1
|
||||
|
||||
SRC_ROOT=$DIR/../../
|
||||
SPARK_BIN_ROOT=`readlink -f $SRC_ROOT/build`/ninja_release/server
|
||||
RAW_DATA=$SRC_ROOT/analysis_data/raw/$DATE
|
||||
PROCESSED_DATA=$SRC_ROOT/analysis_data/processed/$DATE
|
||||
|
||||
mkdir -p $PROCESSED_DATA
|
||||
rm $PROCESSED_DATA/* >/dev/null
|
||||
|
||||
ALL_COMMANDS=""
|
||||
for IN_PATH in $RAW_DATA/*.gz
|
||||
do
|
||||
BASE_NAME=`basename $IN_PATH`
|
||||
BASE_NAME_STEM=${BASE_NAME%.*}
|
||||
for CMD in trades orders msgs books
|
||||
do
|
||||
OUT_PATH=$PROCESSED_DATA/$BASE_NAME_STEM.$CMD.gz
|
||||
ALL_COMMANDS+="(cd $SPARK_BIN_ROOT && zcat $IN_PATH | ./sparkd parsehistoricalfix $CMD | gzip >$OUT_PATH)\n"
|
||||
done
|
||||
done
|
||||
echo -e $ALL_COMMANDS | parallel --will-cite
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
d=$1
|
||||
end=${2-`date +%Y%m%d`}
|
||||
until [[ $d > $end ]]; do
|
||||
echo "$d"
|
||||
d=$(date -d "$d + 1 day" +%Y%m%d)
|
||||
done
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DATE=${1-`date -d "yesterday" +%Y%m%d`}
|
||||
|
||||
$DIR/sync.sh $DATE &&
|
||||
$DIR/parse.sh $DATE &&
|
||||
$DIR/load.sh $DATE
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DATE=$1
|
||||
SRC_ROOT=$DIR/../../
|
||||
RAW_DATA_ROOT=$SRC_ROOT/analysis_data/raw/$DATE
|
||||
mkdir -p $RAW_DATA_ROOT
|
||||
AWS_ACCESS_KEY_ID=AKIAIFIGOOWGBLEMO3PQ AWS_SECRET_ACCESS_KEY=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 AWS_REGION=us-east-1 aws s3 sync s3://cme-bts-datamine/mbo/mbo/$DATE/ $RAW_DATA_ROOT
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(spark_client)
|
||||
include(${CMAKE_SOURCE_DIR}/tools/safe_dep_glob.cmake)
|
||||
|
||||
file(GLOB_RECURSE spark_client_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cs)
|
||||
file(GLOB_RECURSE spark_client_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.csproj)
|
||||
file(GLOB_RECURSE spark_ui_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.cs)
|
||||
file(GLOB_RECURSE spark_ui_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.csproj)
|
||||
list(REMOVE_ITEM spark_ui_sources ${spark_client_sources} )
|
||||
list(REMOVE_ITEM spark_ui_csproj ${spark_client_csproj} )
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/../
|
||||
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh
|
||||
ARGS ${CMAKE_BUILD_TYPE} ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Building sparkcmd in netcore3"
|
||||
DEPENDS libspark ${spark_client_sources} ${spark_client_csproj} ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh)
|
||||
|
||||
add_custom_target(spark_client_dir ALL DEPENDS ${CMAKE_BINARY_DIR}/../)
|
||||
@@ -5,6 +5,7 @@
|
||||
<SparkRoot>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)\..\..))</SparkRoot>
|
||||
<SourceRoot>$([System.IO.Path]::GetFullPath($(SparkRoot)\src))</SourceRoot>
|
||||
<Externals>$(SparkRoot)\externals\windows</Externals>
|
||||
<Externals Condition=" '$(OS)' != 'Windows_NT' ">$(SparkRoot)/externals/linux</Externals>
|
||||
<DepotName>$([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath($(SparkRoot)))))</DepotName>
|
||||
<ErrorReport>None</ErrorReport>
|
||||
<Prefer32Bit>False</Prefer32Bit>
|
||||
@@ -22,17 +23,20 @@
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
||||
|
||||
<ItemGroup>
|
||||
<AvailableItemName Include="Proto2">
|
||||
<Targets>CompileProto3</Targets>
|
||||
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto2</Targets>
|
||||
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto2Linux</Targets>
|
||||
</AvailableItemName>
|
||||
<AvailableItemName Include="Proto3">
|
||||
<Targets>CompileProto3</Targets>
|
||||
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto3</Targets>
|
||||
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto3Linux</Targets>
|
||||
</AvailableItemName>
|
||||
<AvailableItemName Include="Proto3Excel">
|
||||
<Targets>CompileProto3Excel</Targets>
|
||||
<Targets Condition=" '$(OS)' == 'Windows_NT' ">CompileProto3Excel</Targets>
|
||||
</AvailableItemName>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -43,6 +47,14 @@
|
||||
<protogen>$(Externals)\bin\protogen.exe</protogen>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="ProtoBuf" Condition=" '$(OS)' != 'Windows_NT' ">
|
||||
<ProtocPluginBase>$(SparkRoot)/src/lib/api/service_compiler</ProtocPluginBase>
|
||||
<ProtoInclude>$(Externals)/include</ProtoInclude>
|
||||
<protoc>$(Externals)/bin/protoc</protoc>
|
||||
<protoc_rename>$(Externals)/bin/lowercase_files.sh</protoc_rename>
|
||||
<protogen>$(Externals)/bin/protogen</protogen>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<Proto2>
|
||||
</Proto2>
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
<Target Name="CompileProto2"
|
||||
Condition="'@(Proto2)'!=''"
|
||||
Condition="'@(Proto2)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
|
||||
Outputs="$(GenDir)\proto2\%(Proto2.filename).cs"
|
||||
AfterTargets="PreBuildEvent">
|
||||
@@ -28,7 +28,7 @@
|
||||
passes wipes out the additional compile includes that we add.
|
||||
-->
|
||||
<Target Name="CompileProto3" AfterTargets="GetAssemblyVersion"
|
||||
Condition="'@(Proto3)'!=''"
|
||||
Condition="'@(Proto3)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||
Inputs="$(protoc);
|
||||
%(Proto3.Identity);$(ProtocPluginBase)\plugin.py;
|
||||
$(ProtocPluginBase)\templates\c#\client.pb.cs;
|
||||
@@ -58,7 +58,7 @@
|
||||
</Target>
|
||||
|
||||
<Target Name="CompileProto3Excel" AfterTargets="GetAssemblyVersion"
|
||||
Condition="'@(Proto3Excel)'!=''"
|
||||
Condition="'@(Proto3Excel)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||
Inputs="$(protoc);
|
||||
%(Proto3Excel.Identity);$(ProtocPluginBase)\plugin.py;
|
||||
$(ProtocPluginBase)\templates\excel\excel.pb.cs;"
|
||||
@@ -82,12 +82,74 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="CompileProto2Linux"
|
||||
Condition="'@(Proto2)'!='' AND '$(OS)' != 'Windows_NT' "
|
||||
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
|
||||
Outputs="$(GenDir)/proto2/%(Proto2.filename).cs"
|
||||
AfterTargets="PreBuildEvent">
|
||||
<PropertyGroup>
|
||||
<SourceRelativeFile>$([System.String]::Copy('%(proto2.fullpath)').Replace($(SourceRoot), ''))</SourceRelativeFile>
|
||||
<SourceRelativeDir>$([System.IO.Path]::GetDirectoryName($(SourceRelativeFile)))</SourceRelativeDir>
|
||||
<WorkingDirectory>$(GenDir)/proto2</WorkingDirectory>
|
||||
</PropertyGroup>
|
||||
<MakeDir Directories="$(WorkingDirectory)" />
|
||||
<Copy SourceFiles="%(Proto2.FullPath)" DestinationFolder="$(WorkingDirectory)" SkipUnchangedFiles="True" UseHardLinksIfPossible="True" />
|
||||
<Error Text="protogen.exe does not exist. If you forgot to clone with externals try: git submodule update --init --recursive" Condition="!Exists($(protogen))" />
|
||||
<Message Importance="High" Text="Doing Proto2 %(proto2.filename)" />
|
||||
<Exec Command="PYTHONPATH=$(Externals)/python $(Protogen) %(Proto2.filename)%(proto2.extension) --csharp_out=. -I. -I$(SourceRoot) -I$(Externals)/include"
|
||||
WorkingDirectory="$(WorkingDirectory)" />
|
||||
<ItemGroup>
|
||||
<Compile Include="$(WorkingDirectory)/%(Proto2.filename).cs" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="CompileProto3Linux" AfterTargets="GetAssemblyVersion"
|
||||
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' "
|
||||
Inputs="$(protoc);
|
||||
%(Proto3.Identity);$(ProtocPluginBase)/plugin.py;
|
||||
$(ProtocPluginBase)/templates/c#/client.pb.cs;
|
||||
$(ProtocPluginBase)/templates/c#/server.pb.cs;
|
||||
$(ProtocPluginBase)/templates/c#/immutable.pb.cs;"
|
||||
Outputs="%(Proto3.OutCS);%(Proto3.OutCSServer);%(Proto3.OutCSClient);%(Proto3.OutCSImmutable)">
|
||||
<MakeDir Directories="%(Proto3.OutBase)" />
|
||||
<MakeDir Directories="$(IntermediateOutputPath)/gen" />
|
||||
|
||||
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
|
||||
<Exec Command="
|
||||
PYTHONPATH=$(Externals)/python $(protoc) \
|
||||
%(Proto3.FullPath) \
|
||||
--error_format=gcc \
|
||||
--proto_path=$(protoinc):$(SourceRoot) \
|
||||
--plugin='protoc-gen-service=$(ProtocPluginBase)/plugin.py' \
|
||||
--service_out=lang=c#,strip_out_path=1:%(Proto3.OutBase) \
|
||||
--csharp_out=%(CompileOptions)%(Proto3.OutBase)"
|
||||
WorkingDirectory="%(relativedir)" />
|
||||
|
||||
<!-- <Exec Command="$(protoc_rename) %(Proto3.OutBase)" /> -->
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="%(Proto3.OutCS)" />
|
||||
<Compile Include="%(Proto3.OutCSClient)" />
|
||||
<Compile Include="%(Proto3.OutCSServer)" />
|
||||
<Compile Include="%(Proto3.OutCSImmutable)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- <Target Name="RenameProto3Linux" AfterTargets="CompileProto3Linux"
|
||||
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' ">
|
||||
<Move SourceFiles="$(OutBase)\Apicore.cs" DestinationFiles="$(OutBase)\apicore.cs" />
|
||||
<Move SourceFiles="$(OutBase)\Apioptions.cs" DestinationFiles="$(OutBase)\apioptions.cs" />
|
||||
<Move SourceFiles="$(OutBase)\Co2.cs" DestinationFiles="$(OutBase)\co2.cs" />
|
||||
</Target> -->
|
||||
|
||||
|
||||
<Target Name="AfterClean" AfterTargets="Clean">
|
||||
<Delete Files="%(Proto3.OutCS)" />
|
||||
<Delete Files="%(Proto3.OutCSClient)" />
|
||||
<Delete Files="%(Proto3.OutCSServer)" />
|
||||
<Delete Files="%(Proto3.OutCSImmutable)" />
|
||||
<Delete Files="$(GenDir)\proto2\*" />
|
||||
<Delete Files="$(GenDir)/proto2/*" />
|
||||
<Delete Files="%(Proto3Excel.OutCSExcel)" />
|
||||
</Target>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
#$1 = Debug / Release (CMAKE_BUILD_TYPE), $2 = CMAKE_BINARY_DIR
|
||||
cd "$(dirname "$0")"
|
||||
echo $PWD dotnet build -c $1 ./sparkcmd/
|
||||
dotnet build -c $1 ./sparkcmd/
|
||||
|
||||
if [ "$1" == "Release" ]
|
||||
then
|
||||
dotnet publish -c $1 ./sparkcmd/
|
||||
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64/publish
|
||||
fi
|
||||
|
||||
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64
|
||||
@@ -136,8 +136,11 @@ namespace mbts.ui.shell
|
||||
try {
|
||||
if (argv.Contains("-multiple") || _mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
|
||||
{
|
||||
if(_mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
|
||||
init_stunnel(argv);
|
||||
if(_mutex == null || _mutex.WaitOne(TimeSpan.Zero, true)) {
|
||||
if (!argv.Contains("-nostunnel")) {
|
||||
init_stunnel(argv);
|
||||
}
|
||||
}
|
||||
return base.Run();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.collections;
|
||||
using bts.utils.csv;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
@@ -52,7 +53,7 @@ namespace mbts.spark {
|
||||
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
|
||||
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
|
||||
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
|
||||
// if the collection is not populated, the column settings will not be applied
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||
@@ -173,6 +174,19 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(TimeAndSalesVM.Row),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(time),
|
||||
nameof(price),
|
||||
nameof(size),
|
||||
nameof(aggressor_side),
|
||||
nameof(security_key),
|
||||
});
|
||||
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils.csv;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
@@ -63,6 +64,7 @@ namespace mbts.spark {
|
||||
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
_commands = new SparkCommand[] {
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.value.row_vms.ToCsv(TimeAndSalesVM.Row.csv_ordered_properties), "TaS")),
|
||||
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||
|
||||
@@ -357,19 +357,13 @@ namespace mbts.spark {
|
||||
if (leg_tuples.Count == 2) {
|
||||
_is_setting_price_mults = true;
|
||||
_price_mult_model.override_value(leg_tuples[1], -1);
|
||||
} else if (leg_tuples.Count == 3) {
|
||||
_is_setting_price_mults = true;
|
||||
_price_mult_model.override_value(leg_tuples[1], -2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _deduce_quantities() {
|
||||
if (leg_tuples.Count == 2) {
|
||||
_is_setting_quantities = true;
|
||||
_quantity_model.override_value(leg_tuples[1], -1);
|
||||
} else if (leg_tuples.Count == 3) {
|
||||
_is_setting_quantities = true;
|
||||
_quantity_model.override_value(leg_tuples[1], -2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
@@ -23,6 +24,7 @@ using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
using static bts.shell.FillsWindowFactory;
|
||||
@@ -298,6 +300,7 @@ namespace mbts.spark {
|
||||
public FillStatus fill_status => _fill.FillStatus;
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
public string symbol => fill_tuple.security?.Symbol;
|
||||
|
||||
// Root Algo
|
||||
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
|
||||
@@ -309,6 +312,28 @@ namespace mbts.spark {
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(typeof(Row),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(symbol),
|
||||
nameof(order_type),
|
||||
nameof(root_algo_display_name),
|
||||
nameof(side),
|
||||
nameof(fill_time),
|
||||
nameof(user),
|
||||
nameof(account),
|
||||
nameof(exchange),
|
||||
nameof(time_in_force),
|
||||
nameof(size),
|
||||
nameof(price),
|
||||
nameof(fill_kind),
|
||||
nameof(fill_status),
|
||||
nameof(fill_key),
|
||||
nameof(order_key),
|
||||
nameof(security_key)
|
||||
});
|
||||
|
||||
public event Action invalidated;
|
||||
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils.csv;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
@@ -59,6 +60,7 @@ namespace mbts.spark {
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_commands = new List<SparkCommand> {
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
|
||||
@@ -249,6 +249,7 @@ namespace mbts.spark {
|
||||
const double _default_order_entry_panel_width = 110.0;
|
||||
const double _default_zoom_factor = 1.0;
|
||||
const bool _default_show_warn_on_cross = true;
|
||||
const bool _default_enforce_max_clip = false;
|
||||
public static readonly IEqualityComparer<LadderSettings> profile_equality_comparer =
|
||||
EqualityComparer.Create<LadderSettings>(profile_equals);
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
@@ -282,6 +283,7 @@ namespace mbts.spark {
|
||||
use_defaults = true;
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
|
||||
show_warn_on_cross = _default_show_warn_on_cross;
|
||||
enforce_max_clip = _default_enforce_max_clip;
|
||||
}
|
||||
|
||||
public static LadderSettings Default {
|
||||
@@ -347,6 +349,8 @@ namespace mbts.spark {
|
||||
zoom_factor = _default_zoom_factor,
|
||||
net_change = NetChangeTerms.None,
|
||||
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
|
||||
max_clip = 0,
|
||||
price_display_format = PriceDisplayFormat.Default,
|
||||
};
|
||||
rv.row_height = LadderRender.measure_row_height(rv);
|
||||
return rv;
|
||||
@@ -365,6 +369,13 @@ namespace mbts.spark {
|
||||
Ticks
|
||||
}
|
||||
|
||||
public enum PriceDisplayFormat {
|
||||
[Description("Default")]
|
||||
Default,
|
||||
[Description("Integer")]
|
||||
Integer
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public double row_height { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -499,6 +510,15 @@ namespace mbts.spark {
|
||||
[ProtoMember(57)]
|
||||
public double left_panel_scroll_position { get; set; }
|
||||
|
||||
[ProtoMember(58), DefaultValue(_default_enforce_max_clip)]
|
||||
public bool enforce_max_clip { get; set; }
|
||||
|
||||
[ProtoMember(59)]
|
||||
public uint max_clip { get; set; }
|
||||
|
||||
[ProtoMember(60)]
|
||||
public PriceDisplayFormat price_display_format { get; set; }
|
||||
|
||||
public void sanitize() =>
|
||||
sanitize(this);
|
||||
|
||||
@@ -600,7 +620,10 @@ namespace mbts.spark {
|
||||
a.favorite_algos == b.favorite_algos &&
|
||||
a.net_change == b.net_change &&
|
||||
a.show_warn_on_cross == b.show_warn_on_cross &&
|
||||
a.mini_ob == b.mini_ob;
|
||||
a.mini_ob == b.mini_ob &&
|
||||
a.enforce_max_clip == b.enforce_max_clip &&
|
||||
a.max_clip == b.max_clip &&
|
||||
a.price_display_format == b.price_display_format;
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
profile_equals(this, other) &&
|
||||
|
||||
@@ -29,7 +29,7 @@ using TextUtils = mbts.ui.utility.text.TextUtils;
|
||||
namespace mbts.spark {
|
||||
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
|
||||
readonly Property<LadderSettings.BadgeStyle> _badge_style;
|
||||
readonly Property<uint> _alternate_order_size;
|
||||
readonly Property<uint> _alternate_order_size, _max_clip;
|
||||
readonly DelegateProperty<double> _row_height;
|
||||
readonly UserProperty<AccountTuple?> _default_account;
|
||||
readonly UserProperty<AlgoDefinitionVM> _default_algo;
|
||||
@@ -41,8 +41,9 @@ namespace mbts.spark {
|
||||
AlgoID? _desired_algo;
|
||||
TimeInForce _default_time_in_force;
|
||||
LadderSettings.NetChangeTerms _price_format;
|
||||
LadderSettings.PriceDisplayFormat _price_display_format;
|
||||
double _order_entry_panel_width, _zoom_factor;
|
||||
bool _auto_clear, _show_warn_on_cross;
|
||||
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
@@ -91,6 +92,7 @@ namespace mbts.spark {
|
||||
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
|
||||
_default_account = user_backing<AccountTuple?>(nameof(default_account));
|
||||
_default_algo = user_backing<AlgoDefinitionVM>(nameof(default_algo));
|
||||
_max_clip = backing<uint>(nameof(max_clip));
|
||||
// be sure to initialize backings before this measure_row_height call
|
||||
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
|
||||
_orders.font_size_changed += on_font_size_changed;
|
||||
@@ -166,7 +168,9 @@ namespace mbts.spark {
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
// price format
|
||||
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
|
||||
public LadderSettings.PriceDisplayFormat price_display_format { get => _price_display_format; set => Set(ref _price_display_format, value); }
|
||||
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
|
||||
public IEnumerable<LadderSettings.PriceDisplayFormat> price_display_format_options => EnumHelper.get_values<LadderSettings.PriceDisplayFormat>();
|
||||
// time in force
|
||||
public TimeInForce default_time_in_force { get => _default_time_in_force; set => Set(ref _default_time_in_force, value); }
|
||||
public IEnumerable<TimeInForce> time_in_force_options => EnumHelper.get_values_except(TimeInForce.Undefined);
|
||||
@@ -182,6 +186,8 @@ namespace mbts.spark {
|
||||
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
|
||||
// safety checks
|
||||
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
|
||||
public bool enforce_max_clip { get => _enforce_max_clip; set => Set(ref _enforce_max_clip, value); }
|
||||
public uint max_clip { get => _max_clip.Value; set => _max_clip.Value = value; }
|
||||
|
||||
// favorites
|
||||
public FavoriteAlgosVM favorite_algos_vm { get; }
|
||||
@@ -354,8 +360,11 @@ namespace mbts.spark {
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
price_format = settings.net_change;
|
||||
price_display_format = settings.price_display_format;
|
||||
// safety checks
|
||||
show_warn_on_cross = settings.show_warn_on_cross;
|
||||
enforce_max_clip = settings.enforce_max_clip;
|
||||
max_clip = settings.max_clip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,8 +437,11 @@ namespace mbts.spark {
|
||||
clone.cancel_all_visible = _cancel_all_visibility.show;
|
||||
clone.zoom_slider_visible = _zoom_slider_visbility.show;
|
||||
clone.net_change = price_format;
|
||||
clone.price_display_format = price_display_format;
|
||||
// safety checks
|
||||
clone.show_warn_on_cross = show_warn_on_cross;
|
||||
clone.enforce_max_clip = enforce_max_clip;
|
||||
clone.max_clip = max_clip;
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
@@ -397,6 +397,8 @@ namespace mbts.spark {
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
|
||||
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
|
||||
public bool enforce_max_clip_from_settings => current_settings.enforce_max_clip;
|
||||
public uint max_clip_from_settings => current_settings.max_clip;
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings { get; }
|
||||
|
||||
@@ -447,6 +449,9 @@ namespace mbts.spark {
|
||||
public void key_down(KeyEventArgs e) {
|
||||
if (order_properties_vm == null)
|
||||
return;
|
||||
if (KeyboardExtensions.IsDown(Key.Space)) {
|
||||
_bus?.post(SparkTags.RequestRecenterLadder, null);
|
||||
}
|
||||
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
|
||||
if (!KeyboardExtensions.IsCtrlDown()) return;
|
||||
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
|
||||
@@ -462,6 +467,10 @@ namespace mbts.spark {
|
||||
settings_invalidated?.Invoke();
|
||||
}
|
||||
|
||||
public void recenter_all_ladders() {
|
||||
_bus?.post(SparkTags.RequestRecenterLadder, null);
|
||||
}
|
||||
|
||||
public Task send_order_async((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm != null && args.button != MouseButton.Middle) {
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
@@ -470,6 +479,10 @@ namespace mbts.spark {
|
||||
size_calculator.flash_error();
|
||||
} else {
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
// enforce max clip
|
||||
if (enforce_max_clip_from_settings && size > max_clip_from_settings) {
|
||||
size = max_clip_from_settings;
|
||||
}
|
||||
return send_order_async(args.side.Value, size, args.price);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +350,17 @@ namespace mbts.spark {
|
||||
}
|
||||
} else if (_vm != null) {
|
||||
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
|
||||
|
||||
// does the size violate the local max clip
|
||||
if (_vm.enforce_max_clip_from_settings && size > _vm.max_clip_from_settings) {
|
||||
// note this size is only used for the cross market message, not the actual order command
|
||||
size = _vm.max_clip_from_settings;
|
||||
// only update the calculator value when it's a standard left-click order
|
||||
if (e.ChangedButton == MouseButton.Left) {
|
||||
_vm.size_calculator.value = _vm.max_clip_from_settings;
|
||||
}
|
||||
}
|
||||
|
||||
// if warning enabled && (buy above opposite inside market || ask below opposite inside market)
|
||||
// when 1-sided market, _get_top_price will return NaN, and so the price comparison is false
|
||||
// meaning show no warning == correct
|
||||
@@ -479,8 +490,14 @@ namespace mbts.spark {
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
var column = _column_for_point(e.GetPosition(this));
|
||||
if (column.column_type == ColumnType.Price)
|
||||
_scroll_to_center();
|
||||
if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
_vm?.recenter_all_ladders();
|
||||
} else {
|
||||
_scroll_to_center();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Row Utility Methods
|
||||
@@ -588,7 +605,8 @@ namespace mbts.spark {
|
||||
_settings.row_height,
|
||||
_price_list,
|
||||
column.settings,
|
||||
security.ContractSpec);
|
||||
security.ContractSpec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
break;
|
||||
case ColumnType.Ask: render_market_col(_asks); break;
|
||||
@@ -911,7 +929,7 @@ namespace mbts.spark {
|
||||
var prev_price_list = _price_list;
|
||||
_price_list = (!(_settings.net_change == LadderSettings.NetChangeTerms.None)) ?
|
||||
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec) :
|
||||
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec);
|
||||
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec, _settings.price_display_format);
|
||||
_ordershost.update_settings(_price_list, _price_comparer, _settings.row_height, _settings.orders_column);
|
||||
if (prev_price_list != null) {
|
||||
// maintain previous visible price range if possible
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace mbts.spark {
|
||||
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
|
||||
// (see how it is incorporated in measure_row_height() below)
|
||||
public static readonly Thickness row_padding = new Thickness(6);
|
||||
const string tick_separator_period = ".";
|
||||
const string tick_separator_dash = "-";
|
||||
|
||||
// IMPORTANT: this method ONLY WORKS if Badges use the BadgeRender.margin/border_thickness/padding values.
|
||||
// TODO: elminate the dependency on BadgeRender.margin/border_thickness/padding by integrating them into settings.
|
||||
@@ -52,8 +54,11 @@ namespace mbts.spark {
|
||||
public static int index_of(double[] prices, double price, IComparer<double> comparer) =>
|
||||
Array.BinarySearch(prices, price, comparer);
|
||||
|
||||
public static GlyphRun format_text(string text, int row, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) =>
|
||||
glyph_context.get_glyph_run(text, settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height, true);
|
||||
public static GlyphRun format_text(string text, int row, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) =>
|
||||
glyph_context.get_glyph_run(
|
||||
// long term there is probably a better way to get rid of the separator, such as in the TickExtensions._format_tick
|
||||
price_display_format == LadderSettings.PriceDisplayFormat.Integer ? text.Replace(tick_separator_period, string.Empty).Replace(tick_separator_dash, string.Empty) : text,
|
||||
settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height, true);
|
||||
|
||||
public static double[] gen_prices(double mid, int row_count, ContractSpecificationImmutable spec) {
|
||||
// require odd number
|
||||
@@ -79,10 +84,10 @@ namespace mbts.spark {
|
||||
result[i] = format_text(spec.format(prices[i], reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
|
||||
return result;
|
||||
}
|
||||
public static GlyphRun[] format_prices(double[] prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec) {
|
||||
public static GlyphRun[] format_prices(double[] prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var result = new GlyphRun[prices.Length];
|
||||
Parallel.For(0, prices.Length, i =>
|
||||
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context));
|
||||
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -104,9 +109,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static double[] render_prices(DrawingVisual price, GlyphContext glyph_context, double mid, int row_count, double row_height,
|
||||
LadderColumnSettings settings, ContractSpecificationImmutable spec) {
|
||||
LadderColumnSettings settings, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var dprices = gen_prices(mid, row_count, spec);
|
||||
render_prices(price, glyph_context, row_count, row_height, dprices, settings, spec);
|
||||
render_prices(price, glyph_context, row_count, row_height, dprices, settings, spec, price_display_format);
|
||||
return dprices;
|
||||
}
|
||||
|
||||
@@ -122,10 +127,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, double[] dprices, LadderColumnSettings settings, ContractSpecificationImmutable spec) {
|
||||
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, double[] dprices, LadderColumnSettings settings, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, row_height, settings, glyph_context, spec);
|
||||
var fprices = format_prices(dprices, row_height, settings, glyph_context, spec, price_display_format);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, row_count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
|
||||
@@ -178,6 +178,14 @@
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="EnableDisableFeatureText" TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.GrayTextBrushKey}}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
@@ -630,7 +638,11 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4" Grid.Row="0" Grid.RowSpan="2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -710,6 +722,7 @@
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Grid.Column="2"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Top"
|
||||
Header="Price Display"
|
||||
Visibility="{Binding is_synthetic, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}"
|
||||
@@ -717,6 +730,15 @@
|
||||
>
|
||||
<c:RadioButtonsControl ItemsSource="{Binding price_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_format}"/>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Stretch"
|
||||
Header="Price Display Format"
|
||||
Padding="4"
|
||||
>
|
||||
<c:RadioButtonsControl ItemsSource="{Binding price_display_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_display_format}"/>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Size Presets Tab-->
|
||||
@@ -991,21 +1013,45 @@
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox Content="Warn on Market Cross" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
|
||||
<!-- market cross -->
|
||||
<CheckBox Name="warn" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
<TextBlock Grid.Column="1"
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding ElementName=warn, Path=IsChecked}"
|
||||
Text="Warn on Market Cross" />
|
||||
<!-- max order qty -->
|
||||
<CheckBox Name="enforce_max_clip" Grid.Row="1" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding enforce_max_clip}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
|
||||
MinWidth="{Binding ElementName=warn, Path=ActualWidth}"
|
||||
Text="Max Order Quantity"/>
|
||||
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2"
|
||||
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
|
||||
Value="{Binding max_clip}" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mini Order Book">
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
@@ -11,6 +12,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
@@ -60,6 +62,35 @@ namespace mbts.spark {
|
||||
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(OrderBookRowVM),
|
||||
new string[] {
|
||||
nameof(security_text),
|
||||
nameof(symbol),
|
||||
nameof(time_created),
|
||||
nameof(last_change),
|
||||
nameof(user),
|
||||
nameof(account),
|
||||
nameof(exchange),
|
||||
nameof(time_in_force),
|
||||
nameof(order_type),
|
||||
nameof(root_algo_display_name),
|
||||
nameof(order_sub_type),
|
||||
nameof(side),
|
||||
nameof(quantity),
|
||||
nameof(filled_quantity),
|
||||
nameof(price),
|
||||
nameof(avg_price),
|
||||
nameof(last_price),
|
||||
nameof(piq),
|
||||
nameof(status),
|
||||
// last message (could contains commas, so skipping)
|
||||
nameof(security_key),
|
||||
nameof(order_key),
|
||||
nameof(parent_order_key),
|
||||
});
|
||||
|
||||
// Core
|
||||
public OrderSecurityRootPair order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order.order;
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.utils.csv;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
@@ -184,6 +185,7 @@ namespace mbts.spark {
|
||||
show_grid_settings_command.group = CommandGroups.Auxiliary;
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
|
||||
toggle_show_right_panel_command,
|
||||
attach_selected_orders_command,
|
||||
detach_selected_orders_command,
|
||||
|
||||
@@ -1,220 +1,223 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.expression;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
|
||||
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
|
||||
readonly DeferrableActionWrapper _sync_anchors;
|
||||
|
||||
PNLCalculationType _calculation_type;
|
||||
PNLHeaderRowDisplay _header_row_display;
|
||||
PNLSettings _last_loaded;
|
||||
|
||||
public PNLSettingsVM(PNLSettings settings) {
|
||||
// init anchor cache, deferrable resync
|
||||
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
|
||||
_sync_anchors = new DeferrableActionWrapper(() => {
|
||||
for (int i = 0; i < anchor_items.Count; i++) {
|
||||
anchor_items[i].anchor_index = i + 1;
|
||||
anchor_items[i].refresh();
|
||||
}
|
||||
});
|
||||
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
|
||||
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
|
||||
col_vm.load_from(col_settings);
|
||||
return col_vm;
|
||||
});
|
||||
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
|
||||
_dispose = Disposable.Create(
|
||||
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
|
||||
load(settings);
|
||||
}
|
||||
|
||||
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
|
||||
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
|
||||
public SimpleGridSettingsVM core_settings_vm { get; }
|
||||
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
|
||||
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
|
||||
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
|
||||
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
|
||||
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
|
||||
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
|
||||
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
|
||||
|
||||
public PNLSettings get_result() {
|
||||
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
|
||||
s.calculation_type = _calculation_type;
|
||||
s.grid = core_settings_vm.create();
|
||||
// move anchor cols to the left, order wrt grouping hierarchy
|
||||
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
|
||||
for (int i = 0; i < s.grid.columns.Count; i++) {
|
||||
var key = s.grid.columns[i].strong_id<ColumnKeys>();
|
||||
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
|
||||
// anchor index is 1 based, subtract 1 to get 0 based
|
||||
int anchor_index = a.anchor_index.Value - 1;
|
||||
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
|
||||
new_cols.Value.RemoveAt(i);
|
||||
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
|
||||
}
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.expression;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
|
||||
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
|
||||
readonly DeferrableActionWrapper _sync_anchors;
|
||||
|
||||
PNLCalculationType _calculation_type;
|
||||
PNLHeaderRowDisplay _header_row_display;
|
||||
PNLSettings _last_loaded;
|
||||
|
||||
public PNLSettingsVM(PNLSettings settings) {
|
||||
// init anchor cache, deferrable resync
|
||||
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
|
||||
_sync_anchors = new DeferrableActionWrapper(() => {
|
||||
for (int i = 0; i < anchor_items.Count; i++) {
|
||||
anchor_items[i].anchor_index = i + 1;
|
||||
anchor_items[i].refresh();
|
||||
}
|
||||
}
|
||||
// rule-based animations
|
||||
var trigger_anims = core_settings_vm.columns
|
||||
.OfType<PNLColumnVM>()
|
||||
.SelectMany(c => c.get_triggers())
|
||||
.GroupBy(tup => tup.expression)
|
||||
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
|
||||
.Select(g => (
|
||||
expression: g.Key,
|
||||
animations: g.ToArray()))
|
||||
.ToList();
|
||||
|
||||
var normalized_anims = trigger_anims
|
||||
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
|
||||
.ToList();
|
||||
|
||||
s = s.with(anchors: anchor_items.Select(vm => vm.key),
|
||||
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
|
||||
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.group_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.header_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
animations: trigger_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.row)
|
||||
}),
|
||||
group_header_animations: normalized_anims
|
||||
.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.group)
|
||||
}),
|
||||
header_row_animations: normalized_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.header)
|
||||
}));
|
||||
s.header_row_display = header_row_display;
|
||||
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
|
||||
// group
|
||||
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
|
||||
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
|
||||
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
|
||||
//
|
||||
return s;
|
||||
}
|
||||
|
||||
public void load(PNLSettings value) {
|
||||
_last_loaded = value;
|
||||
header_row_defaults.load(value.header_cell_appearance.to_appearance());
|
||||
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
|
||||
var column_map = _map_column_data(value.grid.columns, value.animations);
|
||||
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
|
||||
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
|
||||
|
||||
core_settings_vm.load_from(value.grid);
|
||||
|
||||
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
|
||||
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
|
||||
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
|
||||
var (col_settings, trigger_animations) = column_map[key];
|
||||
var group_data = group_column_map.GetNullable(key);
|
||||
var header_data = header_column_map.GetNullable(key);
|
||||
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
|
||||
}
|
||||
using (_sync_anchors.defer_execution())
|
||||
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
|
||||
header_row_display = value.header_row_display;
|
||||
calculation_type = value.calculation_type;
|
||||
}
|
||||
|
||||
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
|
||||
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
|
||||
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
|
||||
|
||||
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
|
||||
var map = columns.ToDictionary(
|
||||
c => c.strong_id<ColumnKeys>(),
|
||||
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
|
||||
foreach (var trig in triggers) {
|
||||
foreach (var anim in trig.animations) {
|
||||
if (map.TryGetValue(anim.column, out var col_data))
|
||||
col_data.triggers[trig.expression] = anim;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
void _append_or_remove_anchor(ColumnKeys key, bool include) {
|
||||
if (!_anchor_cache.TryGetValue(key, out var vm))
|
||||
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
|
||||
if (vm.include == include)
|
||||
return;
|
||||
if (include) {
|
||||
if (anchor_items.Any(a => a.key == key))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
|
||||
anchor_items.Add(vm);
|
||||
vm.anchor_index = anchor_items.Count;
|
||||
} else {
|
||||
if (!anchor_items.Remove(vm))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
|
||||
vm.anchor_index = null;
|
||||
}
|
||||
_sync_anchors.execute();
|
||||
}
|
||||
|
||||
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
|
||||
readonly PNLSettingsVM _owner;
|
||||
int? _anchor_index;
|
||||
|
||||
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
|
||||
_owner = owner;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public ColumnKeys key { get; }
|
||||
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
|
||||
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
|
||||
|
||||
public void refresh() =>
|
||||
RaisePropertyChanged(nameof(include), nameof(anchor_index));
|
||||
|
||||
public bool Equals(AnchorItemVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as AnchorItemVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
EnumHelper.get_display_name(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
|
||||
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
|
||||
col_vm.load_from(col_settings);
|
||||
return col_vm;
|
||||
});
|
||||
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
|
||||
_dispose = Disposable.Create(
|
||||
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
|
||||
load(settings);
|
||||
}
|
||||
|
||||
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
|
||||
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
|
||||
public SimpleGridSettingsVM core_settings_vm { get; }
|
||||
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
|
||||
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
|
||||
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
|
||||
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
|
||||
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
|
||||
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
|
||||
|
||||
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
|
||||
|
||||
public PNLSettings get_result() {
|
||||
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
|
||||
s.calculation_type = _calculation_type;
|
||||
s.grid = core_settings_vm.create();
|
||||
// move anchor cols to the left, order wrt grouping hierarchy
|
||||
List<GridColumnSettings> new_cols = null;
|
||||
IEnumerable<GridColumnSettings> cols = s.grid.columns;
|
||||
var sorted_anchors = anchor_items.OrderBy(a => a.anchor_index).ToList();
|
||||
for (int i = 0; i < sorted_anchors.Count; i++) {
|
||||
if (GridSettingsHelper.try_find_column(cols, sorted_anchors[i].key, out var match, out int pos) && pos != i) {
|
||||
if (new_cols == null) {
|
||||
new_cols = s.grid.columns.ToList();
|
||||
// set cols to new_cols so subsequent position lookups
|
||||
// take previous column order manipulations into account.
|
||||
cols = new_cols;
|
||||
}
|
||||
new_cols.RemoveAt(pos);
|
||||
new_cols.Insert(i, match);
|
||||
}
|
||||
}
|
||||
// rule-based animations
|
||||
var trigger_anims = core_settings_vm.columns
|
||||
.OfType<PNLColumnVM>()
|
||||
.SelectMany(c => c.get_triggers())
|
||||
.GroupBy(tup => tup.expression)
|
||||
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
|
||||
.Select(g => (
|
||||
expression: g.Key,
|
||||
animations: g.ToArray()))
|
||||
.ToList();
|
||||
|
||||
var normalized_anims = trigger_anims
|
||||
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
|
||||
.ToList();
|
||||
|
||||
s = s.with(anchors: anchor_items.Select(vm => vm.key),
|
||||
grid: new_cols == null ? null : s.grid.with(columns: new_cols),
|
||||
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.group_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
|
||||
var col_settings = c.create();
|
||||
col_settings = c.header_column_vm.apply_to(col_settings);
|
||||
return col_settings;
|
||||
}),
|
||||
animations: trigger_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.row)
|
||||
}),
|
||||
group_header_animations: normalized_anims
|
||||
.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.group)
|
||||
}),
|
||||
header_row_animations: normalized_anims.Select(
|
||||
ta => new PNLAnimationTriggerData {
|
||||
expression = ta.expression,
|
||||
animations = Array.ConvertAll(ta.animations, t => t.header)
|
||||
}));
|
||||
s.header_row_display = header_row_display;
|
||||
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
|
||||
// group
|
||||
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
|
||||
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
|
||||
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
|
||||
//
|
||||
return s;
|
||||
}
|
||||
|
||||
public void load(PNLSettings value) {
|
||||
_last_loaded = value;
|
||||
header_row_defaults.load(value.header_cell_appearance.to_appearance());
|
||||
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
|
||||
var column_map = _map_column_data(value.grid.columns, value.animations);
|
||||
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
|
||||
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
|
||||
|
||||
core_settings_vm.load_from(value.grid);
|
||||
|
||||
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
|
||||
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
|
||||
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
|
||||
var (col_settings, trigger_animations) = column_map[key];
|
||||
var group_data = group_column_map.GetNullable(key);
|
||||
var header_data = header_column_map.GetNullable(key);
|
||||
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
|
||||
}
|
||||
using (_sync_anchors.defer_execution())
|
||||
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
|
||||
header_row_display = value.header_row_display;
|
||||
calculation_type = value.calculation_type;
|
||||
}
|
||||
|
||||
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
|
||||
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
|
||||
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
|
||||
|
||||
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
|
||||
var map = columns.ToDictionary(
|
||||
c => c.strong_id<ColumnKeys>(),
|
||||
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
|
||||
foreach (var trig in triggers) {
|
||||
foreach (var anim in trig.animations) {
|
||||
if (map.TryGetValue(anim.column, out var col_data))
|
||||
col_data.triggers[trig.expression] = anim;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
void _append_or_remove_anchor(ColumnKeys key, bool include) {
|
||||
if (!_anchor_cache.TryGetValue(key, out var vm))
|
||||
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
|
||||
if (vm.include == include)
|
||||
return;
|
||||
if (include) {
|
||||
if (anchor_items.Any(a => a.key == key))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
|
||||
anchor_items.Add(vm);
|
||||
vm.anchor_index = anchor_items.Count;
|
||||
} else {
|
||||
if (!anchor_items.Remove(vm))
|
||||
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
|
||||
vm.anchor_index = null;
|
||||
}
|
||||
_sync_anchors.execute();
|
||||
}
|
||||
|
||||
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
|
||||
readonly PNLSettingsVM _owner;
|
||||
int? _anchor_index;
|
||||
|
||||
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
|
||||
_owner = owner;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public ColumnKeys key { get; }
|
||||
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
|
||||
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
|
||||
|
||||
public void refresh() =>
|
||||
RaisePropertyChanged(nameof(include), nameof(anchor_index));
|
||||
|
||||
public bool Equals(AnchorItemVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as AnchorItemVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
EnumHelper.get_display_name(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,12 +90,11 @@ namespace bts.shell.pnl {
|
||||
}
|
||||
|
||||
int? _try_compare_groups(CollectionViewGroup x, CollectionViewGroup y) {
|
||||
if (x == null || y == null)
|
||||
return null;
|
||||
var xcell = (IPNLCellData)x.Name;
|
||||
var ycell = (IPNLCellData)y.Name;
|
||||
var comp = _get_column_comparer(xcell.column);
|
||||
return comp.value_comparer.Compare(xcell.value, ycell.value);
|
||||
if (x?.Name is IPNLCellData xcell && y?.Name is IPNLCellData ycell) {
|
||||
var comp = _get_column_comparer(xcell.column);
|
||||
return comp.value_comparer.Compare(xcell.value, ycell.value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -698,8 +698,8 @@
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="Security"
|
||||
sort_member_path="security_cell.cell_data"
|
||||
data_member_path="security"
|
||||
data_member_path="security_cell.cell_data"
|
||||
sort_member_path="security"
|
||||
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
|
||||
/>
|
||||
<!--Security Key-->
|
||||
|
||||
@@ -653,54 +653,25 @@ namespace mbts.spark.utilities {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
static CustomOrderDefinition.CustomOneofCase[] algo_config(
|
||||
Func<bts.config.AlgoPermissions, List<string>> selector)
|
||||
{
|
||||
var conf = mbts.config.Configuration.get<bts.config.AlgoPermissions>();
|
||||
return selector(conf).Select(x =>
|
||||
{
|
||||
return (CustomOrderDefinition.CustomOneofCase)System.Enum.Parse(typeof(CustomOrderDefinition.CustomOneofCase), x, true);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[]
|
||||
|
||||
// exchange_order_types are the types which can be submitted on an exchange security
|
||||
exchange_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset,
|
||||
CustomOrderDefinition.CustomOneofCase.Splitter,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
spreader_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Spreader,
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Splitter,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
attachable_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
outright_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
};
|
||||
exchange_order_types = algo_config(x => x.exchange_order_types),
|
||||
spreader_order_types = algo_config(x => x.spreader_order_types),
|
||||
attachable_order_types = algo_config(x => x.attachable_order_types),
|
||||
outright_order_types = algo_config(x => x.outright_order_types);
|
||||
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
|
||||
|
||||
public static void copy_values(CustomOrderDefinitionImmutable source, ref CustomOrderDefinition target) =>
|
||||
|
||||
@@ -54,13 +54,16 @@ namespace mbts.spark
|
||||
{
|
||||
readonly Func<TModel, TView> _convert;
|
||||
readonly Func<TView, TModel> _convert_back;
|
||||
readonly Func<IMessage, IMessage> _message_override;
|
||||
TView _value;
|
||||
|
||||
public GenericOrderParamVM(FieldDescriptor field_descriptor, Func<TModel, TView> convert, Func<TView, TModel> convert_back) :
|
||||
public GenericOrderParamVM(FieldDescriptor field_descriptor, Func<TModel, TView> convert,
|
||||
Func<TView, TModel> convert_back, Func<IMessage, IMessage> message_override = null) :
|
||||
base(field_descriptor)
|
||||
{
|
||||
_convert = convert;
|
||||
_convert_back = convert_back;
|
||||
_message_override = message_override;
|
||||
}
|
||||
|
||||
public TView value
|
||||
@@ -68,7 +71,8 @@ namespace mbts.spark
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
this.descriptor.Accessor.SetValue(this.message, _convert_back(_value = value));
|
||||
this.descriptor.Accessor.SetValue( _message_override == null ? this.message : _message_override(this.message),
|
||||
_convert_back(_value = value));
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
@@ -84,7 +88,7 @@ namespace mbts.spark
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
_value = _convert(ConvertUtils.try_convert<TModel>(this.descriptor.Accessor.GetValue(this.message)));
|
||||
_value = _convert(ConvertUtils.try_convert<TModel>(this.descriptor.Accessor.GetValue(_message_override == null ? this.message : _message_override(this.message))));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
@@ -163,7 +167,6 @@ namespace mbts.spark
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<QuantityMeasureImmutable?> subscribe_value()
|
||||
{
|
||||
return Observable.Create<QuantityMeasureImmutable?>(o =>
|
||||
@@ -174,10 +177,9 @@ namespace mbts.spark
|
||||
o.OnNext(quantity);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
RaisePropertyChanged(nameof(quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,9 +96,41 @@ namespace mbts.spark {
|
||||
{
|
||||
Google.Protobuf.Reflection.MessageDescriptor desc = new TMutable().Descriptor;
|
||||
_parameters.SyncWithDictionary(
|
||||
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), _mk_param_vm);
|
||||
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), x => _mk_param_vm(x));
|
||||
add_message_params(desc);
|
||||
}
|
||||
}
|
||||
void add_message_params(Google.Protobuf.Reflection.MessageDescriptor desc)
|
||||
{
|
||||
var msgs = desc.Fields.InDeclarationOrder().Where(x => !_is_value_param_desc(x) &&
|
||||
x.MessageType.ClrType != typeof(CustomOrderDefinition) );
|
||||
foreach(var message_field in msgs)
|
||||
{
|
||||
foreach (var fd in message_field.MessageType.Fields.InDeclarationOrder())
|
||||
{
|
||||
if(_is_value_param_desc(fd)) //one level deep
|
||||
{
|
||||
var mk = _mk_param_vm(fd,
|
||||
outside_object =>
|
||||
{
|
||||
var msgfield = message_field.Accessor.GetValue(outside_object);
|
||||
if(msgfield == null)
|
||||
{
|
||||
message_field.Accessor.SetValue(outside_object, Activator.CreateInstance(message_field.MessageType.ClrType));
|
||||
msgfield = message_field.Accessor.GetValue(outside_object);
|
||||
}
|
||||
return (IMessage)msgfield;
|
||||
});
|
||||
if(mk == null)
|
||||
continue;
|
||||
|
||||
if(!_parameters.Add(mk))
|
||||
_parameters.Add(message_field.Name + "_" + mk.descriptor.Name, mk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _is_value_param_desc(FieldDescriptor desc)
|
||||
{
|
||||
return (desc.FieldType != Google.Protobuf.Reflection.FieldType.Message ||
|
||||
@@ -117,7 +149,7 @@ namespace mbts.spark {
|
||||
return v;
|
||||
}
|
||||
|
||||
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor)
|
||||
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor, Func<IMessage, IMessage> message_override = null)
|
||||
{
|
||||
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
|
||||
|
||||
@@ -134,33 +166,36 @@ namespace mbts.spark {
|
||||
return pvm;
|
||||
}
|
||||
else if (descriptor.FieldType == FieldType.Bool)
|
||||
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
|
||||
else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float)
|
||||
return mk<double, double>(descriptor, v => v, v => v, double.NaN);
|
||||
return mk<double, double>(descriptor, v => v, v => v, message_override, double.NaN);
|
||||
else if (descriptor.FieldType == FieldType.Int32)
|
||||
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
|
||||
else if (descriptor.FieldType == FieldType.UInt32)
|
||||
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
|
||||
else if (descriptor.FieldType == FieldType.String)
|
||||
return mk<string, string>(descriptor, v => v, v => v);
|
||||
return mk<string, string>(descriptor, v => v, v => v, message_override);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default)
|
||||
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back,
|
||||
Func<IMessage, IMessage> message_override = null, TView default_value = default)
|
||||
{
|
||||
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
|
||||
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
|
||||
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back, message_override);
|
||||
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param,
|
||||
message_override, default_value);
|
||||
|
||||
TView get_from_param(IMessage param, FieldDescriptor fd) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
|
||||
void assign_to_param(IMessage param, FieldDescriptor fd, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
|
||||
}
|
||||
|
||||
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
|
||||
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param,
|
||||
Func<IMessage, IMessage> message_override = null, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
|
||||
{
|
||||
add_property<T>(
|
||||
pvm.descriptor.Name,
|
||||
mdef => mdef != null ? selector(mdef, pvm.descriptor) : default_value,
|
||||
(mdef, v) => assign_to_param(mdef, pvm.descriptor, v),
|
||||
mdef => mdef != null ? selector( message_override == null ? mdef : message_override(mdef), pvm.descriptor) : default_value,
|
||||
(mdef, v) => assign_to_param(message_override == null ? mdef : message_override(mdef), pvm.descriptor, v),
|
||||
() => pvm.value,
|
||||
v => pvm.value = v,
|
||||
pvm.subscribe_value(),
|
||||
|
||||
@@ -4,14 +4,14 @@ using OffsetDefi = bts.api.orderman.OffsetOrderDefinitionImmutable;
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class OffsetOrderVM : OrderTypeVMBase<OffsetDef, OffsetDefi> {
|
||||
readonly Property<uint?> _tick_offset_on_deploy;
|
||||
readonly Property<int?> _tick_offset_on_deploy;
|
||||
|
||||
public OffsetOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_tick_offset_on_deploy = add_nullable_property(nameof(tick_offset_on_deploy), OffsetDef.TickOffsetOnDeployFieldNumber, 0u);
|
||||
_tick_offset_on_deploy = add_nullable_property(nameof(tick_offset_on_deploy), OffsetDef.TickOffsetOnDeployFieldNumber, 0);
|
||||
}
|
||||
|
||||
public uint? tick_offset_on_deploy { get => _tick_offset_on_deploy.Value; set => _tick_offset_on_deploy.set(value); }
|
||||
public int? tick_offset_on_deploy { get => _tick_offset_on_deploy.Value; set => _tick_offset_on_deploy.set(value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
@@ -35,7 +34,7 @@
|
||||
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
|
||||
<PackageReference Include="IronPython" Version="2.7.7" />
|
||||
<PackageReference Include="NAudio" Version="1.8.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
|
||||
<PackageReference Include="NUnit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
|
||||