Compare commits

..
509 Commits
Author SHA1 Message Date
Kevin Darby 5bc629e9f8 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2020-04-17 13:12:59 -04:00
Kevin Darby cc04f6b6ab remove /m for clientbuild 2020-03-31 16:39:30 -04:00
Kevin Darby 9325370519 move up docker 2020-03-31 15:22:12 -04:00
Kevin Darby f3d0f786ad Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2020-03-31 15:18:05 -04:00
Kevin Darby ed1a45d775 pushing new AWS keys for data downloader 2020-03-31 11:37:06 -04:00
Kevin Darby b04c61a42b fixed bug in optimal_duration 2020-03-13 17:58:17 -04:00
Kevin Darby 0790568e55 moving excel work into the algo class 2020-03-10 18:22:08 -04:00
Kevin Darby 1ecf68566d build 2020-03-10 15:43:52 -04:00
Kevin Darby 9792cd16d7 arrival 2020-03-10 15:31:19 -04:00
Kevin Darby 50b5447128 fix build 2020-03-10 10:03:09 -04:00
Kevin Darby 6893cafdd8 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2020-03-09 21:39:37 -04:00
Kevin Darby abf99f2da3 fixed ac200- 2020-03-09 21:38:48 -04:00
Kevin Darby ed87739066 fixed vol shifts, need to get my ac2000 changes from home. Demo looking ok 2020-03-09 17:09:50 -04:00
Kevin Darby 0550c01290 found optimal t based on liquidity costs vs bizt of an option priced equally 2020-03-06 18:21:43 -05:00
Kevin Darby d2dff1f296 progress 2020-03-04 18:24:10 -05:00
Kevin Darby 2af98b73bb cost vs move dynamic 2020-03-03 18:25:45 -05:00
Kevin Darby de3abfb724 moving right along 2020-03-03 13:30:32 -05:00
Kevin Darby e44c4104fc insiders.cmd 2020-03-03 09:11:45 -05:00
Kevin Darby 8fb772ee81 factoring out analytics functions for excel 2020-03-02 18:24:42 -05:00
Kevin Darby 674655f37f moved structures around for excel 2020-02-27 18:21:34 -05:00
Kevin Darby 5333c129ec can't await in a lock 2020-02-27 18:03:17 -05:00
Kevin Darby 4742535481 move some code around, make vol term structure and a shifter 2020-02-27 17:52:52 -05:00
Kevin Darby 6296f66133 we have vols 2020-02-26 18:31:12 -05:00
Kevin Darby a411402865 rain + motorcycle = double.Nan; 2020-02-25 17:58:32 -05:00
Kevin Darby 14626ad446 volservice 2020-02-25 17:57:20 -05:00
Kevin Darby 5ead098f76 working on vol model 2020-02-25 14:45:21 -05:00
Kevin Darby 510a050dd7 volservice 2020-02-24 18:34:20 -05:00
Kevin Darby 895a47b5ac added trade detail for john to consume in the sim 2020-02-24 12:26:31 -05:00
Kevin Darby ba3bcb7dd0 Merge branch 'vwap2' of bitbucket.org:bluetradesys/spark into vwap 2020-02-24 09:49:47 -05:00
Kevin Darby 704819c3e3 min cost of a tick 2020-02-24 09:48:42 -05:00
Kevin Darby 526a0a0a29 need a better variance 2020-02-21 16:44:52 -05:00
Jake Norbut ea776d2dcd only put parents in the dict 2020-02-21 13:39:54 -06:00
Kevin Darby 759ba55551 build 2020-02-20 21:58:49 -05:00
Kevin Darby 4ef82cc4ab aggressive vs passive 2020-02-20 21:42:57 -05:00
Kevin Darby a9834e6df8 unsaved vwap changes going home because of snow 2020-02-20 16:31:44 -05:00
Kevin Darby f2d592f9c7 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2020-02-19 18:18:00 -05:00
Kevin Darby 26fda7f88f arrival price 2020-02-19 18:17:44 -05:00
Jake Norbut 9864d0b0fc using enums instead of magic numbers; OrderID to NONE on cancel(replace) rejects 2020-02-19 16:36:08 -06:00
Jake Norbut 59692852b5 New, Cancel-Replace, Cancel client sample FIX sequence 2020-02-19 16:33:49 -06:00
Jake Norbut ed3bc9f71c Cancel and Cancel-Replace shoud use OrigClOrdID to lookup bts key 2020-02-19 16:32:50 -06:00
Jake Norbut a2c7481301 populate new ExternalOrderId field with ClOrdID 2020-02-19 16:30:48 -06:00
Jake Norbut f1fdaf1aca dict for external id vs bts key 2020-02-19 16:28:35 -06:00
Kevin Darby bf34963b88 switch fix to use new streaming marketdata (securities only) 2020-02-19 14:46:39 -05:00
Kevin Darby ed1609a89f fixing casing in MarketDataService.proto 2020-02-19 14:28:03 -05:00
Kevin Darby c66202117f Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2020-02-19 14:22:40 -05:00
Kevin Darby 49342e30dd Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2020-02-19 14:18:59 -05:00
Kevin Darby 20b8d095d8 historical data looking ok 2020-02-19 14:18:11 -05:00
Kevin Darby 995b4cbefe added external_order_id to our order def for echo back in order state to facilitate incoming FIX 2020-02-19 10:48:57 -05:00
Kevin Darby fb8c603b18 added cache to the server, seems like somehow do_before_launch is getting called twice 2020-02-18 18:15:35 -05:00
Kevin Darby 743b606030 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2020-02-18 17:07:32 -05:00
Kevin Darby 4950796660 adding logs 2020-02-18 17:06:51 -05:00
Kevin Darby 417fccb03c utc / local errors in sparkcmd 2020-02-18 16:53:07 -05:00
Jake Norbut 3014ceb85f fixing malformed cancel-reject message 2020-02-18 15:02:07 -06:00
Kevin Darby c1efbc00a5 arrival shaping up 2020-02-17 18:18:20 -05:00
Kevin Darby b8851f36bb getting these algos back in my head 2020-02-17 16:48:59 -05:00
Kevin Darby 9de3d80891 getting TradingDistribution data into algo 2020-02-13 18:37:31 -05:00
Kevin Darby f07fd7044f price / volume distribution looking good 2020-02-12 17:30:19 -05:00
Kevin Darby f025eed845 hooking excel to historical_query for testing 2020-02-11 18:10:01 -05:00
Kevin Darby 6a8930f594 fixed a warning, factored interface 2020-02-04 14:52:58 -05:00
Kevin Darby e49e11feb5 historical data service 2020-02-03 21:45:37 -05:00
Kevin Darby a6d4a05253 filling out volume and price distributions 2020-01-31 18:19:50 -05:00
Kevin Darby db0ff984cb new externals 2020-01-31 10:37:42 -05:00
Kevin Darby 770c54ee88 working on volume distribution 2020-01-30 18:17:46 -05:00
Kevin Darby 7ac87269c2 making my own trades files from mbo data; the exchange supplied stuff doesn't do spreads correctly 2020-01-30 17:14:07 -05:00
Kevin Darby 3a2a52d708 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2020-01-30 09:45:25 -05:00
Kevin Darby 7119e18436 Merge branch 'master' of bitbucket.org:bluetradesys/spark into dotnet 2020-01-21 10:24:48 -05:00
Kevin Darby a7b8d39787 getting twap back working 2020-01-15 18:41:31 -05:00
Kevin Darby ce88ebcebe som setup screen shots 2020-01-15 12:16:45 -05:00
Kevin Darby 1b8cca55e5 fake spreader is probably a little more trouble than worth 2020-01-14 17:10:17 -05:00
Kevin Darby a8f26e998b corn notebook 2020-01-14 14:51:58 -05:00
Kevin Darby 55eb830c7c upstream fix parser changes into historical stuff 2020-01-14 14:30:54 -05:00
Kevin Darby 0230ac06bd Merge branch 'cqg' of bitbucket.org:bluetradesys/spark into vwap 2020-01-14 14:04:50 -05:00
Kevin Darby 83005d7439 algos over fix 2020-01-14 13:28:53 -05:00
Kevin Darby a74da85f18 Merge branch 'cqg' of bitbucket.org:bluetradesys/spark into vwap 2020-01-13 15:54:15 -05:00
Kevin Darby 4872e8ad7f create, update, delete limit orders works 2020-01-13 14:57:11 -05:00
Kevin Darby 065f92937c added note about node 2020-01-11 16:39:01 -05:00
Kevin Darby cdfb60f5d9 native orders going through 2020-01-10 18:14:28 -05:00
Kevin Darby f2b407fdac json file for algos 2020-01-10 13:51:12 -05:00
Kevin Darby 4c4fa77d3a algo structure in a file 2020-01-09 22:47:00 -05:00
Kevin Darby 2aa5a02ce2 putting composites in a file 2020-01-09 18:13:20 -05:00
Kevin Darby 5e1f642076 algo types 2020-01-09 17:18:56 -05:00
Kevin Darby 90b4c7e26f starting cqg integration 2020-01-09 15:50:59 -05:00
Kevin Darby eccc3f5f38 fix merge error 2020-01-09 10:48:34 -05:00
Kevin Darby de20957ae7 change comment 2020-01-09 09:53:58 -05:00
Kevin Darby e410322fcd Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2020-01-08 23:41:57 -05:00
Kevin Darby 85cb481849 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2020-01-08 23:41:23 -05:00
Kevin Darby f7dec1161a build 2020-01-08 18:09:53 -05:00
Kevin Darby f5e826b095 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into cqg 2020-01-08 18:05:25 -05:00
Kevin Darby 4cd3bc40d9 note about not sharing externals 2020-01-08 18:02:00 -05:00
Kevin Darby 3800f3344b netcore 3.1 2020-01-08 17:35:19 -05:00
Kevin Darby 6ad139804f Merge branch 'windows_docker' of bitbucket.org:bluetradesys/spark into dotnet 2020-01-08 17:33:28 -05:00
Kevin Darby 45d5c2d7cd docker / externals 2020-01-08 17:32:19 -05:00
Kevin Darby 0d24062f03 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into windows_docker 2020-01-08 17:25:30 -05:00
Kevin Darby 44e0eae281 Merge branch 'master' of bitbucket.org:bluetradesys/spark into dotnet 2020-01-08 17:24:55 -05:00
Kevin Darby 25805cf675 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2020-01-08 17:22:14 -05:00
Kevin Darby 152ab05b4e remove externals sharing 2020-01-08 17:08:49 -05:00
kevin b7a733a306 fixed_ext 2020-01-08 14:00:16 -06:00
Kevin Darby 11d7291acb Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2020-01-08 14:12:54 -05:00
Kevin Darby d17b643e31 thought better of this assertion 2020-01-08 14:05:20 -05:00
Kevin Darby 8832e97f52 update docker 2020-01-08 12:11:51 -05:00
Kevin Darby c971d07958 MES_ES looking ok 2020-01-07 18:10:30 -05:00
Kevin Darby 55bb13e536 TED spread seems to work... 2020-01-07 17:18:29 -05:00
Kevin Darby f7c37c0a61 more TED 2020-01-07 15:33:37 -05:00
Kevin Darby f742364e46 MES vs ES and TED spread 2020-01-06 18:24:15 -05:00
Kevin Darby 3b6eef465a ted spread 2020-01-06 18:20:28 -05:00
Kevin Darby 3d93038bc6 build trouble 2020-01-05 22:01:47 -05:00
Kevin Darby 8d3b4f4957 somehow onix ice external sym links got delete 2020-01-05 21:24:54 -05:00
Kevin Darby d1764997d3 fix parse bug for scientific notation 2020-01-05 21:24:48 -05:00
Kevin Darby 2cf9974b73 for ted need to multiply quote leg 2020-01-03 18:51:46 -05:00
Kevin Darby 7f41cb6ca1 ted notebook 2020-01-03 18:46:58 -05:00
Kevin Darby 8c408dd06e moved screenshots 2020-01-03 18:26:44 -05:00
Kevin Darby 40aad212db screenshots 2020-01-03 18:25:27 -05:00
Kevin Darby 872c42d12c fix dollar pnl 2020-01-03 18:03:56 -05:00
Kevin Darby c80cdd8a52 added dollars to pnl output and lean_factor to FakeSpreader 2020-01-03 17:26:28 -05:00
Kevin Darby ffcefa06aa gc nb 2020-01-02 18:33:13 -05:00
Kevin Darby c038bf0f89 add price factor for gc/mgc spreader 2020-01-02 18:23:20 -05:00
Kevin Darby f93dd9f307 clean up log 2020-01-02 15:36:29 -05:00
Kevin Darby 1939c9c3c0 bug in stacker for 3 length 2020-01-02 15:34:27 -05:00
Kevin Darby 68b37ccfc8 added order stacker, had to change algo creation a bit to facilitate 2020-01-02 14:37:26 -05:00
Kevin Darby c5d77d4a70 corrected merge issue 2020-01-01 22:50:57 -05:00
Kevin Darby cbec2476be stacker 2020-01-01 18:13:57 -05:00
Kevin Darby 9fd3563ef8 stacker 2020-01-01 18:11:18 -05:00
Kevin Darby b66d7b50f5 changes in roll_query notebook 2020-01-01 16:18:45 -05:00
Kevin Darby fcf6ed5a96 added a get_done() function so we can get pnl for strategies that arent' done executing in our query time 2020-01-01 13:44:48 -05:00
Kevin Darby ae0aa8be2a looks like we could make a little money on roll 2019-12-31 17:31:22 -05:00
Kevin Darby 11ee1357d4 compute algo_pnl 2019-12-31 15:38:26 -05:00
Kevin Darby 937fcf9fb5 added some diagnostics, seems like we can get filled on roll 2019-12-31 15:23:10 -05:00
Kevin Darby 7601fb0a7d Merge branch 'windows_docker' of bitbucket.org:bluetradesys/spark into vwap 2019-12-31 12:40:29 -05:00
Kevin Darby 4257a172c1 fixed shared obj dir on linux build 2019-12-31 12:39:54 -05:00
Kevin Darby e38b173a0e added spark_vm_shell for windows
# Conflicts:
#	src/client/spark_historical/Notebooks/roll_cover.ipynb
2019-12-31 12:39:24 -05:00
Kevin Darby 8afeedc488 fill model logging 2019-12-31 12:14:37 -05:00
Kevin Darby f43bf20a54 Merge branch 'windows_docker' of bitbucket.org:bluetradesys/spark into vwap 2019-12-31 11:16:06 -05:00
Kevin Darby 721a08c4b4 remove these CMakeDeps so gitignore can ignore 2019-12-31 10:44:32 -05:00
Kevin Darby 41a3535e84 made some minor changes to support running a dev environment in our api docker on windows 2019-12-31 10:07:55 -05:00
Kevin Darby 626629bf06 added spark_vm_shell for windows 2019-12-31 10:06:54 -05:00
Kevin Darby 6ec2ee2182 added spark_vm_shell for windows 2019-12-30 12:52:24 -05:00
Kevin Darby 8c89a91164 fix bug in k fill model, change setup_notes to reflect .netcore 3.1 2019-12-30 12:10:08 -05:00
Kevin Darby 58c05b301a KFillModel2, heuristic 2019-12-26 23:07:17 -05:00
Kevin Darby f5e2a3c28f debugging KFillModel, realizing I don't have all the info here 2019-12-24 00:17:40 -05:00
Kevin Darby 0fd45ba92a . 2019-12-23 00:15:10 -05:00
Kevin Darby 4e440fc218 debugging KFillModel 2019-12-22 16:46:06 -05:00
Kevin Darby 41c8351709 ready to test KFillModel 2019-12-22 15:14:38 -05:00
Kevin Darby 1238648c2b leveling applies to those orders that did not receive prorata 2019-12-20 23:43:34 -05:00
Kevin Darby 03ac88683f KFillModel looks to be code complete, needs debug / test 2019-12-20 23:37:39 -05:00
Kevin Darby fe4c057b20 working on KFillModel 2019-12-20 18:18:40 -05:00
Kevin Darby e6c6997e46 prorata start 2019-12-19 23:47:41 -05:00
Kevin Darby ddd8778bab added some comments for taha, some display stuff for notebook 2019-12-19 18:09:02 -05:00
Kevin Darby f95d258210 put real life data with notebooks 2019-12-19 17:13:00 -05:00
Kevin Darby a2e1cf8dd5 moving code around 2019-12-19 15:54:37 -05:00
Kevin Darby 0b8b68e6e6 Merge branch 'csv_export' of bitbucket.org:bluetradesys/spark into vwap 2019-12-19 14:48:38 -05:00
Kevin Darby 8658a11d1f 100 ticks in one day...not enough 2019-12-19 11:20:58 -05:00
Kevin Darby d0aa90131a top doesn't do much 2019-12-19 00:35:11 -05:00
Kevin Darby ffb30177b1 trying top 2019-12-19 00:24:57 -05:00
Kevin Darby 297093f9ce honing in 2019-12-18 23:41:31 -05:00
Kevin Darby 5ba8440877 couple small bugs and some diagnostics 2019-12-18 22:35:35 -05:00
Kevin Darby 228e49d152 taking this home 2019-12-18 18:28:39 -05:00
Kevin Darby a9e983a717 we rollin 2019-12-18 15:32:19 -05:00
Jake Norbut 25445d58d0 refactoring TaS CSV export 2019-12-18 13:48:14 -06:00
Jake Norbut 0b313a8aa8 refactoring Fills CSV export 2019-12-18 13:47:36 -06:00
Jake Norbut 54f954fd4b adding CSV export to order book 2019-12-18 13:47:03 -06:00
Jake Norbut 980f38ba03 using OrderedDictionary to CSVs are output with a predictable column order 2019-12-18 13:46:22 -06:00
Kevin Darby 198086e639 got roll going 2019-12-18 13:16:49 -05:00
Jake Norbut 9eada2724b Having the Command be on the Window rather than the VM; nice thinking John 2019-12-18 10:11:10 -06:00
Jake Norbut b5f53ee918 adding csv_properties to TimeAndSalesVM.Row 2019-12-18 10:10:04 -06:00
Kevin Darby a3a8eb14ad trying another way 2019-12-18 00:07:34 -05:00
Kevin Darby 333acf0822 progress 2019-12-17 18:17:45 -05:00
Jake Norbut efc175f654 adding export to csv command to Fills window 2019-12-17 10:32:58 -06:00
Jake Norbut 6074afd999 adding csv export command to fills vm 2019-12-17 10:32:15 -06:00
Jake Norbut 219dff4055 adding new export_csv IDialogCommand which just creates a new file 2019-12-17 10:28:57 -06:00
Jake Norbut 9a3b07b622 adding ToCsv IEnumerable ext method in a new util namespace 2019-12-17 10:21:38 -06:00
Kevin Darby 1af49376b6 progress in passive fills model 2019-12-16 23:42:58 -05:00
Kevin Darby f90f6cfb33 using sortedlist for our orders in fifo 2019-12-16 23:27:29 -05:00
Kevin Darby 6433f9ba98 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-12-16 21:39:53 -05:00
Kevin Darby cc1882ea1a trying to add iceberg reup to passive fill stream 2019-12-16 18:46:30 -05:00
Kevin Darby 56e83a1e69 fix timestamp bug 2019-12-16 15:17:59 -05:00
Eric Harding 487aaef02f update cmake 2019-12-16 13:12:50 -05:00
Eric Harding f22fc03e56 merge master 2019-12-16 12:54:19 -05:00
Kevin Darby 69d429fa4e nb 2019-12-15 23:08:36 -05:00
Kevin Darby e8c9e70a4a change name of methods 2019-12-15 21:30:22 -05:00
Kevin Darby 6c78850902 need to deduct from our orders in fifo model 2019-12-15 14:24:14 -05:00
Kevin Darby adab377e44 got some passive fills, need to verify 2019-12-13 23:09:31 -05:00
Kevin Darby 7bdaa35768 passive fifo fill model 2019-12-13 22:14:02 -05:00
Kevin Darby 1c7f79f293 icomparer 2019-12-13 18:18:47 -05:00
Kevin Darby 3a7246c5b0 time to leave 2019-12-13 18:16:59 -05:00
Kevin Darby 3bd95de839 bugs 2019-12-13 18:14:23 -05:00
Kevin Darby 576d3836b3 fills by query 2019-12-13 18:05:41 -05:00
Kevin Darby f1788b172c trying to layer these as queries 2019-12-13 16:53:56 -05:00
Kevin Darby ae5912d41d now we're cookin 2019-12-13 14:36:15 -05:00
Kevin Darby adbab4057f renames 2019-12-13 14:15:29 -05:00
Kevin Darby ca4a639b86 changed IFillModel to IMatchingModel, because that's really what it is, now I need fill model 2019-12-13 14:12:55 -05:00
Kevin Darby a539a06f45 interim push to go more stateless 2019-12-13 13:33:18 -05:00
Kevin Darby 1a87544ccb orders 2019-12-12 23:51:18 -05:00
Kevin Darby b97450297e update notes 2019-12-12 23:10:07 -05:00
Kevin Darby 6a1040a339 regen for trade counter parties 2019-12-12 23:07:49 -05:00
Kevin Darby 7fe20129d2 seems like we really can't count on the order of trade vs delete, so we have to use the price book to approximate crosses 2019-12-12 22:46:12 -05:00
Kevin Darby 4f59c1bb47 1 based arrays, so good 2019-12-12 18:26:10 -05:00
Kevin Darby 3ba17f1c87 allow disk in query 2019-12-12 18:19:08 -05:00
Kevin Darby ef874dda7e bigblue is slow, but I think we're on the right track now 2019-12-12 18:10:03 -05:00
Kevin Darby 6e966a6229 write the data with orders in same mongo document 2019-12-12 16:56:43 -05:00
Kevin Darby ed3b890c67 might have to store the data a little differently 2019-12-12 15:16:02 -05:00
Kevin Darby 2a46fd6227 push for pete 2019-12-12 14:27:28 -05:00
Kevin Darby 23e13846c6 botched order priority when only reducing size 2019-12-12 10:13:40 -05:00
Kevin Darby 39e4549972 only got one fill 2019-12-11 22:55:12 -05:00
Kevin Darby d924962546 kinda starting to work 2019-12-11 18:11:13 -05:00
Kevin Darby 0f1c81eb48 roundtrip 2019-12-11 18:07:33 -05:00
Kevin Darby 5e6fe7282d moved code around verified roll_snipe still works 2019-12-11 17:02:47 -05:00
Kevin Darby a47dad1ce4 changes in algo backtester to facilitate jupyter, will probably remove backwards compatibility in a bit 2019-12-11 13:28:48 -05:00
Kevin Darby 1809c778e2 ialgo 2019-12-10 23:40:55 -05:00
Kevin Darby 2178e16d3c restructure for jupyter 2019-12-10 22:50:49 -05:00
Kevin Darby deadbc6cf2 we actually do want one row per order for better query perf 2019-12-10 21:14:57 -05:00
Kevin Darby 445d77223f move some stuff around 2019-12-10 18:18:05 -05:00
Kevin Darby 09b24e33fc Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-12-09 14:05:56 -06:00
Kevin Darby 84553d11c4 new_externals 2019-12-09 14:05:04 -06:00
Kevin Darby eb1bab5878 linux build 2019-12-09 14:18:05 -05:00
Kevin Darby a7a58038b3 release build 2019-12-09 14:15:04 -05:00
Kevin Darby 1b9af8f010 new externals 2019-12-09 11:45:10 -05:00
Kevin Darby 113c4fe539 changes in notebooks, I am matching Taha 2019-12-09 11:12:06 -05:00
Kevin Darby c33662dc2c py 2019-12-09 00:30:36 -05:00
Kevin Darby 8817b22376 write book with trades 2019-12-08 14:37:48 -05:00
Kevin Darby b48da514e5 I think we got it 2019-12-08 00:18:25 -05:00
Kevin Darby 59cba2456b honing in on some timing issue 2019-12-07 22:02:45 -05:00
Kevin Darby 839c3d430b fixed build 2019-12-07 16:27:30 -05:00
Kevin Darby 5b1f2522e1 found bug in synth processor, won't build until I fix 2019-12-06 18:29:58 -05:00
Kevin Darby 6c3ccdf856 that's 400 micros, not nanos 2019-12-06 14:30:15 -05:00
Kevin Darby 157d834cbb getting somewhere with the roll 2019-12-06 13:42:40 -05:00
Kevin Darby 4f2e9b19cc trying to merge some collections 2019-12-05 23:49:11 -05:00
Kevin Darby fed7ca1ff3 honing in 2019-12-05 18:20:28 -05:00
Kevin Darby 73cfa17791 notes 2019-12-05 10:50:54 -05:00
Kevin Darby 474a53ba92 change notebook destination to big blue 2019-12-05 10:50:10 -05:00
Kevin Darby 69612eaee7 thought better of two tables, this makes simulation way easier 2019-12-04 23:33:48 -05:00
Kevin Darby b7ad777de4 have to go back to orders stream 2019-12-04 22:44:22 -05:00
Kevin Darby d6a70d852e added orders to the mongo 2019-12-04 18:10:47 -05:00
Kevin Darby 33f12edce5 finally figured out timestamps 2019-12-03 23:47:48 -05:00
Kevin Darby ab2e9fe6bd I might have fixed the dates 2019-12-03 22:34:04 -05:00
Kevin Darby 8bcffea686 jupyter 2019-12-03 18:17:31 -05:00
Kevin Darby 941472c5c0 bug in spread processing, small things in simulation 2019-12-02 23:35:42 -05:00
Kevin Darby d2a5e03976 notes 2019-12-02 18:14:54 -05:00
Kevin Darby 97e3bce327 moving historical analysis stuff to spark_historical 2019-12-02 16:06:05 -05:00
Kevin Darby e766667584 <= vs < 2019-12-02 14:48:54 -05:00
Kevin Darby a93c110c29 better serialization for pbuf Timestamps 2019-12-02 14:43:29 -05:00
Kevin Darby e1c7b89408 mongo 2019-12-01 21:38:55 -05:00
Kevin Darby ad3ce72a39 added a mongo generator, much nicer 2019-11-29 16:35:46 -05:00
Kevin Darby 57f48472d3 trying out timescaledb, 2019-11-29 15:47:20 -05:00
Kevin Darby 45ae6ac585 parsing this data needs to get faster 2019-11-29 13:33:06 -05:00
Kevin Darby 5a90676a25 need a finally 2019-11-27 21:57:03 -05:00
Kevin Darby 8536f782ce Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 21:47:03 -05:00
Kevin Darby 5f45ddf210 wack vs backwack 2019-11-28 02:46:27 +00:00
Kevin Darby 67c83c7d68 remove postgres tag from spark_vm_shell 2019-11-27 21:10:22 -05:00
Kevin Darby 6f6e1a9e55 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 21:01:43 -05:00
Kevin Darby c80451bc5e added async 2019-11-27 18:20:14 -05:00
Kevin Darby d6a533ca5c PR feedback 2019-11-27 17:21:24 -05:00
Kevin Darby e0112eff89 stuff 2019-11-27 17:20:55 -05:00
Kevin Darby 38b29f0afb more pg 2019-11-27 17:14:29 -05:00
Kevin Darby 8b9d689a5f able to preprocess trades and books into a postgres 2019-11-27 16:18:56 -05:00
Kevin Darby 097edb2726 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 15:09:58 -05:00
Kevin Darby 12e3e9a222 gave up and decided to run postgres in docker on my host 2019-11-27 15:08:51 -05:00
Taha Afzal ce19ba1419 added MarketQueriesHelper 2019-11-27 14:04:32 -05:00
Taha Afzal c81a22e55d Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 11:50:45 -05:00
Taha Afzal bd04f49ff5 Reorg 2019-11-27 11:50:38 -05:00
Eric Harding 3a9e8970d6 merge temp_synthetic_compute 2019-11-27 10:28:22 -05:00
Taha Afzal 9837645d16 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-27 09:34:43 -05:00
Taha Afzal 1789c02295 skeleton in place 2019-11-27 09:34:28 -05:00
Kevin Darby 3280376df3 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-26 21:31:42 -05:00
Taha Afzal d251593f04 parsing sniper args 2019-11-26 19:49:51 -05:00
Kevin Darby 9afeb29c63 pg 2019-11-26 18:21:06 -05:00
Kevin Darby e4dc07ce3f Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-26 17:40:01 -05:00
Kevin Darby 2bbf22a153 new docker for postges some db code 2019-11-26 16:03:43 -05:00
Taha Afzal a598ae8bd2 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-26 15:40:19 -05:00
Taha Afzal 4d72b9d2e7 filling in 2019-11-26 15:40:10 -05:00
Kevin Darby e000b1d0c0 hmm 2019-11-26 00:07:36 -05:00
Kevin Darby ef4261fe7f small parser tweaks for taha / me 2019-11-25 23:17:30 -05:00
Kevin Darby c3b75aa742 rename packet variable 2019-11-25 15:13:30 -05:00
Taha Afzal 2518be52fb book post processing path formalized 2019-11-25 15:12:29 -05:00
Kevin Darby f3ee6c0e30 TWAP completes 2019-11-25 14:54:15 -05:00
Kevin Darby 94363767ab small bug in slicer 2019-11-25 14:10:54 -05:00
Taha Afzal 9dd6e39665 post processing of books + helper functions to allign boks 2019-11-25 09:34:38 -05:00
Kevin Darby 722ae68cf1 more 2019-11-18 15:08:09 -05:00
Eric Harding e39313ecf6 Add merge_synthetic_book sample test code 2019-11-15 17:42:49 -05:00
Eric Harding 8095100bee p/invoke for synthetic book computation 2019-11-15 16:11:02 -05:00
Kevin Darby a76415a52f native algo state 2019-11-14 17:04:42 -05:00
Kevin Darby 06305ee4ac having trouble routing fills down to c++ 2019-11-14 16:21:51 -05:00
Kevin Darby 70e87151bc upstream changes for PR 2019-11-13 16:49:17 -05:00
Kevin Darby d1299f8a78 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-13 16:47:18 -05:00
Kevin Darby 20e58dc80b address PR concerns 2019-11-13 16:46:36 -05:00
Kevin Darby 9f7b0e5898 running twap from c#, need some stats 2019-11-12 18:12:06 -05:00
Kevin Darby 5e073c2330 interop with c++ algos kinda works 2019-11-12 17:56:25 -05:00
Kevin Darby 1b425d9a7c Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-12 11:31:31 -05:00
Kevin Darby 3546b65c91 don't finalize 2019-11-12 11:18:24 -05:00
Kevin Darby 758da29e25 added assert I think works 2019-11-11 23:19:03 -05:00
Kevin Darby 60e246036b back to state where I can repl dan 2019-11-11 23:14:49 -05:00
Kevin Darby 4b34f959e6 skip default 2019-11-11 18:17:29 -05:00
Kevin Darby 8abbd24cbe bug in orderbook refactor 2019-11-11 17:57:19 -05:00
Kevin Darby 932f852d6a Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-11 17:14:16 -05:00
Kevin Darby 5773945791 add data dir for .net in linux
make sparkcmd build in the right place on windows again
2019-11-11 17:12:55 -05:00
Kevin Darby bda1bd8f96 round trip algo runs on linux 2019-11-11 17:11:27 -05:00
Kevin Darby c3632e02d3 Added NativeOrderWrapper for algo backtesting 2019-11-08 18:04:34 -05:00
Kevin Darby 1b959b09c8 hooking c++ algos to algo backtest framework 2019-11-07 18:01:21 -05:00
Kevin Darby f55637ea08 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-06 21:36:57 -05:00
Kevin Darby 0272df9974 Merge branch 'vwap' of bitbucket.org:bluetradesys/spark into vwap 2019-11-06 21:34:45 -05:00
Kevin Darby 0e02428b79 change algoanalyzer startup for taha 2019-11-06 21:34:35 -05:00
Kevin Darby 721c889235 release mode uses published sparkcmd 2019-11-06 18:22:06 -05:00
Kevin Darby 9957af655f hooked sparkcmd linux build to the main server build stuff 2019-11-06 17:47:21 -05:00
Kevin Darby eab92fb486 damn windows casing 2019-11-06 13:55:29 -05:00
Kevin Darby 9f1cd47cfa hooking build to cmake 2019-11-06 13:55:07 -05:00
Kevin Darby 90c3edbbe7 adding core 2 back to docker per eric 2019-11-06 13:24:57 -05:00
Kevin Darby adee8b584c capitalized first letter of proto to work around a protoc bug 2019-11-06 12:43:27 -05:00
Kevin Darby 329ad323e0 delete protos to change case on windows 2019-11-06 12:41:37 -05:00
Kevin Darby a8473f9340 Capitalizing first letter of some protos because of a protoc bug 2019-11-06 12:31:58 -05:00
Kevin Darby 1c0115f8cf had to delete these protos to change case 2019-11-06 12:31:12 -05:00
Kevin Darby a6ef802d3f fix proj files 2019-11-05 18:38:17 -05:00
Kevin Darby 37b5c37015 merging dotnet branch 2019-11-05 18:24:24 -05:00
Kevin Darby 1e28e214a4 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-05 18:23:47 -05:00
Kevin Darby c713b97441 cleanup csproj 2019-11-05 18:23:09 -05:00
Kevin Darby 4e8525f015 Merge branch 'dotnet' of bitbucket.org:bluetradesys/spark into vwap 2019-11-05 18:21:40 -05:00
Kevin Darby eb79264b9b netcore builds on linux through dotnet build 2019-11-05 18:19:17 -05:00
Kevin Darby 9c359ddb33 for_taha 2019-11-05 16:38:01 -05:00
Kevin Darby 67a16d5085 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-11-03 23:20:44 -05:00
Kevin Darby 6049cc4b43 oil spreads seem to work 2019-10-26 22:38:13 -04:00
Kevin Darby d916180132 back to oil 2019-10-25 23:50:20 -04:00
Kevin Darby ad9f28f782 round trip treasuries...not great 2019-10-25 23:32:02 -04:00
Kevin Darby 2890e4ebcc rt spreader 2019-10-25 18:56:52 -04:00
Kevin Darby 6638e4c765 fixed spreads / outright arbitration 2019-10-25 18:50:04 -04:00
Kevin Darby a251a8f411 clone 2019-10-25 00:50:02 -04:00
Kevin Darby e7790679ca this isn't as fast as I thought it would be 2019-10-25 00:15:34 -04:00
Kevin Darby 2a5e777c15 this is better 2019-10-24 18:30:24 -04:00
Kevin Darby fdbbce9ff3 remove concept of pre_book, instead separate the book arrangement and the trade / cross processing and layer in our orders by timestamp 2019-10-24 18:25:06 -04:00
Kevin Darby d47ac768c4 trying something 2019-10-24 16:30:21 -04:00
Kevin Darby 4867f4fa53 match event 2019-10-24 15:53:16 -04:00
Kevin Darby 902a99e7d0 new files 2019-10-24 14:30:13 -04:00
Kevin Darby faf5f664dc share one order book 2019-10-24 14:29:35 -04:00
Kevin Darby 4af94aaaa8 made some trades 2019-10-23 23:52:23 -04:00
Kevin Darby 2bea0a0a11 replicated some CL spread arbs 2019-10-23 18:34:40 -04:00
Kevin Darby b55067be91 trying 10/21 oil spreads 2019-10-23 00:24:21 -04:00
Kevin Darby 7ea20dbbff had to introduce concept of can_aggress-- if we're first in the queue we can't tell the difference between that and a cross 2019-10-22 23:50:07 -04:00
Kevin Darby 2dcf1424ea . 2019-10-22 18:21:30 -04:00
Kevin Darby caedb1ed64 round trip spreader taking shape 2019-10-22 18:15:13 -04:00
Kevin Darby d39856a93a refactor for multiple securities, implemented round tripper 2019-10-22 16:27:01 -04:00
Kevin Darby f8c7cc87b9 complicated hierarchies 2019-10-22 00:07:35 -04:00
Kevin Darby ee9a9bd721 moving stuff around for spreader case 2019-10-21 18:21:21 -04:00
Kevin Darby 7520d3516a move pnl computation to orderbook 2019-10-21 12:37:12 -04:00
Kevin Darby a2a9df86fc now we need prorata 2019-10-20 22:18:36 -04:00
Kevin Darby aa31aa7a3e testing 2019-10-20 21:27:19 -04:00
Kevin Darby 950b8686f2 small bug here in change / cancel 2019-10-20 21:22:50 -04:00
Kevin Darby b6ab217a11 assert that we are moving forward in time 2019-10-20 21:21:59 -04:00
Kevin Darby 3b8176d326 status event 2019-10-20 21:19:49 -04:00
Kevin Darby e9ec07cb2b have to queue the hedge with latency 2019-10-20 01:13:12 -04:00
Kevin Darby 78659b5d20 if these treasury results are real we are getting into a new business 2019-10-20 00:25:07 -04:00
Kevin Darby 9e26a2c651 adding top 2019-10-19 21:42:08 -04:00
Kevin Darby 3243ce1a45 treasury roll 2019-10-18 23:58:11 -04:00
Kevin Darby 475b6ae25b peg, cover, lean 2019-10-18 22:48:17 -04:00
Kevin Darby b853af59d6 usings 2019-10-18 22:24:10 -04:00
Kevin Darby ded1ad42e7 arbitraged spread and futures streams, separated replay from sending 2019-10-18 22:13:30 -04:00
Kevin Darby f3541b6c7f added latency to the catalyst loop; my clock isn't great though because it relies on the tape 2019-10-18 17:21:55 -04:00
Kevin Darby 3c40c84c02 got the mbo fixed 2019-10-18 16:47:02 -04:00
Kevin Darby d50420edb9 cme can send order updates in MDEntry or OrderIdEntry.... 2019-10-18 15:06:37 -04:00
Kevin Darby e0794f0725 added some assertions, cleaning up, now have to look at q 2019-10-18 13:23:40 -04:00
Kevin Darby fb6282ae9e optimizations 2019-10-18 11:15:08 -04:00
Kevin Darby 23995e5cc6 shaping up 2019-10-17 23:52:20 -04:00
Kevin Darby 161545b864 jcl is small loss always joining at end of q 2019-10-17 22:10:30 -04:00
Kevin Darby 285250dded bugs 2019-10-17 18:22:23 -04:00
Kevin Darby bbc3e03e48 now lets look at this queue 2019-10-17 18:04:03 -04:00
Kevin Darby 6b8d0d203c partials work, the queue seems to have broken 2019-10-17 17:42:03 -04:00
Kevin Darby c0bec8cfa5 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-17 12:18:01 -04:00
Kevin Darby 19b0d8bf56 typo that I need to fix in master 2019-10-17 11:47:46 -04:00
Kevin Darby ede14ea7b3 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-17 11:40:24 -04:00
Kevin Darby 48ae7c4c33 this looks a lot like our c++ interface; I do not know if that's good 2019-10-17 11:36:35 -04:00
Kevin Darby fc217fd904 sweet replication 2019-10-16 23:23:16 -04:00
Kevin Darby b692a3e9d2 partial fills kinda kill everything 2019-10-16 18:37:13 -04:00
Kevin Darby 48f5e47335 now partial fills 2019-10-16 15:38:43 -04:00
Kevin Darby a080a0ccaa using sendingTime instead of transactTime 2019-10-16 14:45:13 -04:00
Kevin Darby 90321045f6 need to filter orders separately 2019-10-15 23:55:16 -04:00
Kevin Darby 8ad9e24415 interesting results 2019-10-15 23:26:10 -04:00
Kevin Darby 2008786c4e some semblance of a simulation going 2019-10-15 18:28:49 -04:00
Kevin Darby 57f8036796 jcl 2019-10-14 18:10:21 -04:00
Kevin Darby 21a304261e merge 2019-10-14 00:14:29 -04:00
Kevin Darby cefb2b2524 Merge branch 'fix' of bitbucket.org:bluetradesys/spark into vwap 2019-10-13 14:12:50 -04:00
Kevin Darby e26bdb228b Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-13 14:10:12 -04:00
Kevin Darby 1621ad03bf Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-10-13 14:09:45 -04:00
Kevin Darby 174acf1597 getting join fills 2019-10-11 18:22:53 -04:00
Kevin Darby bf32c6133a mbo parsing in c# 2019-10-11 18:18:22 -04:00
Kevin Darby fe73874b19 more analysis 2019-10-11 17:28:12 -04:00
Kevin Darby 0eb82a0d5c inching towards a simulated pnl of an arbitrary strategy 2019-10-10 18:05:14 -04:00
Kevin Darby fc2165d85d CL, ZN, ZC exhibit spookily similar histograms, ES is a little different 2019-10-10 11:04:34 -04:00
Kevin Darby a368b2ea16 factored out analyzers so we can test Join/Cover/Lean and Spreader 2019-10-09 17:46:10 -04:00
Kevin Darby 6294ef6db2 made some headway in analysis 2019-10-09 16:25:44 -04:00
Kevin Darby a9cca80c6d some corn stats 2019-10-08 17:36:16 -04:00
Kevin Darby aee9066ccf cmake 2019-10-08 15:14:44 -04:00
Kevin Darby ea3c130738 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-10-08 13:13:56 -04:00
Kevin Darby 193f248f06 merge deleted some of my files 2019-10-07 17:40:01 -04:00
Kevin Darby 5212fb272e Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-10-07 17:27:12 -04:00
Kevin Darby 6772c77e01 rudimentary fix orders seem to work 2019-10-07 17:18:20 -04:00
Kevin Darby 95c589f594 hooking up to run this fix server 2019-09-30 15:00:30 -04:00
Kevin Darby 699c7c5bfe Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-09-30 12:02:13 -04:00
Kevin Darby 7de82b5685 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-09-30 11:45:44 -04:00
Kevin Darby a8e9d0d7d8 ordersession and dropcopy session code complete for native orders, time to debug 2019-09-27 18:21:35 -04:00
Kevin Darby d078e73179 order session 2019-09-27 17:58:36 -04:00
Kevin Darby 4351d6e66a drop copy session code complete 2019-09-27 17:04:42 -04:00
Kevin Darby 9850d6bde0 rx-cpp requires concurrent use with prescription medicine 2019-09-27 14:29:22 -04:00
Kevin Darby d23a072ad6 rx 2019-09-26 18:27:41 -04:00
Kevin Darby 6ef0808f49 never thought I'd use rxcpp...it's truly terrible; I feel physical pain 2019-09-26 18:16:48 -04:00
Kevin Darby e1c2d8aaaf it builds, ship it 2019-09-26 12:33:42 -04:00
Kevin Darby 719fd03063 nothing builds 2019-09-26 11:45:08 -04:00
Kevin Darby d436dae7cd more fix 2019-09-24 18:22:09 -04:00
Kevin Darby f66004b465 fix port 2019-09-24 14:37:30 -04:00
Kevin Darby aa8a56787b fix fix fix 2019-09-23 18:16:05 -04:00
Kevin Darby 35a69396f1 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-09-23 17:08:56 -04:00
Kevin Darby ddfb6d1105 update externals 2019-09-23 17:03:49 -04:00
Kevin Darby 005c0f9a5d c++ fix 2019-09-23 16:59:39 -04:00
Kevin Darby cf62eae51b replay book building 2019-09-06 18:13:16 -04:00
Kevin Darby c7e49e8d21 data replay 2019-09-05 18:47:04 -04:00
Kevin Darby 8bc420190b Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-05 18:46:53 -04:00
Kevin Darby 62711a144b got some replay 2019-09-05 14:14:41 -04:00
Kevin Darby 333273b48a testing 2019-09-04 17:41:48 -04:00
Kevin Darby dd194f8ac0 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-04 17:11:44 -04:00
Kevin Darby 5992f4814d testing replay 2019-09-04 17:02:06 -04:00
Kevin Darby d67f15b3fb Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-04 15:12:02 -04:00
Kevin Darby be8635788a Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-04 14:03:41 -04:00
Kevin Darby d96355e6c4 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-03 14:36:29 -04:00
Kevin Darby 4ef2c8a2da conf changes 2019-09-03 14:35:23 -04:00
Kevin Darby 058fbe1aee utils is not in mbts any longer 2019-09-02 17:14:41 -04:00
Kevin Darby bc0f9f574a Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-09-02 17:05:21 -04:00
Kevin Darby f575b82ce0 almost got the replay going 2019-08-30 17:54:58 -04:00
Kevin Darby 050ea18a02 generation 2019-08-30 00:17:00 -04:00
Kevin Darby 18829279d2 working on replay from parser 2019-08-29 17:44:55 -04:00
Kevin Darby 75703d54ea getting mbo from s3 2019-08-29 12:21:01 -04:00
Kevin Darby 0a3fc1bca0 s3 2019-08-28 18:52:02 -04:00
Kevin Darby bc688ef44d parsing mbo files 2019-08-28 17:47:29 -04:00
Kevin Darby 57af89b628 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-08-28 16:47:20 -04:00
Kevin Darby 4fcc70bcf0 mbo fix parser 2019-08-27 18:06:03 -04:00
Kevin Darby cad6f2c1eb Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-26 17:57:16 -04:00
Kevin Darby c16b898d86 scaffolding for historical data 2019-08-26 17:55:26 -04:00
Kevin Darby 4f98244c3a arrival 2019-08-23 22:23:59 -04:00
Kevin Darby 8770f98fb1 more algo 2019-08-19 11:54:23 -04:00
Kevin Darby bbcf78517f self adjust 2019-08-16 11:30:36 -04:00
Kevin Darby b9a940b4e3 adjust for my size 2019-08-15 18:00:55 -04:00
Kevin Darby d8a4c1aaf7 added VolumeSlicer hierarchy, VolumeSliceAlgo and POVAlgo 2019-08-15 17:41:23 -04:00
Kevin Darby 9803cf31c8 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-15 09:36:00 -04:00
Kevin Darby 4a36177992 externals with master 2019-08-14 16:17:19 -04:00
Kevin Darby 421614198a fixed external 2019-08-14 15:45:31 -04:00
Kevin Darby 51e227e921 timeslicer 2019-08-12 22:33:26 -04:00
Kevin Darby 74948d11c5 build 2019-08-12 18:33:00 -04:00
Kevin Darby f88e1b5903 build 2019-08-12 18:30:27 -04:00
Kevin Darby 59115ce886 volume estimator 2019-08-12 18:24:29 -04:00
Kevin Darby 4b18d15421 refactor into TimeSlicer and VolumeSlicer 2019-08-12 18:07:07 -04:00
Kevin Darby 9be591c634 need a slightly different hierarchy for pov 2019-08-12 16:08:46 -04:00
Kevin Darby 462af2f1f3 make offset algo support a negative offset which means cross 2019-08-12 14:20:45 -04:00
Kevin Darby ab35c943fd working on pov 2019-08-12 13:52:57 -04:00
Kevin Darby aa36b24165 slicer params 2019-08-12 11:11:27 -04:00
Kevin Darby 547cdda3db adding aggression params to base class 2019-08-11 16:06:01 -04:00
Kevin Darby 3b77fd1a6c peg price updates 2019-08-09 18:09:31 -04:00
Kevin Darby a8110c9a8d added peg algo to support vwap/twap 2019-08-09 17:58:09 -04:00
Kevin Darby b6f80d6a4a twap working ok 2019-08-09 14:52:25 -04:00
Kevin Darby 18489e64a2 twapping 2019-08-08 18:16:00 -04:00
Kevin Darby a3d2df9ba2 . 2019-08-08 16:44:35 -04:00
Kevin Darby 314700e2e7 residual quantity 2019-08-08 16:09:06 -04:00
Kevin Darby 461b6c15d6 twap bugs 2019-08-08 15:25:00 -04:00
Kevin Darby d23ac7e8ef allow generic order vm to reflect one level deep into protos to generate a gui; will need john's help on a datetimepicker 2019-08-08 14:54:34 -04:00
Kevin Darby 68c0179465 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-08 12:20:15 -04:00
Kevin Darby 8bc1455813 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-08 11:07:40 -04:00
Kevin Darby ecaa04ecf8 factor 2019-08-08 09:59:14 -04:00
Kevin Darby 44e3070544 add TimeSlicer 2019-08-08 00:05:41 -04:00
Kevin Darby 1815950835 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-07 17:31:50 -04:00
Kevin Darby 8762fbf7ce added twap to algo types 2019-08-06 18:20:10 -04:00
Kevin Darby 0459171d6e moved algo types to config 2019-08-06 18:15:47 -04:00
Kevin Darby fcdf72c5d9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-06 16:57:36 -04:00
Kevin Darby f5caf0b7ba twap bones 2019-08-06 16:57:02 -04:00
Kevin Darby a0f2673b3a basic timing structure down; maybe all these derive from twap 2019-08-05 23:51:28 -04:00
Kevin Darby ba3439b049 twap 2019-08-05 18:21:03 -04:00
Kevin Darby ae4d7f719b Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-02 17:57:21 -04:00
Kevin Darby 64916f6bfc Merge branch 'master' of bitbucket.org:bluetradesys/spark into vwap 2019-08-02 17:38:30 -04:00
Kevin Darby 1a9dd1c37a cleaning up this proto 2019-08-01 17:40:21 -04:00
Kevin Darby d77925c568 Merge branch 'prorata' of bitbucket.org:bluetradesys/spark into vwap 2019-08-01 17:11:38 -04:00
Kevin Darby f1f1b68ffb arrival 2019-07-03 22:14:39 -04:00
Kevin Darby 835f453f03 Merge branch 'prorata' of bitbucket.org:bluetradesys/spark into vwap 2019-07-03 15:17:09 -04:00
Kevin Darby 0eb9518485 didn't get far 2019-06-13 23:41:30 -04:00
Kevin Darby b5c70fc34e added price to slice 2019-06-11 23:56:11 -04:00
Kevin Darby a1dba1d7d0 historicaldataservice 2019-06-11 23:12:20 -04:00
Kevin Darby efdea5256f algoflow will probably not be in python 2019-06-11 21:07:55 -04:00
Kevin Darby 5df1e5f383 py notes 2019-06-09 22:50:46 -04:00
Kevin Darby 99750da31d beginnings of python driver for algo development. goal is to allow visual / interactive development environment for
more complex algos.  Will also offer backtesting of bts algos over MBO data.
2019-06-09 22:47:51 -04:00
Kevin Darby 49247dba3f externals 2019-06-09 21:58:11 -04:00
Kevin Darby a25257cb33 update externals for pybind 2019-06-08 23:33:43 -04:00
Kevin Darby b1c180d663 Merge branch 'prorata' of bitbucket.org:bluetradesys/spark into vwap 2019-06-07 16:01:49 -04:00
Kevin Darby 6afb16abcb bones for bluex orders 2019-06-07 15:53:25 -04:00
Kevin Darby 995fa3df35 . 2019-06-06 23:34:25 -04:00
Kevin Darby 18eb28dfd2 waps 2019-06-06 22:08:03 -04:00
Kevin Darby 99cdfadf8d bones for vwap, twap, arrivalprice, pov 2019-06-05 23:30:52 -04:00
Kevin Darby feab3cbb89 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2019-01-22 16:10:16 -05:00
Kevin Darby 1f7c1be194 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2018-12-28 13:51:59 -05:00
Kevin Darby f78f18b3b4 Merge branch 'master' of bitbucket.org:bluetradesys/spark into fix 2018-12-20 16:20:19 -05:00
Kevin Darby 6e7fa91718 ready for testing 2018-12-14 10:39:34 -05:00
Kevin Darby 8cac406547 fix error handling 2018-12-12 17:33:25 -05:00
Kevin Darby 0d83f28cbc fix lives 2018-12-12 14:26:03 -05:00
Kevin Darby 941f4bf837 Merge branch 'cleanup_mdclient' of bitbucket.org:bluetradesys/spark into fix 2018-12-11 23:05:35 -05:00
Kevin Darby 8bbe9cfd98 Merge branch 'master' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-06 12:56:02 -05:00
Kevin Darby ba633532eb hit a wall here with MDClient, going to refactor 2018-12-06 12:55:21 -05:00
Kevin Darby 7bf50415ca nearly ready for testing 2018-12-05 17:55:06 -05:00
Kevin Darby edbaa9c435 looks like we have dropcopy 2018-12-05 15:35:49 -05:00
Kevin Darby 7789f39c9d handle some messages 2018-12-04 18:20:23 -05:00
Kevin Darby 6e188b4f73 shaping up 2018-12-04 16:37:41 -05:00
Kevin Darby 33e8ff58c5 Merge branch 'master' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-04 09:42:30 -05:00
Kevin Darby 24ae03a9f8 access permissions api so fix session can report fills and orders 2018-12-03 18:19:20 -05:00
Kevin Darby 5fe0335e1a Merge branch 'master' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-03 17:02:34 -05:00
Kevin Darby 6cbf707094 Merge branch 'sdk_doc' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-03 16:06:58 -05:00
Kevin Darby 7e4ad55d9d established a session 2018-12-03 15:32:45 -05:00
Kevin Darby 35a0074014 bts fix44 adapter 2018-12-03 14:49:35 -05:00
Kevin Darby b8dd5c119d Merge branch 'sdk_doc' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-12-03 10:41:34 -05:00
Kevin Darby 8ada73aa64 Merge branch 'sdk_doc' of bitbucket.org:bluetradesys/spark into sdk_doc2 2018-11-30 18:37:39 -05:00
Kevin Darby a16ff30873 lets see how hard this is 2018-11-30 18:32:51 -05:00
823 changed files with 124610 additions and 163115 deletions

No files matched your search

+3 -3
View File
@@ -34,7 +34,7 @@ trace_data/
/doc/user/public/*
.ionide/*
/.ignore
*.rej
CMakeDeps.cmake
/build
.clangd/
*CMakeDeps.cmake
.fake
.ionide
+160 -42
View File
@@ -1,41 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Spark Local",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
"args": [
"run",
"test",
"-a",
"OutrightVanillaOrder"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
"environment": [
{ "name": "TZ", "value": "America/Chicago" }
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "SparkVM Launch",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"test",
"-a",
"[ilink3]"
"run"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
@@ -72,9 +44,10 @@
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
"/home/bts/spark/": "${workspaceRoot}",
"/home/bts/spark_host/": "${workspaceRoot}",
}
},
},
{
"name": "(gdb) Launch",
"type": "cppdbg",
@@ -126,19 +99,87 @@
"externalConsole": true,
"requireExactSource": false
},
{
"name": "sparkcmd Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"vol",
"--server",
//"--rstart", "11/22/2019",
//"--rend", "11/25/2019",
//"--rsymbol", "ES",
//"--rsecuritytype", "Synthetic",
//"--post_process_file_type", "Trade",
//"--upload_to_aws",
],
"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",
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
"program": "C:/Program Files/Microsoft Office/Office16/EXCEL.EXE",
"args": [
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceRoot}/build/Debug/bin/net471/",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Win C++ Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office16/EXCEL.EXE",
"args": [
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
],
"cwd": "${workspaceRoot}/build/Debug/bin/net471/",
"stopAtEntry": false,
"environment": [],
"externalConsole": false
},
{
"name": "Fix Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"fix",
"-order_session",
],
"cwd": "${workspaceRoot}/build/Debug/bin/net471/",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Tests",
"type": "clr",
@@ -154,11 +195,18 @@
"requireExactSource": false
},
{
"name": "Client Attach",
"name": ".NET Framework Attach",
"type": "clr",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}",
"requireExactSource": false
},
{
"name": "Spreader Tests",
"type": "cppdbg",
@@ -365,20 +413,24 @@
"quoteArgs": true
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
"/home/bts/spark_host/": "${workspaceRoot}"
},
"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": {
@@ -389,11 +441,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",
},
]
}
+12 -2
View File
@@ -41,7 +41,6 @@ else ()
-Wno-missing-braces -Wno-ignored-qualifiers \
--std=c++17 -fdiagnostics-color")
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wno-undef")
add_subdirectory(src/server/sparkd)
add_subdirectory(src/lib)
@@ -49,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
@@ -56,11 +56,21 @@ else ()
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
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
COMMENT "Creating server runtime layout"
DEPENDS sparkd data_files_dir spark_client_dir
DEPENDS sparkd data_files_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.1/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.1/linux-x64/publish/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
endif()
endif()
+1
View File
@@ -0,0 +1 @@
[0108/231015.447:ERROR:file_io.cc(89)] ReadExactly: expected 36, observed 0
@@ -68,21 +68,12 @@ To update your Spark SDK simply download an unzip a new [spark_sdk.zip](https://
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.
### Re-build the Dev Container
If you are using Visual Studio Code you also need to rebuild the dev-container so it picks up the new image.
#### Inside VSCode
To re-build from inside Visual Studio Code. Click the 'Remote Explorer' icon on the left hand side.
You should see your spark_sdk project listed under the Containers section. Right click on it and select **rebuild-container**.
#### Command Line
To re-build from the command line run `docker ps -a` and find the row with the spark_sdk image.
<!--
Then stop, remove and restart spark_sdk.
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
54d96cfa3888 spark_sdk "/bin/sh -c 'echo Co…" About a minute ago Up About a minute 127.0.0.1:9000-9001->9000-9001/tcp quizzical_lichterman
```
Then run `docker rm 54d96cfa3888` (use your container id instead of 54d96cfa3888) or `docker rm quizzical_lichterman` (use your container name).
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
``` -->
+1 -1
View File
@@ -1 +1 @@
set(sources /home/pfisher/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+1 -22
View File
@@ -1,29 +1,9 @@
+++
+++
title = "Release notes"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.16.2
### User Visible Change
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
## 2.16.1
### Bug Fixes
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
## 2.16.0
### Bug Fixes
- Solved an issue that could result in incorrect ladder levels
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
### API Changes
- Add C# client side API samples for order entry, position monitoring and market data observation
## 2.15.10
### Bug Fixes
- Solved an issue that could cause the user interface to fail when playing some alert sounds
### API Changes
- Added settlement and position information to the API
- Added the ability for user algos to report custom state to the user interface
- Added the ability for algos to discover all active securities
## 2.15.3
### Bug Fixes
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
@@ -728,4 +708,3 @@ insert_anchor_links = "right"
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
- Corrected cash per point values, used in pnl calculations, for a number of products
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
-6
View File
@@ -1,6 +0,0 @@
{
"sdk": {
"version": "2.1.508",
"rollForward": "latestMajor"
}
}
+77
View File
@@ -23,6 +23,12 @@ 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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_analytics", "src\client\spark_analytics\spark_analytics.csproj", "{22DBF34A-961F-4620-91B8-21B0C257A8C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -219,6 +225,74 @@ 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
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Debug|x64.ActiveCfg = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Debug|x64.Build.0 = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Debug|x86.ActiveCfg = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Debug|x86.Build.0 = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.MinSizeRel|x86.Build.0 = Debug|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Release|Any CPU.Build.0 = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Release|x64.ActiveCfg = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Release|x64.Build.0 = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Release|x86.ActiveCfg = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.Release|x86.Build.0 = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{22DBF34A-961F-4620-91B8-21B0C257A8C6}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -229,4 +303,7 @@ Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal
+19
View File
@@ -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>
@@ -19,24 +20,25 @@
<OutputPath>$(BaseOutputPath)\bin</OutputPath>
<BaseIntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseOutputPath)\obj\$(MSBuildProjectName)</IntermediateOutputPath>
<BaseIntermediateOutputPath Condition=" '$(OS)' != 'Windows_NT' ">$(BaseOutputPath)\linuxobj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<IntermediateOutputPath Condition=" '$(OS)' != 'Windows_NT' ">$(BaseOutputPath)\linuxobj\$(MSBuildProjectName)</IntermediateOutputPath>
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
</PropertyGroup>
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
</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>
@@ -47,6 +49,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=&apos;protoc-gen-service=$(ProtocPluginBase)/plugin.py&apos; \
--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>
+14
View File
@@ -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 -o $2/../$1/bin/netcoreapp3.1/linux-x64 ./sparkcmd/
if [ "$1" == "Release" ]
then
dotnet publish -c $1 -o $2/../$1/bin/netcoreapp3.1/linux-x64 ./sparkcmd/
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.1/linux-x64/publish
fi
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.1/linux-x64
@@ -1,193 +0,0 @@
using bts;
using bts.api;
using bts.api.core;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class AlgoProviderFactory {
[ImportingConstructor]
AlgoProviderFactory(IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
var from_conf = config.Configuration.get<bts.config.AlgoTypeRuleSet>();
if (from_conf.rules.Count == 0) {
// There seems to be a config race bug, where the returned config values are empty, despite having entries in spark.conf.
// [John]I've only noticed it happen when attached to the debugger,
// but best to have a contingency plan in place anyways, to prevent any chance of regression.
// The fallback sets below mimic the old hardcoded algo type arrays.
var full_algo_set = new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(EnumHelper.get_values_except(
// non-factory algos
AlgoType.None,
AlgoType.Dynamic,
AlgoType.Composite,
// obsolete algos
AlgoType.Oco,
AlgoType.TimeBox,
AlgoType.Pricer,
AlgoType.Tick,
AlgoType.Trigger,
AlgoType.QueueHolder)),
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
var rules = new[] {
// Glbx: Futures/Spreads
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
exchanges: new[] { Venue.Glbx },
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
new AlgoTypeSetImmutable(
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader }),
full_algo_set.dynamic_types)),
// Gen: Futures/Spreads
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
exchanges: EnumHelper.get_values_except(Venue.Glbx, Venue.Unset).ToArray(),
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
new AlgoTypeSetImmutable(
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader, AlgoType.GlbxIceberg }),
full_algo_set.dynamic_types)),
// Gen: Synthetics
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
security_types: new[] { SecurityType.Synthetic }),
new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(new[] {
AlgoType.Spreader,
AlgoType.Iceberg,
AlgoType.Lean,
AlgoType.Join,
AlgoType.Cover,
AlgoType.Offset,
AlgoType.Top,
AlgoType.Splitter,
AlgoType.Scratch,
AlgoType.Prorata
}),
full_algo_set.dynamic_types))
};
// attachable
var attachable_algos =
ImmutableHashSet.CreateRange(new[] {
AlgoType.Iceberg,
AlgoType.Lean,
AlgoType.Join,
AlgoType.Cover,
AlgoType.Top,
AlgoType.Offset,
AlgoType.Scratch,
AlgoType.Prorata
});
var attachable_rules = Array.ConvertAll(rules,
satsi => new SecurityAlgoTypeSetImmutable(
satsi.security_criteria,
new AlgoTypeSetImmutable(
satsi.algo_type_set.factory_types.Intersect(attachable_algos),
satsi.algo_type_set.dynamic_types)));
Log.Config.warn($"Failed to load AlgoTypes config, falling back to the following algo set rules:" +
"\n\tStandard\n" +
rules.fmt_diagnostic("\n\t\t") +
"\n\n\tAttachable\n" +
attachable_rules.fmt_diagnostic("\n\t\t"));
security_algo_provider = new AlgoProvider(rules, studio_model, dynamic_helper, security_auxiliary_model);
attachable_algo_provider = new AttachableAlgoProvider(attachable_rules, studio_model, dynamic_helper, security_auxiliary_model);
} else {
var algo_type_data =
EnumHelper.get_values_except(AlgoType.Dynamic, AlgoType.Composite, AlgoType.None)
.Select(t => _get_type_data(t))
.Concat(dynamic_helper.dynamic_types.Select(dt => _get_type_data(AlgoType.Dynamic, dt)))
.ToListLazy();
var conf_sets = from_conf.rules.Select(r => _to_security_algo_set(r, dynamic_helper, algo_type_data)).ToList();
security_algo_provider = new AlgoProvider(conf_sets.Select(tup => tup.standard).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
attachable_algo_provider = new AttachableAlgoProvider(conf_sets.Select(tup => tup.attachable).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
}
}
[Export]
public IAlgoProvider security_algo_provider { get; }
[Export]
public IAttachableAlgoProvider attachable_algo_provider { get; }
static (string name, AlgoType factory, string dynamic) _get_type_data(AlgoType type, string dynamic_type = null) {
string type_name = type.ToString();
return (type_name, type, dynamic_type ?? type_name);
}
static SecurityCriteriaClause _to_clause(bts.config.AlgoTypeContext context) =>
context == null ?
new SecurityCriteriaClause() :
new SecurityCriteriaClause(
exchanges: context.exchanges?.Select(EnumHelper.parse<Venue>)?.ToArray(),
security_types: context.security_types?.Select(EnumHelper.parse<Security.ContractOneofCase>)?.Select(t => t.security_type())?.ToArray(),
symbols: context.symbols?.ToArray());
static (SecurityAlgoTypeSetImmutable standard, SecurityAlgoTypeSetImmutable attachable) _to_security_algo_set(
bts.config.AlgoTypeRule rule,
DynamicOrderHelper dynamic_helper,
IEnumerable<(string name, AlgoType factory, string dynamic)> algo_type_data) {
//
var factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
var dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
var attachable_factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
var attachable_dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
foreach (var a in rule.algos) {
if (Enum.TryParse(a.name, out AlgoType sys_algo) && sys_algo != AlgoType.Composite && sys_algo != AlgoType.Dynamic) {
on_sys_match(sys_algo, a.is_attachable);
}
else if (dynamic_helper.try_parse(a.name, out var dyn_type)) {
on_dyn_match(dyn_type, a.is_attachable);
} else {
foreach (var (name, factory, dynamic) in algo_type_data) {
if (Regex.Match(name, a.name).Success) {
switch (factory) {
case AlgoType.None:
case AlgoType.Composite:
// unexpected, but no need to crash
continue;
case AlgoType.Dynamic:
on_dyn_match(dynamic, a.is_attachable);
break;
default:
on_sys_match(factory, a.is_attachable);
break;
}
}
}
}
}
return (
// standard
new SecurityAlgoTypeSetImmutable(
_to_clause(rule.context),
new AlgoTypeSetImmutable(
factory_types.ToImmutable(),
dynamic_types.ToImmutable())),
// attachable
new SecurityAlgoTypeSetImmutable(
_to_clause(rule.context),
new AlgoTypeSetImmutable(
attachable_factory_types.ToImmutable(),
attachable_dynamic_types.ToImmutable())));
// local methods
void on_sys_match(AlgoType sys_algo, bool is_attachable) {
factory_types.Add(sys_algo);
if (is_attachable)
attachable_factory_types.Add(sys_algo);
}
void on_dyn_match(string dyn_algo, bool is_attachable) {
dynamic_types.Add(dyn_algo);
if (is_attachable)
attachable_dynamic_types.Add(dyn_algo);
}
}
}
}
@@ -1,132 +0,0 @@
using bts.api.core;
using bts.api.orderman;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
readonly struct AlgoTypeSetImmutable {
public static readonly AlgoTypeSetImmutable empty = new AlgoTypeSetImmutable(ImmutableHashSet<AlgoType>.Empty, ImmutableHashSet<string>.Empty);
public readonly ImmutableHashSet<AlgoType> factory_types;
public readonly ImmutableHashSet<string> dynamic_types;
public AlgoTypeSetImmutable(
ImmutableHashSet<AlgoType> factory_types,
ImmutableHashSet<string> dynamic_types) {
this.factory_types = factory_types;
this.dynamic_types = dynamic_types;
}
}
readonly struct SecurityAlgoTypeSetImmutable {
public readonly SecurityCriteriaClause security_criteria;
public readonly AlgoTypeSetImmutable algo_type_set;
public SecurityAlgoTypeSetImmutable(SecurityCriteriaClause security_criteria, AlgoTypeSetImmutable algo_type_set, CustomOrderDefinitionImmutable? default_algo = null) {
this.security_criteria = security_criteria;
this.algo_type_set = algo_type_set;
}
}
static class AlgoTypeSetExtensions {
public static string fmt_diagnostic(this IEnumerable<SecurityAlgoTypeSetImmutable> rules, string line_delim = "\n") {
var sb = new StringBuilder();
int rule_count = 1;
using (var en = rules.GetEnumerator()) {
if (en.MoveNext()) {
string line_delim_tab1 = line_delim + "\t";
string line_delim_tab2 = line_delim + "\t\t";
append_rule(en.Current);
while (en.MoveNext()) {
sb.AppendLine();
append_rule(en.Current);
}
//
void append_rule(SecurityAlgoTypeSetImmutable r) {
sb.Append(line_delim + $"**RULE {rule_count++}**");
sb.Append(line_delim_tab1);
sb.Append("Security Criteria: ");
sb.Append(r.security_criteria.is_defined ? string.Join("; ", r.security_criteria.fmt_diagnostic_msgs()) : "(Undefined)");
sb.Append(line_delim_tab1);
// Factory
sb.Append("Factory Types:");
sb.Append(line_delim_tab2);
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.factory_types));
sb.Append(line_delim_tab1);
// Dynamic
sb.Append("Dynamic Types:");
sb.Append(line_delim_tab2);
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.dynamic_types));
}
}
}
return sb.ToString();
}
public static AlgoTypeSetImmutable intersect_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
new AlgoTypeSetImmutable(a.factory_types.Intersect(b.factory_types), a.dynamic_types.Intersect(b.dynamic_types));
public static AlgoTypeSetImmutable union_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
new AlgoTypeSetImmutable(a.factory_types.Union(b.factory_types), a.dynamic_types.Union(b.dynamic_types));
public static IEnumerable<AlgoData> define_algos(
this AlgoTypeSetImmutable algo_set,
DynamicOrderHelper dynamic_helper,
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
uint display_qty_divisor = 1) =>
//
algo_set.factory_types
.Select(algo_type => {
var def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(algo_type);
if (fit_to_type != null)
def = fit_to_type(def);
// multiply quantity props by display qty divisor to get appropriate actual value
if (display_qty_divisor > 1) {
switch (def.CustomCase) {
case AlgoType.Iceberg:
var mdef = def.CloneToMutable();
mdef.Iceberg.ShowQuantity *= display_qty_divisor;
def = new CustomOrderDefinitionImmutable(mdef);
break;
}
}
return def;
})
.Concat(algo_set.dynamic_types.Select(dynamic_helper.create_custom_defi))
.Select(AlgoData.from_custom_def);
public static ImmutableDictionary<AlgoKey, AlgoData> match_algos(
this IEnumerable<SecurityAlgoTypeSetImmutable> security_algo_sets,
SecurityCriteriaClause clause,
DynamicOrderHelper dynamic_helper,
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
uint display_qty_divisor = 1) {
//
ImmutableDictionary<AlgoKey, AlgoData> rv = null;
foreach (var set in security_algo_sets) {
if (!clause.is_subset_of(set.security_criteria)) {
continue;
} else if (rv == null) {
rv = ImmutableDictionary.CreateRange(
set.algo_type_set.define_algos(dynamic_helper, fit_to_type, display_qty_divisor)
.Select(a => new KeyValuePair<AlgoKey, AlgoData>(a.key, a)));
} else {
foreach (var kv in rv) {
if (kv.Value.value.CustomCase == AlgoType.Dynamic) {
if (!set.algo_type_set.dynamic_types.Contains(kv.Value.value.Dynamic.DynamicOrderType))
rv = rv.Remove(kv.Key);
} else if (!set.algo_type_set.factory_types.Contains(kv.Value.value.CustomCase))
rv = rv.Remove(kv.Key);
}
}
}
return rv ?? ImmutableDictionary<AlgoKey, AlgoData>.Empty;
}
}
}
@@ -1,234 +0,0 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
interface IAlgoProvider {
AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security);
AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause);
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos();
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security);
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause clause);
}
// This interface is used to distinguish the attachable algo provider
// from the standard one wrt MEF export/import.
interface IAttachableAlgoProvider : IAlgoProvider { }
class AlgoProvider : IAlgoProvider {
readonly IEnumerable<SecurityAlgoTypeSetImmutable> _security_algo_types;
readonly IAlgoStudioModel _studio_model;
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
readonly IObservable<TableUpdate<AlgoData, AlgoKey>> _studio_algos_source;
readonly DynamicOrderHelper _dynamic_helper;
readonly AlgoTypeSetImmutable _full_set;
public AlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
_security_algo_types = algo_sets;
_studio_model = studio_model;
_dynamic_helper = dynamic_helper;
_full_set = new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(
EnumHelper.get_values_except(CustomOrderDefinition.CustomOneofCase.None, CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite)),
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
_security_auxiliary_model = security_auxiliary_model;
/// initialize <see cref="_studio_algos_source"/> with an empty table, so the
/// <see cref="UpdateExtensions.AppendTable{T, TKey}(IObservable{TableUpdate{T, TKey}}, IEnumerable{T})"/>
/// calls made by the 'subscribe_algos' methods will fire at least once, even if the underlying
/// studio algos stream is empty have been created (this would be the case if the user had not yet created any studio algos).
/// <see cref="subscribe_algos"/>,
/// <see cref="subscribe_algos(SecurityCriteriaClause)"/>,
/// <see cref="subscribe_algos(SecurityImmutable)"/>
_studio_algos_source = studio_model.definitions
.StartWith(TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn))
.Cache();
}
public AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security) =>
_security_algo_types.Where(p => p.security_criteria.includes(security))
.Select(sec_algos => sec_algos.algo_type_set)
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
public AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause) =>
_security_algo_types.Where(p => p.security_criteria.is_subset_of(clause))
.Select(sec_algos => sec_algos.algo_type_set)
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos() =>
_studio_algos_source
.AppendTable(_security_algo_types
.Select(s => s.algo_type_set)
.Aggregate(AlgoTypeSetImmutable.empty, (a, b) => a.union_with(b))
.define_algos(_dynamic_helper));
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause security_clause) {
var system_algos = _security_algo_types.match_algos(
security_clause,
_dynamic_helper,
def => _fit_to_type(def, security_clause, _security_algo_types));
return _studio_algos_source.Select(
a => (algo: a, fitted_def: _fit_to_type(a.value, security_clause, _security_algo_types)),
tup => tup.algo.key)
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), a => a.key)
.AppendTable(system_algos.Values);
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security) {
var clause = new SecurityCriteriaClause(
exchanges: new[] { security.Exchange },
security_types: new[] { security.security_type() });
// i.e., non-studio algos
var system_algos = _security_algo_types.match_algos(
clause,
_dynamic_helper,
def => _fit_to_type(def, security, _security_algo_types),
_security_auxiliary_model.get_display_quantity_divisor(security));
return _studio_algos_source.Select(
a => (algo: a, fitted_def: _fit_to_type(a.value, security, _security_algo_types)),
tup => tup.algo.key)
// filter out algos that use an unsupported root, because this would have the effect of duplicating the default algo
// (e.g., GlbxIceberg orders would be converted to Limit/Spreader orders for non-Glbx securities).
.Filter(tup => tup.fitted_def.actual().CustomCase == tup.algo.value.actual().CustomCase, tup => tup.algo.key)
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), AlgoData.key_fn)
.AppendTable(system_algos.Values);
}
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) {
if (security_clause.security_types.empty_if_null().Contains(Security.ContractOneofCase.Synthetic.security_type())) {
mdef.Spreader = new SpreaderOrderDefinition();
} else {
mdef.Limit = new LimitOrderDefinition();
}
}
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) {
if (security.ContractCase == Security.ContractOneofCase.Synthetic) {
mdef.Spreader = new SpreaderOrderDefinition {
Security = security.CloneToMutable()
};
} else {
mdef.Limit = new LimitOrderDefinition();
}
}
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
var mroot = defi.CloneToMutable();
if (_fit_to_type(mroot, security_clause, algo_sets)) {
defi = new CustomOrderDefinitionImmutable(mroot);
}
return defi;
}
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
var mroot = defi.CloneToMutable();
if (_fit_to_type(mroot, security, algo_sets)) {
defi = new CustomOrderDefinitionImmutable(mroot);
}
return defi;
}
bool _fit_to_type(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
mdef = mdef.Composite.Definition;
var sec_sets = algo_sets.Where(s => security_clause.is_subset_of(s.security_criteria));
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
if (!algo_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
assign_fallback_algo(mdef, security_clause);
return true;
}
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
assign_fallback_algo(mdef, security_clause);
return true;
}
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security_clause, algo_sets)) > 0;
}
bool _fit_to_type(CustomOrderDefinition mdef, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
mdef = mdef.Composite.Definition;
var sec_sets = algo_sets.Where(s => s.security_criteria.includes(security));
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
if (!sec_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
assign_fallback_algo(mdef, security);
return true;
}
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
assign_fallback_algo(mdef, security);
return true;
}
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security, algo_sets)) > 0;
}
}
class AttachableAlgoProvider : AlgoProvider, IAttachableAlgoProvider {
public AttachableAlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model)
: base(algo_sets, studio_model, dynamic_helper, security_auxiliary_model) { }
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) =>
mdef.Limit = new LimitOrderDefinition();
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) =>
mdef.Limit = new LimitOrderDefinition();
}
static class AlgoProviderExtensions {
// This stream filters out composite algos that are not subsets of the non-composite algo types in the table
// (normally, such composites are 'auto-fit,' meaning the algo nodes that are not part of the set are replaced with ones that are).
// For example, consider a composite algo the following structure: Offset -> GlbxIceberg.
// If an update arrives with these noncomposite types: "Iceberg, Limit, Market," that composite algo is not a subset of the non-composite algo types,
// because GlbxIceberg is not in the table, so it would only be added to the table if/when GlbxIceberg arrives.
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(this IAlgoProvider algo_provider, bool allow_undefined_child = false) =>
subscribe_complete_algos(algo_provider.subscribe_algos(), allow_undefined_child);
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(IObservable<TableUpdate<AlgoData, AlgoKey>> source, bool allow_undefined_child = false) =>
Observable.Create<TableUpdate<AlgoData, AlgoKey>>(o => {
var last_out = TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn);
var incomplete_composites = ImmutableHashSet<AlgoData>.Empty;
return source.Subscribe(table => {
var bd = last_out.to_builder();
foreach (var algo in incomplete_composites.Where(a => _validate_algo_structure(a.value, table.state, allow_undefined_child))) {
incomplete_composites = incomplete_composites.Remove(algo);
bd.add_or_update(algo);
}
foreach (var a in table.removed.Concat(table.changes.Old()))
bd.remove(a.key);
foreach (var a in table.added.Concat(table.changed)) {
if (_validate_algo_structure(a.value, table.state, allow_undefined_child)) {
bd.add_or_update(a);
} else {
incomplete_composites = incomplete_composites.Add(a);
}
}
o.OnNext(last_out = bd.to_table());
});
});
static bool _validate_algo_structure(CustomOrderDefinitionImmutable custom_def, IReadOnlyDictionary<AlgoKey, AlgoData> allowed_algo_parts, bool allow_undefined_child) =>
OrderDataHelper.enumerate_algo_structure(custom_def)
.Select((algo_node, index) =>
// None is an acceptable algo type as long as it's not the root algo (i.e., index > 0)
(index > 0 && allow_undefined_child && algo_node.type == CustomOrderDefinition.CustomOneofCase.None) || _is_part_contained(algo_node, allowed_algo_parts))
.All();
static bool _is_part_contained((CustomOrderDefinition.CustomOneofCase type, string dyn, uint studio_id, uint spreader_sec) structure, IReadOnlyDictionary<AlgoKey, AlgoData> state) =>
structure.type == CustomOrderDefinition.CustomOneofCase.Composite ||
structure.type == CustomOrderDefinition.CustomOneofCase.Dynamic ?
state.ContainsKey(AlgoKey.from_dynamic_type(structure.dyn)) :
state.ContainsKey(AlgoKey.from_known_type(structure.type));
}
}
-2
View File
@@ -27,7 +27,6 @@ namespace mbts.ui {
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
CurrencyExchangeGeometryKey = nameof(CurrencyExchangeGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
@@ -162,7 +161,6 @@ namespace mbts.ui {
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
public static ImageSource CopyBoxes => create_icon(CopyBoxesGeometryKey, null, _copy_boxes_pen);
public static ImageSource CurrencyExchange => create_icon(CurrencyExchangeGeometryKey);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
@@ -62,10 +62,6 @@
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.CurrencyExchangeGeometryKey}">
M0,24L15.324005,24C16.042007,24.785004,16.875,25.460999,17.800003,26L2,26 2,30 23.087997,30 23.087006,27.424988C23.771011,27.423004,24.439011,27.352997,25.085999,27.225983L25.085999,30C25.085999,31.10199,24.189011,32,23.085999,32L2,32C0.89700317,32,0,31.10199,0,30z M22.484015,13.026998L23.475018,13.026998 23.475018,13.856997C24.151018,13.885996,24.605021,14.016996,24.93902,14.167996L24.646021,15.233995C24.38302,15.129995 23.919018,14.912995 23.191017,14.912995 22.535015,14.912995 22.323015,15.176995 22.323015,15.440995 22.323015,15.751994 22.676016,15.950994 23.535017,16.251994 24.73702,16.647994 25.222021,17.166993 25.222021,18.015992 25.222021,18.854991 24.58502,19.571991 23.424017,19.759991L23.424017,20.72199 22.434015,20.72199 22.434015,19.825991C21.757013,19.797991,21.101011,19.627991,20.71701,19.420991L21.019012,18.317992C21.444014,18.533992 22.040014,18.731992 22.697016,18.731992 23.272017,18.731992 23.666018,18.524992 23.666018,18.147992 23.666018,17.788992 23.343018,17.562993 22.596016,17.326993 21.515013,16.987993 20.777012,16.516994 20.777012,15.601995 20.777012,14.771996 21.404013,14.119996 22.484015,13.922997z M0.065009117,12.384011L13.628021,12.384011C13.320021,13.018011,13.075021,13.687011,12.89902,14.384011L2.065011,14.384011 2.065011,18.384011 12.68002,18.384011C12.77602,19.075011,12.93702,19.743011,13.16102,20.384011L0.065009117,20.384011z M23.060013,10.460008C19.480015,10.460008 16.568016,13.368006 16.568016,16.943003 16.568016,20.519001 19.480015,23.426999 23.060013,23.427 26.640011,23.426999 29.55201,20.519001 29.55201,16.943003 29.55201,13.368006 26.640011,10.460008 23.060013,10.460008z M23.060013,8.4600086C27.74201,8.4600086 31.552008,12.266006 31.552008,16.943003 31.552008,21.621 27.74201,25.426998 23.060013,25.426998 18.378014,25.426998 14.568016,21.621 14.568016,16.943003 14.568016,12.266006 18.378014,8.4600086 23.060013,8.4600086z M2.0000181,2L2.0000181,6 23.08602,6 23.08602,2z M2.0000181,0L23.08602,0C24.18902,0,25.08602,0.89699936,25.08602,2L25.08602,6.6599998C24.430019,6.5310001 23.753019,6.4599991 23.060019,6.4599991 21.06402,6.4599991 19.20402,7.0300007 17.614019,8L1.8119812E-05,8 1.8119812E-05,2C1.8066406E-05,0.89699936,0.89701813,0,2.0000181,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
</sys:String>
@@ -280,7 +276,6 @@
m464.867188 0c-25.988282 0-47.132813 21.144531-47.132813 47.132812v313.75c0 49.324219 11.660156 98.710938 33.71875 142.824219 2.539063 5.082031 7.734375 8.292969 13.414063 8.292969 5.683593 0 10.875-3.210938 13.417968-8.292969 22.054688-44.113281 33.714844-93.503906 33.714844-142.824219v-313.75c0-25.988281-21.144531-47.132812-47.132812-47.132812zm0 30c9.445312 0 17.132812 7.6875 17.132812 17.132812v33.199219h-34.265625v-33.199219c0-9.445312 7.683594-17.132812 17.132813-17.132812zm17.132812 323.46875h-34.265625v-243.136719h34.265625zm-17.132812 105.4375c-8.71875-24.277344-14.1875-49.734375-16.222657-75.4375h32.445313c-2.035156 25.707031-7.507813 51.160156-16.222656 75.4375zm0 0
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CheckMarkGeometryKey}">
F1M9.97498,1.22334L4.6983,9.09834 4.52164,9.09834 0,5.19331 1.27664,3.52165 4.255,6.08833 8.33331,1.52588E-05 9.97498,1.22334z
</sys:String>
@@ -17,7 +17,7 @@ namespace bts.shell {
class MarketShellModule : IShellModule {
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub, orders_model.security_auxiliary_model);
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -1,12 +1,10 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.co2;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -26,6 +24,7 @@ namespace mbts.spark {
readonly AnimationManager _animation = new AnimationManager();
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
readonly string _security_text, _security_key_text;
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
readonly Property<bool> _last_price_from_spread;
readonly CellProperty<int> _position;
@@ -34,51 +33,48 @@ namespace mbts.spark {
readonly CellProperty<bool> _inside_bids, _inside_asks;
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
readonly CellProperty<double> _working_best_bid, _working_best_ask;
readonly CellProperty<string> _working_bids_text, _working_asks_text;
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
PropertyAutoWrapper<uint, uint>
_bid_direct_size,
_bid_implied_size,
_bid_merged_size,
_ask_direct_size,
_ask_implied_size,
_ask_merged_size;
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
double _raw_day_position;
string _bid_merged_size_text, _ask_merged_size_text;
ulong _bbo_ref, _last_ref;
uint? _display_qty_divisor_backing;
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
_bus = bus;
_orders_model = orders_model;
_security_text = smd.security.ToString();
_security_key_text = security_key.ToString();
string format_price(double price) => security.ContractSpec.format(price);
_position = new CellProperty<int>(this, nameof(position), fmt_display_qty_int);
_position = new CellProperty<int>(this, nameof(position), 0);
_bid_price = backing(nameof(bid_price), double.NaN);
_ask_price = backing(nameof(ask_price), double.NaN);
_last_price = backing(nameof(last_price), double.NaN);
_last_size = new CellProperty<uint>(this, nameof(last_size), fmt_display_qty_uint);
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
_day_change = backing(nameof(day_change), double.NaN);
_session_low = backing(nameof(session_low), double.NaN);
_session_high = backing(nameof(session_high), double.NaN);
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
_settlement = backing(nameof(settlement), double.NaN);
_volume = new CellProperty<ulong>(this, nameof(volume), fmt_display_qty_ulong, 0);
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
//
_bid_direct_size = backing(nameof(bid_direct_size), 0u, convert_display_qty_uint);
_bid_implied_size = backing(nameof(bid_implied_size), 0u, convert_display_qty_uint);
_bid_merged_size = backing(nameof(bid_merged_size), 0u, convert_display_qty_uint);
_ask_direct_size = backing(nameof(ask_direct_size), 0u, convert_display_qty_uint);
_ask_implied_size = backing(nameof(ask_implied_size), 0u, convert_display_qty_uint);
_ask_merged_size = backing(nameof(ask_merged_size), 0u, convert_display_qty_uint);
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
//
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
//
default_command = new DelegateCommand(_show_ladder);
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
@@ -94,45 +90,17 @@ namespace mbts.spark {
_animation.AnimationsChanged += on_animations_changed;
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
_dispose = Disposable.Create(
attach_prop_changed_cb(_bid_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_direct_size_text))),
attach_prop_changed_cb(_bid_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_implied_size_text))),
attach_prop_changed_cb(_bid_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_merged_size_text))),
attach_prop_changed_cb(_ask_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_direct_size_text))),
attach_prop_changed_cb(_ask_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_implied_size_text))),
attach_prop_changed_cb(_ask_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_merged_size_text))),
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position))) {
RaisePropertyChanged(nameof(raw_day_position_text));
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
raise_invalidated();
}
}),
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
);
uint convert_display_qty_uint(uint value) =>
value.safe_divide(_display_qty_divisor);
string fmt_display_qty_int(int value) =>
value.safe_divide(_display_qty_divisor).ToString();
string fmt_display_qty_uint(uint value) =>
value.safe_divide(_display_qty_divisor).ToString();
string fmt_display_qty_ulong(ulong value) =>
value.safe_divide(_display_qty_divisor).ToString();
PropertyChange<T> mk_echo_prop_changed_cb<T>(params string[] props) =>
(oldval, newval) => RaisePropertyChanged(props);
IDisposable attach_prop_changed_cb<TView, TModel>(PropertyAutoWrapper<TModel, TView> prop, PropertyChange<(TModel, TView)> callback) {
prop.changed += callback;
return Disposable.Create(() => prop.changed -= callback);
}
}
public event Action invalidated;
@@ -141,63 +109,32 @@ namespace mbts.spark {
public bool inside_bids => _inside_bids.value;
public bool inside_asks => _inside_asks.value;
public int position => _position.value;
public string position_text => _position.text;
public double raw_day_position => _raw_day_position;
public string raw_day_position_text => SparkUtils.fmt_position(_raw_day_position.safe_divide(_display_qty_divisor));
public double last_price => _last_price.Value;
public bool last_price_from_spread => _last_price_from_spread.Value;
public uint last_size => _last_size.value;
public string last_size_text => _last_size.text;
// bid price/size
public double bid_price => _bid_price.Value;
public uint bid_direct_size => _bid_direct_size.model;
public uint bid_implied_size => _bid_merged_size.model - _bid_direct_size.model;
public uint bid_merged_size => _bid_merged_size.model;
// bid size text
public string bid_direct_size_text => _bid_direct_size.view.ToString();
public string bid_implied_size_text => _bid_implied_size.view.ToString();
public string bid_merged_size_text => _bid_merged_size.view.ToString();
// ask price/size
public uint bid_direct_size => _bid_direct_size.Value;
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
public uint bid_merged_size => _bid_merged_size.Value;
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
public double ask_price => _ask_price.Value;
public uint ask_direct_size => _ask_direct_size.model;
public uint ask_implied_size => _ask_implied_size.model;
public uint ask_merged_size => _ask_merged_size.model;
// ask size text
public string ask_direct_size_text => _ask_direct_size.view.ToString();
public string ask_implied_size_text => _ask_implied_size.view.ToString();
public string ask_merged_size_text => _ask_merged_size.view.ToString();
//
public uint ask_direct_size => _ask_direct_size.Value;
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
public uint ask_merged_size => _ask_merged_size.Value;
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
public ulong volume => _volume.value;
public string volume_text => _volume.text;
public double settlement => _settlement.Value;
public double day_change => _day_change.Value;
public double session_low => _session_low.Value;
public double session_high => _session_high.Value;
public int working_bid_volume => _working_bid_volume.value;
public int working_ask_volume => _working_ask_volume.value;
public string working_bids_text => OrderUtils.order_collection_summary(
_order_summarizer.best_working_bid,
_order_summarizer.best_working_bid_qty.safe_divide(_display_qty_divisor),
_order_summarizer.working_bid_qty.safe_divide(_display_qty_divisor),
security.ContractSpec);
public string working_asks_text => OrderUtils.order_collection_summary(
_order_summarizer.best_working_ask,
_order_summarizer.best_working_ask_qty.safe_divide(_display_qty_divisor),
_order_summarizer.working_ask_qty.safe_divide(_display_qty_divisor),
security.ContractSpec);
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
public double working_best_bid => _working_best_bid.value;
public double working_best_ask => _working_best_ask.value;
// ice props
public string market_description => security.market_description();
public string cleared_alias => security.cleared_alias();
public string product_description => security.product_name();
public string hub_alias => security.hub_alias();
public string strip_name => security.strip_name();
public string mic_code => security.mic_code();
// animations
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
// commands
public ICommand bid_price_click { get; }
public ICommand bid_merged_size_click { get; }
public ICommand bid_direct_size_click { get; }
@@ -209,30 +146,27 @@ namespace mbts.spark {
public ICommand ask_implied_size_click { get; }
public ICommand working_asks_click { get; }
public ICommand default_command { get; }
public string market_description {get;}
public string cleared_alias { get; }
public string product_description { get; }
public string hub_alias { get; }
public string strip_name{ get; }
public string mic_code { get; }
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
#endregion
uint _display_qty_divisor => _display_qty_divisor_backing ??
(_display_qty_divisor_backing = _orders_model.security_auxiliary_model.get_display_quantity_divisor(security)).Value;
public void raise_invalidated() => invalidated?.Invoke();
public void refresh() {
bool dirty = false;
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
_bid_price.set(b.price);
// don't use the '||' operator - every 'set_new' call must be made
// (the operation mustn't terminate the first time a set_new call returns true).
dirty |= (
_bid_direct_size.set_new(b.direct_size) |
_bid_implied_size.set_new(b.implied_size) |
_bid_merged_size.set_new(b.size) |
_ask_direct_size.set_new(a.direct_size) |
_ask_implied_size.set_new(a.implied_size) |
_ask_merged_size.set_new(a.size));
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
_ask_price.set(a.price);
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
}
@@ -249,6 +183,16 @@ namespace mbts.spark {
_volume.on_next_distinct(md.md.total_volume, ref dirty);
if (dirty)
invalidated?.Invoke();
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
merged.set(nps.size);
direct.set(nps.direct_size);
text = null;
dirty = true;
RaisePropertyChanged(text_prop);
}
}
}
public bool Equals(Row other) =>
@@ -9,7 +9,6 @@ using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Data;
using System.Windows.Media;
@@ -19,9 +18,6 @@ using static mbts.ui.spark.MarketViewView;
namespace mbts.spark {
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
static IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>();
readonly IBus _bus;
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
@@ -41,21 +37,21 @@ namespace mbts.spark {
const double working_orders_col_width = 105;
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Volume, "Volume", r => r.volume_text, nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size_text, nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position_text, nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => r.raw_day_position_text, nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size_text, nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size_text, nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size_text, nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size_text, nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
@@ -155,16 +151,14 @@ namespace mbts.spark {
working_ask_volume_filter.load_state(filters.working_ask_volume);
volume_filter.load_state(filters.volume);
security_key_filter.load_state(filters.security_key);
market_description_filter.load_state(filters.market_description);
cleared_alias_filter.load_state(filters.cleared_alias);
product_description_filter.load_state(filters.product_description);
hub_alias_filter.load_state(filters.hub_alias);
strip_name_filter.load_state(filters.strip_name);
mic_code_filter.load_state(filters.mic_code);
}
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
}
}), DataGridFilterHelper.hook_filters(rows_view, _filters),
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
);
@@ -239,15 +233,13 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.market_description, nameof(Row.market_description));
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.cleared_alias, nameof(Row.cleared_alias));
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.product_description, nameof(Row.product_description));
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.hub_alias, nameof(Row.hub_alias));
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.strip_name, nameof(Row.strip_name));
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.mic_code, nameof(Row.mic_code));
IEnumerable<IDataGridColumnFilter<Row>> _filters =>
_filter_getters.Select(fn => fn(this));
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
public FilterStates get_filter_states() {
return new FilterStates {
@@ -22,15 +22,12 @@ namespace bts.shell.time_and_sales {
.to_safe();
readonly TradeMessageImmutable _trade;
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
uint? _display_size;
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade) {
this.row_key = row_key;
this.security = security;
_trade = trade;
trade_key = trade.Key;
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
}
public event Action invalidated;
@@ -42,8 +39,6 @@ namespace bts.shell.time_and_sales {
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
public double price => _trade.price();
public uint size => _trade.Size;
public uint display_size => _display_size ??
(_display_size = size.safe_divide(_get_display_qty_divisor(security))).Value;
public AggressorSide aggressor_side => _trade.AggressorSide;
public void raise_invalidated() =>
@@ -40,7 +40,6 @@ namespace bts.shell.time_and_sales {
_refresh_filter,
_refresh_sort,
_refresh_selected_rows;
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
IComparer<TimeAndSalesRowVM> _comparer_backing;
Func<TimeAndSalesRowVM, bool> _filter_backing;
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
@@ -59,9 +58,8 @@ namespace bts.shell.time_and_sales {
_filter_change_count,
_sort_change_count;
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager) {
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
_settings_manager = settings_manager;
@@ -97,7 +95,7 @@ namespace bts.shell.time_and_sales {
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Size,
r => r.display_size.ToString(),
r => r.size.ToString(),
DefaultColumnHeaders.Size,
DefaultColumnWidths.Size,
size_filter,
@@ -306,7 +304,7 @@ namespace bts.shell.time_and_sales {
}
var all_rows_bd = _all_rows.ToBuilder();
foreach (var t in trades) {
var r = _row_factory.create(t.security, t.trade, _get_display_qty_divisor);
var r = _row_factory.create(t.security, t.trade);
all_rows_bd.Add(r);
if (_filter_backing == null || _filter_backing(r))
rows.InsertSorted(r, _comparer);
@@ -402,11 +400,11 @@ namespace bts.shell.time_and_sales {
// the row key must be unique to each row in the collection
int _row_id_counter;
public TimeAndSalesRowVM create(TradeInfo trade_info, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade, get_display_qty_divisor);
public TimeAndSalesRowVM create(TradeInfo trade_info) =>
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade);
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
new TimeAndSalesRowVM(_row_id_counter++, security, trade, get_display_qty_divisor);
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade) =>
new TimeAndSalesRowVM(_row_id_counter++, security, trade);
}
}
}
@@ -26,7 +26,6 @@ namespace bts.shell.time_and_sales {
readonly TimeAndSalesWindowFactory _factory;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly bool _can_pause_updates;
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
TimeAndSalesVM _vm;
TimeAndSalesModel _model;
SecurityCriteria _security_criteria_backing;
@@ -36,21 +35,12 @@ namespace bts.shell.time_and_sales {
SparkMenuDisplayVM _menu_display_vm;
IDisposable _subscriptions;
public TimeAndSalesWindow(
TimeAndSalesWindowFactory factory,
IBus bus,
MDConsumer consumer,
TimeAndSalesSettings state,
ImageSource icon,
IWindowSettingsManager<GridSettings> settings_manager,
bool can_pause_updates,
Func<SecurityImmutable?, uint> get_display_qty_divisor)
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, TimeAndSalesSettings state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager, bool can_pause_updates)
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
Log.assert(state != null);
_consumer = consumer;
_factory = factory;
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
if (_state.security_criteria == null)
_state = _state.with(new SecurityCriteria());
_security_criteria_backing = _state.security_criteria;
@@ -96,7 +86,7 @@ namespace bts.shell.time_and_sales {
_settings_manager.instance_settings = _state.grid;
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_vm = new TimeAndSalesVM(_settings_manager, _get_display_qty_divisor);
_vm = new TimeAndSalesVM(_settings_manager);
var selected_rows_src = Observable.Create<ImmutableHashSet<TimeAndSalesRowVM>>(o => {
_vm.selected_rows_changed += o.OnNext;
return Disposable.Create(() => _vm.selected_rows_changed -= o.OnNext);
@@ -20,15 +20,12 @@ namespace bts.shell {
readonly MDConsumer _consumer;
readonly UserSettingsHub _settings_hub;
readonly TimeAndSalesConfig _time_and_sales_conf;
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, ISecurityAuxiliaryDataModel security_auxiliary_model)
: base(module, "TimeAndSales") {
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
_bus = bus;
_consumer = consumer;
_settings_hub = settings_hub;
_security_auxiliary_model = security_auxiliary_model;
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
@@ -41,8 +38,7 @@ namespace bts.shell {
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g)),
_time_and_sales_conf.can_pause_updates,
_security_auxiliary_model.get_display_quantity_divisor));
_time_and_sales_conf.can_pause_updates));
public async void show(SecurityCriteria criteria) {
var w = await load(new TimeAndSalesSettings(criteria));
@@ -549,48 +549,24 @@
</u:BindingUpdateGroupCollection>
</u:Behavior.BindingUpdateGroups>
</TextBox>
<StackPanel
<TextBlock
x:Name="PART_TickFactors_Label"
Grid.Column="2"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks"
IsEnabled="True"
Orientation="Horizontal"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
>
<TextBlock
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks ("
/>
<CheckBox
Margin="1,0,0,0"
IsChecked="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Padding="0"
>
fractional
</CheckBox>
<TextBlock VerticalAlignment="Center" Margin="1,0,0,0">)</TextBlock>
</StackPanel>
<Grid
/>
<c:TickFactorsPicker
x:Name="PART_TickFactors_Picker"
Grid.Column="3"
IsEnabled="True"
Margin="{StaticResource Margin.LHeaderPanelItem}"
HorizontalAlignment="Stretch"
IsEnabled="True"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
>
<c:TickFactorsPicker
HorizontalAlignment="Stretch"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:BooleanToVisibilityConverter.HiddenInstance}}"
/>
<c:TickUpDownInt
HorizontalAlignment="Stretch"
Minimum="1"
Value="{Binding IntegerTickSize, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource FalseVisibilityConverter}}"
/>
</Grid>
/>
<TextBlock
x:Name="PART_Pricing_Label"
Grid.Column="4"
@@ -52,26 +52,6 @@ namespace mbts.ui.spark {
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
#endregion
#region UseFractionalTicks
public static readonly DependencyProperty UseFractionalTicksProperty = WPFHelper.create_dp<bool, ExecutionStrategyEditor>(
nameof(UseFractionalTicks), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, true);
public bool UseFractionalTicks {
get => (bool)GetValue(UseFractionalTicksProperty);
set => SetValue(UseFractionalTicksProperty, value);
}
#endregion
#region IntegerTickSize
public static readonly DependencyProperty IntegerTickSizeProperty = WPFHelper.create_dp<int, ExecutionStrategyEditor>(
nameof(IntegerTickSize), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 1);
public int IntegerTickSize {
get => (int)GetValue(IntegerTickSizeProperty);
set => SetValue(IntegerTickSizeProperty, value);
}
#endregion
#region IsNew
public static readonly DependencyProperty IsNewProperty =
DependencyProperty.Register(
@@ -46,7 +46,7 @@ namespace bts.shell {
async void _show_synthetic_create(SecurityCriteria security_criteria) {
var found_securities = await security_criteria.securities(_bus);
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Length > 0);
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Count > 0);
if (!respect_clause_security_ordering_is_possible) {
_show_synthetic_create(found_securities.Values.ToList());
} else {
@@ -36,8 +36,6 @@
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
UseFractionalTicks="{Binding use_fractional_ticks}"
IntegerTickSize="{Binding integer_tick_size}"
/>
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
</Grid>
@@ -2,6 +2,7 @@
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
@@ -29,16 +30,8 @@ namespace mbts.spark {
readonly Property<bool> _has_working_orders, _can_add_leg;
Security _synthetic;
bool
_set_name_automatically,
_set_ticks_automatically,
_set_quantities_automatically,
_set_price_mults_automatically,
_is_setting_quantities,
_is_setting_price_mults,
_use_fractional_ticks = true;
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
TickFactors _tick_factors;
int _integer_tick_size = 1;
ExecutionStrategyImmutable _strategy;
HedgePricing _hedge_pricing;
@@ -51,10 +44,6 @@ namespace mbts.spark {
: this(synthetic, strategy, false) {
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
_use_fractional_ticks = false;
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
}
title = $"Edit '{synthetic.Synthetic.Name}'";
icon = Icons.ModifySynthetic;
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
@@ -85,7 +74,9 @@ namespace mbts.spark {
icon = Icons.Synthetics;
_has_working_orders = backing(nameof(has_working_orders), false);
contract_picker_model = new ContractPickerModel(bus, false, null, new SecurityCriteria(new[] {
new SecurityCriteriaClause(exchanges: EnumHelper.get_values_except(Venue.Bts).ToArray())
new SecurityCriteriaClause() {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
}
}));
contract_picker_model.cancel_action += _close_new_leg_popup;
contract_picker_model.ok_action += () => {
@@ -140,14 +131,14 @@ namespace mbts.spark {
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
valid = false;
}
if (use_fractional_ticks ? (_tick_factors == null || !_tick_factors.is_valid()) : integer_tick_size == 0) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, use_fractional_ticks ? nameof(tick_factors) : nameof(integer_tick_size)).ignore();
if (_tick_factors == null || !_tick_factors.is_valid()) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
valid = false;
}
if (valid) {
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
_synthetic.Symbol = _deduce_symbol(_synthetic);
_synthetic.ContractSpec = use_fractional_ticks ? _tick_factors.to_spec() : TickUtils.create_integer_tick_spec(integer_tick_size);
_synthetic.ContractSpec = _tick_factors.to_spec();
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
@@ -304,20 +295,6 @@ namespace mbts.spark {
public bool has_working_orders => _has_working_orders.Value;
public bool is_new { get; }
public bool is_new_leg_popup_open { get; set; }
public bool use_fractional_ticks {
get => _use_fractional_ticks;
set {
if (Set(ref _use_fractional_ticks, value))
_set_ticks_automatically = false;
}
}
public int integer_tick_size {
get => _integer_tick_size;
set {
if (Set(ref _integer_tick_size, value))
_set_ticks_automatically = false;
}
}
public bool can_add_leg => _can_add_leg.Value;
public SyntheticContract.Types.PriceCalc price_calc { get => _synthetic.Synthetic.PriceCalc; set => _synthetic.Synthetic.PriceCalc = value; }
public ObservableCollection<LegSpreaderParamsTuple> leg_tuples { get; } = new ObservableCollection<LegSpreaderParamsTuple>();
@@ -346,16 +323,6 @@ namespace mbts.spark {
_strategy = strategy;
hedge_pricing = strategy.HedgePricing;
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
if (synthetic.HasContractSpec) {
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
/// rather than the properties <see cref="use_fractional_ticks"/> and <see cref="integer_tick_size"/>
/// because the setters of the former flip <see cref="_set_ticks_automatically"/> to false if the value changes.
/// This behavior causes the ticks to be automatically deduced as legs are added/removed,
/// until the user explicitly enters a different value (as the properties are bound to tick input controls in the view).
Set(ref _use_fractional_ticks, !TickUtils.is_integer_tick(synthetic.ContractSpec), nameof(use_fractional_ticks));
if (!use_fractional_ticks)
Set(ref _integer_tick_size, DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0, nameof(integer_tick_size));
}
RaisePropertyChanged(nameof(name), nameof(tick_factors));
}
@@ -9,8 +9,6 @@ namespace mbts.spark {
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
struct AlgoData : IEquatable<AlgoData> {
public static readonly Func<AlgoData, AlgoKey> key_fn = a => a.key;
public readonly CustomOrderDefinitionImmutable value;
public readonly string name;
public readonly AlgoKey key;
@@ -128,7 +128,6 @@ namespace mbts.spark {
}
public bool is_persisted => mode == Mode.Persisted;
public bool is_composite => _composite_key.HasValue || _unpersisted.HasValue;
public uint persisted => _composite_key.Value;
public Guid unpersisted => _unpersisted.Value;
@@ -200,15 +199,6 @@ namespace mbts.spark {
}
return EqualityComparer.phase_hash(hash, phase_mult);
}
public override string ToString() {
switch (mode) {
case Mode.Dynamic: return $"Dynamic: {dynamic_type}";
case Mode.Factory: return $"Factory: {factory}";
case Mode.Persisted: return $"Composite: {persisted}";
case Mode.Unpersisted: return $"(Composite): {unpersisted}";
default: return mode.ToString();
}
}
public static bool operator ==(AlgoKey a, AlgoKey b) => a.Equals(b);
public static bool operator !=(AlgoKey a, AlgoKey b) => !a.Equals(b);
@@ -12,20 +12,20 @@ namespace mbts.spark {
[Export(typeof(IAlgoStudioModel))]
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
readonly AlgoDefinitionService _service;
readonly UserTuple _user;
readonly AdminModel _admin_model;
readonly ErrorReporter _error_reporter;
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
IDisposable _dispose;
[ImportingConstructor]
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, UserTuple user, ErrorReporter error_reporter) {
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
_service = service;
this.metadata_service = metadata_service;
_user = user;
_admin_model = admin_model;
_error_reporter = error_reporter;
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && _user.can_access_user_data(r.Metadata.CreatorUserId))
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
@@ -37,7 +37,7 @@ namespace mbts.spark {
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving definition");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
@@ -93,4 +93,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -11,8 +11,6 @@ namespace mbts.spark {
/// decouple the client-side key type from the server-side one, so both key types may evolve independently moving forward.
/// </summary>
struct AlgoDefinitionMetadata : IEquatable<AlgoDefinitionMetadata> {
public static readonly Func<AlgoDefinitionMetadata, AlgoKey> key_fn = m => m.id;
public readonly AlgoKey id, default_template;
readonly AlgoKey[] _favorite_templates;
public readonly bool is_favorite, is_hidden;
@@ -35,21 +35,34 @@ namespace mbts.spark {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) =>
update_persisted_definitions(target, update.added, update.changed, update.removed.Select(AlgoData.key_fn), factory);
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, IEnumerable<AlgoData> added, IEnumerable<AlgoData> changed, IEnumerable<AlgoKey> removed, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var k in removed)
target.Remove(k);
foreach (var d in changed)
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed)
target[d.key].load(d);
foreach (var d in added) {
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out AlgoDefinitionVM def)) {
def.load(d);
} else target.Add(factory(d));
}
}
public static void update_persisted_definition_wrappers(IDictionary<AlgoKey, IAlgoVM> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed) {
var vm = target[d.key];
if (vm is AlgoVMWrapper wrapper) {
wrapper.value.load(d);
} else vm.load_from(d.value);
}
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out IAlgoVM def)) {
def.load_from(d.value);
} else target.Add(d.key, new AlgoVMWrapper(factory(d)));
}
}
public static void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).commit_all();
@@ -42,21 +42,21 @@ namespace mbts.spark {
readonly Property<string> _name, _abbreviation;
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
readonly DeferrableActionWrapper _refresh_on_local_changes;
readonly Property<AlgoFormats> _selected_format;
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
int? _favorite_index;
Brush _badge_fill;
Snapshot? _committed;
AlgoDefinitionMetadata _metadata;
IDisposable _loaded_data_hook;
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
new AlgoDefinitionVM(model, data, editing_permissions);
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name) =>
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), (AlgoEditingPermissions)int.MaxValue);
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoEditingPermissions init_permissions) {
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
// init core data
_model = model;
_permissions_mask.mask = init_permissions;
@@ -80,6 +80,7 @@ namespace mbts.spark {
// init backing props
_name = backing(nameof(name), def_data.name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
_selected_format = backing(nameof(selected_format), init_format);
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
_is_expanded = backing(nameof(is_expanded), true);
_is_selected = backing(nameof(is_selected), true);
@@ -132,6 +133,17 @@ namespace mbts.spark {
break;
}
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeEach(t => {
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(t.fmt);
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(t.fmt));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}
return Disposable.empty;
}));
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
resources.Add(this.connect_data(permission_src));
resources.Add(this.connect_data(Observable.Return(model)));
@@ -190,6 +202,9 @@ namespace mbts.spark {
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
public Brush badge_fill => _badge_fill;
// format
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
// visual states
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
@@ -221,20 +236,12 @@ namespace mbts.spark {
get => algo_root;
set => algo_root = value;
}
IAlgoVM IAlgoParamVM<IAlgoVM>.display_value {
get => algo_root;
set => algo_root = value;
}
bool IAlgoParamVM.has_value => _algo_root != null;
bool IAlgoParamVM.is_enabled { get; set; } = true;
object IAlgoParamVM.value {
get => algo_root;
set => algo_root = (IAlgoVM)value;
}
object IAlgoParamVM.display_value {
get => algo_root;
set => algo_root = (IAlgoVM)value;
}
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
AlgoParamPermissions IAlgoParamVM.permissions { get => _permissions; set => _permissions = value; }
@@ -255,14 +262,17 @@ namespace mbts.spark {
void IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)>.OnError(Exception error) { }
#endregion
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> data, AlgoKey? default_algo) {
Disposable.dispose(ref _loaded_data_hook);
_algo_roots.SyncWithDictionary(data, d => OrderTypeVMFactory.create(d, is_expanded: true));
_loaded_data_hook = ObservableAutoWrapper.connect_item_hooks(algo_roots, vm =>
ObservableAutoWrapper.connect_item_actions(vm.child_algo_selectors, s => s.load_algo_data(data, null)));
if (algo_root == null || !_algo_roots.ContainsKey(algo_root.id)) {
if (default_algo.HasValue && _algo_roots.TryGetValue(default_algo.Value, out IAlgoVM match))
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
var orig_algo = algo_root?.id;
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
if (!try_select(orig_algo) && !try_select(default_algo))
algo_root = algo_roots.FirstOrDefault();
//
bool try_select(AlgoKey? algo_key) {
if (algo_key.HasValue && _algo_roots.TryGetValue(algo_key.Value, out IAlgoVM match)) {
algo_root = match;
return true;
} else return false;
}
}
@@ -328,7 +338,7 @@ namespace mbts.spark {
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
public AlgoDefinitionVM to_persisted(AlgoData def) =>
new AlgoDefinitionVM(_model, def, editing_permissions_mask);
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
public AlgoData create_algo_def() =>
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
@@ -350,6 +360,7 @@ namespace mbts.spark {
void _assign_from_data(Snapshot state) {
_assign_from_data(state.data);
_selected_format.set(state.format);
_abbreviation.set(state.abbreviation);
custom_badge_color.set_nullable(state.custom_badge_color);
}
@@ -410,36 +421,41 @@ namespace mbts.spark {
}
Snapshot _snapshot() =>
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable());
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable(), selected_format);
public struct Snapshot : IEquatable<Snapshot> {
public readonly AlgoData data;
public readonly string abbreviation;
public readonly Color? custom_badge_color;
public readonly AlgoFormats format;
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color) {
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color, AlgoFormats format) {
this.data = data;
this.abbreviation = abbreviation;
this.custom_badge_color = custom_badge_color;
this.format = format;
}
public bool Equals(Snapshot other) =>
data == other.data &&
abbreviation == other.abbreviation &&
custom_badge_color == other.custom_badge_color;
custom_badge_color == other.custom_badge_color &&
format == other.format;
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
data.GetHashCode(),
abbreviation?.GetHashCode() ?? 0,
custom_badge_color?.GetHashCode() ?? 0);
custom_badge_color?.GetHashCode() ?? 0,
(int)format);
#if DEBUG
public override string ToString() {
var sb = new System.Text.StringBuilder();
sb.Append($"name: {data.name}; ");
sb.Append($"abbrev.: {abbreviation}; ");
sb.Append($"badge: {custom_badge_color} ");
sb.Append($"fmt: {format} ");
sb.Append($"def: {data.value}");
return sb.ToString();
}
@@ -453,6 +469,7 @@ namespace mbts.spark {
public bool Equals(Snapshot x, Snapshot y) =>
x.abbreviation == y.abbreviation &&
x.custom_badge_color == y.custom_badge_color &&
x.format == y.format &&
x.data.key == y.data.key &&
x.data.name == y.data.name &&
x.data.value.Equals(y.data.value);
@@ -1,11 +1,7 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
@@ -41,6 +37,7 @@ namespace mbts.spark {
}
class AlgoStudioVM : DisposableViewModelBase {
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
readonly OrdersModel _model;
readonly Property<AlgoDefinitionVM> _selected_definition;
readonly Property<IAlgoVM> _editable_content;
@@ -73,24 +70,12 @@ namespace mbts.spark {
_revert_cmd_tooltip = backing(nameof(revert_cmd_tooltip), get_revert_tooltip);
//
_dispose = new CompositeDisposable(
_model.subscribe_complete_algos(allow_undefined_child: true).OnDispatcher().Subscribe(_definitions_update_buffer),
_definitions_update_buffer.OnDispatcher()
.SubscribeEach(u => {
u.FilterIncremental(a => a.key.is_composite, out var added, out var changes, out var removed);
AlgoCollectionHelper.update_persisted_definitions(
_definitions, added, changes.New(), removed.Select(AlgoData.key_fn), algo => AlgoDefinitionVM.create(_model, algo));
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
var orig_selected_def = selected_definition;
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
if (selected_definition == null)
selected_definition = definitions.FirstOrDefault();
// Load the algo building blocks (i.e., factory and dynamic algos):
// - Composite algos cannot reference other composite algos - that would create awkward dependency issues as composite algos can be modified/deleted.
// - Spreader algos are filtered out as well, because that don't actually mean anything to the algo structure.
// Synthetic securities in our system are defined by a set of contract legs, and a [single]strategy.
// Note: synthetic strategy templates, however, could be introduced at a later date (no reason why 1 synthetic security couldn't have several ExecutionStrategies).
var algo_building_blocks = new FilteredDictionary<AlgoKey, AlgoData>(
u.state,
a => a.key.mode == AlgoKey.Mode.Dynamic || (a.key.mode == AlgoKey.Mode.Factory && a.key.factory != CustomOrderDefinition.CustomOneofCase.Spreader));
return ObservableAutoWrapper.connect_item_actions(_definitions.AsCollection(),
d => ((IAlgoSelector)d).load_algo_data(algo_building_blocks, null));
}),
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
_persisted_meta = meta;
@@ -140,7 +125,7 @@ namespace mbts.spark {
}
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name);
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
_definitions.Add(new_def);
selected_definition = new_def;
request_view_focus_name?.Invoke(new_def);
@@ -0,0 +1,214 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace mbts.spark {
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
IObserver<SecurityMD?>,
IObserver<SecurityImmutable?>,
IObserver<OrdersModel>,
IObserver<AlgoEditingPermissions> {
static readonly HashSet<string>
_algo_root_echo_props = new HashSet<string> {
nameof(is_expanded),
nameof(editing_permissions),
nameof(definition),
nameof(display_name)
};
static readonly IReadOnlyDictionary<string, string>
_value_echo_props = new Dictionary<string, string> {
{ nameof(AlgoDefinitionVM.name), nameof(display_name) },
{ nameof(AlgoDefinitionVM.abbreviation), nameof(abbreviation) },
{ nameof(AlgoDefinitionVM.badge_fill), nameof(meta) }
};
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly DisposableContainer _value_hooks = new DisposableContainer();
Action _definition_changed = () => { };
_ObservedData? _data_cache;
AlgoDefinitionVM _value;
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
value = algo_definition_vm;
_dispose = new CompositeDisposable(_value_hooks) {
_child_algo_selectors.clear_collections,
_algo_params.clear_collections,
() => _value?.Dispose()
};
}
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
public AlgoDefinitionVM value {
get => _value;
set {
if (_value != value) {
_value?.Dispose();
_value = value;
_value_hooks.replace(_set_hooks(value));
RaisePropertyChanged(nameof(value));
}
}
}
public AlgoKey id => _value.key;
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
public OrderMetadata meta {
get {
if (_value != null) {
if (_value.custom_badge_color.enabled)
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
else if (_value.algo_root != null)
return _value.algo_root.meta;
}
return OrderDataHelper.get_metadata(order_type);
}
}
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
public bool can_expand => _algo_root?.can_expand ?? false;
public bool is_expanded {
get => _value.algo_root?.is_expanded ?? false;
set {
if (_value != null)
_value.is_expanded = value;
}
}
public string display_name => _value?.name ?? string.Empty;
public string abbreviation => _value?.abbreviation;
public AlgoDisplayGroup group => _value?.group ?? AlgoDisplayGroup.BTS;
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
[EditorBrowsable(EditorBrowsableState.Never)]
public ObservableCollection<IAlgoSelector> child_algo_selectors_view {
get {
_value?.algo_root?.ensure_init();
return _child_algo_selectors;
}
}
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
[EditorBrowsable(EditorBrowsableState.Never)]
public dynamic children_vm => _algo_root?.children_vm;
IAlgoVM _algo_root => _value?.algo_root;
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
IDisposable _set_hooks(AlgoDefinitionVM vm) {
var algo_root_subs = new DisposableContainer();
vm.PropertyChanged += on_value_prop_changed;
return new CompositeDisposable(
vm.observe_algo_root().Subscribe(r => {
algo_root_subs.replace(null);
_child_algo_selectors.clear_collections();
_algo_params.clear_collections();
if (r != null) {
if (_data_cache.HasValue) {
_data_cache.Value.forward_to(r);
}
_child_algo_selectors.add_collection(r.child_algo_selectors);
_algo_params.add_collection(r.algo_params);
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
algo_root_subs.replace(new CompositeDisposable(
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
});
}
RaisePropertyChanged(nameof(group));
}),
algo_root_subs) {
() => vm.PropertyChanged -= on_value_prop_changed
};
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_algo_root_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
}
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
RaisePropertyChanged(wrapper_prop);
}
}
// permissions
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
}
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
}
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnCompleted() { }
// security
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
}
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
void IObserver<SecurityImmutable?>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
struct _ObservedData {
public readonly AlgoEditingPermissions permissions;
public readonly OrdersModel model;
public readonly SecurityImmutable? security;
public readonly SecurityMD? security_md;
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
this.permissions = permissions;
this.model = model;
this.security = security;
this.security_md = security_md;
}
public void forward_to(IAlgoVM recipient) {
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
if (model != null)
(recipient as IObserver<OrdersModel>)?.OnNext(model);
if (security_md.HasValue)
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
if (security.HasValue)
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
}
public _ObservedData with(AlgoEditingPermissions? permissions) =>
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
public _ObservedData with(OrdersModel model) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityImmutable? security) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityMD? security_md) =>
new _ObservedData(permissions, model, security, security_md);
}
}
}
@@ -199,30 +199,18 @@
Grid.Column="1"
HorizontalAlignment="Center"
Foreground="{u:BTSColor MarketData.bid}"
Text="{Binding DataContext.bbo.bid_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Bid Size"
>
<TextBlock.Text>
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
<Binding Path="DataContext.bbo.bid_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
/>
<TextBlock
x:Name="ask_text"
Grid.Column="2"
HorizontalAlignment="Center"
Margin="4,0"
Foreground="{u:BTSColor MarketData.ask}"
Text="{Binding DataContext.bbo.ask_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Ask Size"
>
<TextBlock.Text>
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
<Binding Path="DataContext.bbo.ask_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
@@ -284,14 +272,6 @@
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--TODO: replace all the param.value bindings with param.display_value.
The reason this has not already been done is that the display_value property is new,
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
<DataTemplate>
<TextBlock Text="{Binding param.display_value}"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -302,7 +282,7 @@
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
<TextBlock Text="{Binding param.value, Converter={StaticResource DoubleTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Enum-->
@@ -380,22 +360,6 @@
/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--TODO: replace all the param.value bindings with param.display_value.
The reason this has not already been done is that the display_value property is new,
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
<DataTemplate>
<c:TickUpDownNullableInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownNullableInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownNullableInt.CommandBindings>
<c:TickUpDownNullableInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownNullableInt.InputBindings>
</c:TickUpDownNullableInt>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -406,7 +370,7 @@
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownDouble.CommandBindings>
@@ -39,6 +39,31 @@ namespace mbts.spark {
}
}
static IDisposable _wrap_rows(AlgoDefinitionVM algo_def, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) {
AlgoParamRowVM root = null;
ObservableAggregateCollection<AlgoParamRowVM> algo_params;
if (include_algo_type_rows) {
// while normal params live and die with the selected algo-type (when the algo-type changes, they are removed, and replaced with params for the new algo type),
// the algo-type row is constant (it is the row with the algo-type dropdown, which the user may use to select different algos).
algo_params = new ObservableAggregateCollection<AlgoParamRowVM>();
rows.add_collection(new ObservableAppendCollection<AlgoParamRowVM>(algo_params, root = new AlgoSelectorParamRowVM(new AlgoVMWrapper(algo_def), algo_def, null)).AsReadOnly());
} else {
algo_params = rows;
}
return algo_def.observe_algo_root().SubscribeEach(a => {
algo_params.clear_collections();
if (a == null)
return Disposable.empty;
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
/// Note: the order of operations here matters
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
var hook = _wrap_rows(a, root, child_rows, include_algo_type_rows);
algo_params.add_collection(child_rows.AsReadOnly());
return hook;
});
}
static IDisposable _wrap_rows(IAlgoVM algo_root, AlgoParamRowVM parent, ObservableAggregateCollection<AlgoParamRowVM> all_rows, bool include_algo_type_rows) {
var wrapped_params = new ObservableAutoWrapper<AlgoParamRowVM, IAlgoParamVM>(algo_root.algo_params, pvm => new AlgoParamRowVM(algo_root, pvm, parent));
var selector_params = new ObservableCollection<AlgoParamRowVM>();
@@ -60,4 +85,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -106,7 +106,7 @@
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding algo_root}"
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
@@ -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;
@@ -122,7 +124,7 @@ namespace mbts.spark {
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.display_size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), new DoubleMinMax<FillTuple>(r => r.fill.LastPrice), DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), dv_filter(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill.FillKind), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
add_col(Columns.FillStatus, DefaultColumnHeaders.FillStatus, r => r.fill_status.ToString(), dv_filter(EnumHelper.get_values<FillStatus>().ToArray(), r => r.fill.FillStatus), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
@@ -286,7 +288,6 @@ namespace mbts.spark {
Color? _root_algo_badge_color;
OrderMetadata _order_metadata;
bool _is_selected;
double? _display_size;
public Row(FillTuple fill_tuple, OrdersModel model) :
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
@@ -307,12 +308,11 @@ namespace mbts.spark {
public Side side => _fill.Side;
public double price => _fill.LastPrice;
public double size => _fill.LastShares;
public double display_size => _display_size ??
(_display_size = size.safe_divide(_model.security_auxiliary_model.get_display_quantity_divisor(security))).Value;
public FillKind fill_kind => _fill.FillKind;
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;
public bool is_selected {
get => _is_selected;
set => Set(ref _is_selected, value);
@@ -333,6 +333,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();
}
@@ -87,15 +87,6 @@ namespace mbts.spark {
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
}
[ProtoContract]
class LadderOrderLimits : IEquatable<LadderOrderLimits> {
[ProtoMember(1)]
public ToggleInt32Proto max_settlement_cross_ticks { get; set; }
public bool Equals(LadderOrderLimits other) =>
other != null && max_settlement_cross_ticks == other.max_settlement_cross_ticks;
}
[ProtoContract]
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
// this class is 100% mutable (there are literally 0 readonly members),
@@ -323,8 +314,6 @@ namespace mbts.spark {
// Settings are liable to change in the near future, so an internal IEqualityComparer simply won't do as it risks becoming out of date.
[ProtoContract]
class LadderSettings : IEquatable<LadderSettings> {
// RESERVED ProtoMember indices
// 900-999: dj settings
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
@@ -603,9 +592,6 @@ namespace mbts.spark {
[ProtoMember(61), DefaultValue(true)]
public bool status_message_visible { get; set; }
[ProtoMember(900)]
public LadderOrderLimits ladder_order_limits { get; set; }
public void sanitize() =>
sanitize(this);
@@ -711,8 +697,7 @@ namespace mbts.spark {
a.enforce_max_clip == b.enforce_max_clip &&
a.max_clip == b.max_clip &&
a.price_display_format == b.price_display_format &&
a.status_message_visible == b.status_message_visible &&
a.ladder_order_limits.safe_equals(b.ladder_order_limits);
a.status_message_visible == b.status_message_visible;
public bool Equals(LadderSettings other) =>
profile_equals(this, other) &&
@@ -100,7 +100,6 @@ namespace bts.shell {
ShellWindowVM shell_window_vm = null;
IShellWindow shell_window = null;
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
LadderVM vm = null;
var view = new Lazy<UIElement>(() => {
// resources
var model = new LadderModel(md, _orders_model, _mbo);
@@ -114,6 +113,7 @@ namespace bts.shell {
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
bool initialized = false, needs_resync = false;
LadderVM vm = null;
var settings_subject = new Subject<LadderSettings>();
var settings_source = Observable.Create<LadderSettings>(o => {
ensure_synced();
@@ -272,11 +272,10 @@ namespace bts.shell {
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
icon: Icons.Ladder,
thread_id: _thread_id_provider.next_id(),
swvm => {
shell_window_vm = swvm;
vm => {
shell_window_vm = vm;
title_source.connect();
},
key_event_args => vm?.key_down(key_event_args));
});
}
async void _show_windows(IImmutableSet<uint> securities) {
@@ -4,7 +4,6 @@ using bts.shell.orderbook;
using mbts.persist;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -144,7 +143,7 @@ namespace mbts.spark {
string candidate = string.Empty;
if (a.HasValue) {
var algo_name = a.Value.name;
var words = algo_name.Split(delim, StringSplitOptions.RemoveEmptyEntries);
var words = algo_name.Split(delim);
if (words.Length == 1) {
var single_word = words[0];
if (single_word.Length > 1) {
@@ -153,7 +152,7 @@ namespace mbts.spark {
candidate = single_word;
}
} else {
candidate = string.Join(string.Empty, words.Take(2).Select(w => w.Substring(0, 1)));
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
}
}
return candidate.ToUpper();
@@ -170,8 +169,7 @@ namespace mbts.spark {
}
private void _set_badge_text() {
var fmt = MDDisplayHelper.get_value_formatter(_owner.display_quantity_divisor);
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style, fmt);
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style);
RaisePropertyChanged(nameof(text_lines));
}
@@ -3,11 +3,9 @@ using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.persist.algo;
using bts.proto;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.color_picker;
@@ -46,8 +44,7 @@ namespace mbts.spark {
LadderSettings.NetChangeTerms _price_format;
LadderSettings.PriceDisplayFormat _price_display_format;
double _order_entry_panel_width, _zoom_factor;
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip, _enforce_max_settlement_cross_ticks;
int _max_settlement_cross_ticks;
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip;
LadderSettings _current;
LadderColumnSettingsVM
_orders = new OrdersColumnSettingsVM(),
@@ -116,16 +113,7 @@ namespace mbts.spark {
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
_algo_definitions.AddRange(_model.get_permitted_algos(security).factory_types
.Select(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable)
.Select(AlgoData.from_custom_def)
.Select(algo_factory));
_algo_definitions.AddRange(_model.get_permitted_algos(security).dynamic_types
.Select(dyn_type => new CustomOrderDefinitionImmutable(new CustomOrderDefinition {
Dynamic = _model.dynamic_helper.create_def(dyn_type)
}))
.Select(AlgoData.from_custom_def)
.Select(algo_factory));
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
_dispose = new CompositeDisposable(
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
@@ -154,7 +142,7 @@ namespace mbts.spark {
c.PropertyChanged -= on_prop_changed;
}
AccountTuple? account_factory(uint key, string name) => new AccountTuple(key, name);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, is_synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange);
}
public event Action result_changed;
@@ -202,8 +190,6 @@ namespace mbts.spark {
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; }
public bool enforce_max_settlement_cross_ticks { get => _enforce_max_settlement_cross_ticks; set => Set(ref _enforce_max_settlement_cross_ticks, value); }
public int max_settlement_cross_ticks { get => _max_settlement_cross_ticks; set => Set(ref _max_settlement_cross_ticks, value); }
// favorites
public FavoriteAlgosVM favorite_algos_vm { get; }
@@ -384,10 +370,6 @@ namespace mbts.spark {
show_warn_on_cross = settings.show_warn_on_cross;
enforce_max_clip = settings.enforce_max_clip;
max_clip = settings.max_clip;
if (settings.ladder_order_limits != null) {
enforce_max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.is_enabled;
max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.value;
}
}
}
@@ -466,10 +448,6 @@ namespace mbts.spark {
clone.show_warn_on_cross = show_warn_on_cross;
clone.enforce_max_clip = enforce_max_clip;
clone.max_clip = max_clip;
if (clone.ladder_order_limits == null)
clone.ladder_order_limits = new LadderOrderLimits();
clone.ladder_order_limits.max_settlement_cross_ticks =
new ToggleInt32Proto(max_settlement_cross_ticks, enforce_max_settlement_cross_ticks);
return clone;
}
@@ -1,6 +1,6 @@
using bts.api;
using bts.api.orderman;
using bts.proto;
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
@@ -8,7 +8,6 @@ using mbts.co2;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -67,8 +66,6 @@ namespace mbts.spark {
current_settings = init;
this.settings = settings;
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
display_quantity_divisor = model.order_gateway.security_auxiliary_model.get_display_quantity_divisor(model.security);
size_calculator.display_divisor = display_quantity_divisor;
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
this.order_properties_vm = order_properties_vm;
_high = backing(nameof(high), double.NaN);
@@ -260,11 +257,7 @@ namespace mbts.spark {
public IBus bus => _bus;
public event Action
on_tick,
request_settings_dialog,
request_recenter,
settings_invalidated;
public event Action on_tick, request_settings_dialog, settings_invalidated;
public MDConsumerHandle md => _model.md;
// we return only entries who have piqs and whose piqs are not uint.Max
@@ -299,7 +292,6 @@ namespace mbts.spark {
remove => _on_disposed_impl -= value;
}
public SecurityImmutable security => _model.security;
public uint display_quantity_divisor { get; }
public LadderSettings current_settings { get; private set; }
public double high => _high.Value;
public double low => _low.Value;
@@ -377,14 +369,14 @@ namespace mbts.spark {
get {
string rv = null;
if (show_day_position)
rv = SparkUtils.fmt_position(day_position / display_quantity_divisor);
rv = SparkUtils.fmt_position(day_position);
if (show_raw_day_position) {
var raw_day_text = SparkUtils.fmt_position(raw_day_position / display_quantity_divisor);
var raw_day_text = SparkUtils.fmt_position(raw_day_position);
rv = rv == null ? raw_day_text : $"{rv} / {raw_day_text}";
}
if (show_account_position) {
var acc_text = SparkUtils.fmt_position(position / display_quantity_divisor);
rv = rv == null ? acc_text : $"{rv} ({acc_text})";
var acc_text = SparkUtils.fmt_position(position);
rv = rv == null ? SparkUtils.fmt_position(position) : $"{rv} ({acc_text})";
}
return rv ?? "";
}
@@ -398,8 +390,7 @@ namespace mbts.spark {
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
public uint actual_alternate_order_size =>
(current_settings?.alternate_order_size ?? 0) * Math.Max(1, display_quantity_divisor);
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;
@@ -453,27 +444,15 @@ namespace mbts.spark {
public void key_down(KeyEventArgs e) {
if (order_properties_vm == null)
return;
switch (e.Key) {
case Key.Space:
if (KeyboardExtensions.IsShiftDown()) {
// only recenter this ladder
request_recenter?.Invoke();
} else {
// recenter all ladders
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
e.Handled = true;
break;
default:
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
if (KeyboardExtensions.IsCtrlDown()) {
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
if (new_algo != null) {
order_properties_vm.selected_algo_definition = new_algo;
e.Handled = true;
}
}
break;
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));
if (new_algo != null) {
order_properties_vm.selected_algo_definition = new_algo;
e.Handled = true;
}
}
@@ -490,11 +469,11 @@ namespace mbts.spark {
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;
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || actual_alternate_order_size > 0)) {
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || current_settings.alternate_order_size > 0)) {
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
} else {
uint size = size_from_alternate ? actual_alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
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;
@@ -507,10 +486,6 @@ namespace mbts.spark {
}
public async Task send_order_async(Side side, uint size, double price) {
if (!_validate_price(side, price, out var invalid_price_msg)) {
status_update(new Activity { security = security, message = invalid_price_msg, category = ActivityLogCategory.order });
return;
}
var order = order_properties_vm.create(side);
if (order.CustomDef != null) {
if (order.CustomDef.Stop != null) {
@@ -531,7 +506,7 @@ namespace mbts.spark {
if (fav_algo != null) {
if (_favorite_algo_map.TryGetValue(fav_algo.key, out var d)) {
if (!d.flags.HasFlag(LadderFavoriteAlgoFlags.Sticky) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(security.ContractCase));
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
@@ -542,23 +517,6 @@ namespace mbts.spark {
}
}
bool _validate_price(Side side, double price, out string msg) {
msg = null;
if (!price.is_real()) {
msg = "not a real number";
} else if (settlement.is_real()) {
var ticks_from_settle = security.ContractSpec.tickdiff(price, settlement);
if (ticks_from_settle.HasValue) {
var max_cross = (current_settings?.ladder_order_limits?.max_settlement_cross_ticks)?.as_nullable();
if (max_cross.HasValue) {
if (side == Side.Sell ? ticks_from_settle < (-1 * max_cross.Value) : ticks_from_settle > max_cross.Value)
msg = $"exceeds settle cross ticks limit ({ticks_from_settle.Value})";
}
}
}
return msg == null;
}
void _update_header() {
bool last_updated = md.get_last_trade(ref _last_trade_time, out NativePriceSize last, out DateTime time, out ulong volume);
if (_misc_time != _model.md.misc_timestamp || last_updated) {
@@ -46,12 +46,12 @@ namespace mbts.spark {
void _reset_ladder_vm() {
if (_value == null && !_security_md.HasValue)
return;
_value?.Dispose();
_value = _security_md.HasValue ?
LadderVM.create_readonly(_bus,
new LadderModel(security_md.Value, _model, _model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null;
Disposable.replace(ref _value,
_security_md.HasValue ?
LadderVM.create_readonly(_bus,
new LadderModel(security_md.Value, _model, _model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
RaisePropertyChanged(nameof(value));
}
}
@@ -53,7 +53,7 @@ namespace mbts.spark {
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container =
new RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>>();
IDisposable _settings_sub, _volume_sub, _tick_sub, _vm_sub;
IDisposable _settings_sub, _volume_sub, _tick_sub;
ScrollViewer _scrollViewer;
LadderSettings _settings;
LadderVM _vm;
@@ -66,8 +66,7 @@ namespace mbts.spark {
double _last_origin_price;
MarketBreakLines _mkt_break_lines;
NativePriceSize _last_trade;
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
Func<uint, string> _fmt_mkt_quantity_impl = v => v.ToString();
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
int _old_tooltip_row = int.MaxValue;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
@@ -353,9 +352,7 @@ namespace mbts.spark {
return;
}
} else if (_vm != null) {
uint size = e.ChangedButton == MouseButton.Right ?
_vm.actual_alternate_order_size :
_vm.size_calculator.value ?? 0;
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) {
@@ -471,7 +468,7 @@ namespace mbts.spark {
security_id: _vm.model.security.Id,
price_level: price_for_point(point)
);
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query, fmt_mbo_qty);
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
// Load the first set of orders so we know if there are any before showing the popup
var result = await model.load_orders();
if (result.success && result.value.orders.Count > 0) {
@@ -492,8 +489,6 @@ namespace mbts.spark {
};
view.RequestClose += (o, _) => popup.IsOpen = false;
}
string fmt_mbo_qty(int value) =>
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
@@ -539,44 +534,13 @@ namespace mbts.spark {
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
uint _volume_from_row(int row) {
const int max_tries = 3;
var errors = new List<string>(0);
int num_tries = 0;
for (; num_tries < max_tries; num_tries++) {
try {
var rv = impl();
if (num_tries > 0) {
log_errors(true);
}
return rv;
} catch (Exception ex) {
errors.Add(ex.ToString());
}
}
log_errors(false);
return 0;
void log_errors(bool succeeded) {
// Only log distinct errors to avoid useless noise.
// It's likely that the exception will always be the same (IndexOutOfRange),
// however, this bug is not fully understood, so assume nothing.
var error_msg = string.Join("; ", errors.Distinct());
if (succeeded) {
Log.Ui.error($"Ladder '{security}' got volume for row at index '{row}' after {num_tries + 1} attempts (price_list array length is {_price_list.Length}). The following exceptions were observed: {error_msg}");
} else {
Log.Ui.error($"Ladder '{security}' failed to get volume for row at index '{row}' after {max_tries} attempts (price_list array length is {_price_list.Length}): {error_msg}");
}
}
uint impl() {
if (_price_list.SafeTryGetAt(row, out var price)) {
var vapls = _volume_container.read_new_value();
if (vapls != null) {
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
}
}
return 0u;
if (_price_list.SafeTryGetAt(row, out var price)) {
var vapls = _volume_container.read_new_value();
if (vapls != null) {
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
}
}
return 0u;
}
@@ -661,7 +625,7 @@ namespace mbts.spark {
}
void render_market_col(NativePriceSize[] depth) {
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, security.ContractSpec, column.settings, _fmt_mkt_depth_item);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, security.ContractSpec, column.settings, _fmt_size);
}
}
_apply_column_visual_offset();
@@ -699,22 +663,10 @@ namespace mbts.spark {
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
_price_dirty = true;
_piq_dirty = true;
// **use local vm to protect against re-assignment,
// so the events are unhooked from the same instance to which they were added
// when the subs are disposed.
var local_vm = _vm;
local_vm.on_tick += _tick;
local_vm.request_recenter += on_request_recenter;
_vm.on_tick += _tick;
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
_volume_sub = _vm.model.order_gateway.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
// **unhook from local_vm.request_recenter.
// todo: organize these subs into a single entity, prob _vm_subs.
// This hasn't already been done bc the ladder a bit fragile, so changes must be carefully implemented,
// with meticulous attention to detail.
_tick_sub = Disposable.Create(() => local_vm.on_tick -= _tick);
_vm_sub = Disposable.Create(() => local_vm.request_recenter -= on_request_recenter);
void on_request_recenter() =>
RecenterCommand?.ExecuteIfCan(null);
_tick_sub = Disposable.Create(() => _vm.on_tick -= _tick);
}
}
@@ -751,7 +703,7 @@ namespace mbts.spark {
_hide_tool_tip();
}
tooltip_view.Content = _fmt_mkt_quantity(volume);
tooltip_view.Content = $"{volume}";
tooltip_view.IsOpen = true;
this.ToolTip = tooltip_view;
} else {
@@ -867,7 +819,7 @@ namespace mbts.spark {
if (last_trade_changed) {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
}
if (depth_changed)
_refresh_sizes();
@@ -909,21 +861,21 @@ namespace mbts.spark {
void _refresh_last_trade() {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
}
void _refresh_piq() {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column, _fmt_mkt_quantity);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column);
}
void _refresh_sizes() {
Log.assert_d(_price_list != null);
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, security.ContractSpec, _settings.bid_column, _fmt_mkt_depth_item);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, security.ContractSpec, _settings.ask_column, _fmt_mkt_depth_item);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, security.ContractSpec, _settings.bid_column, _fmt_size);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, security.ContractSpec, _settings.ask_column, _fmt_size);
}
/// <summary>
@@ -945,17 +897,7 @@ namespace mbts.spark {
if (vapls != null) {
Log.assert_d(_price_list != null);
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
LadderRender.render_volumes(
_volume_vis_tup.visual,
_volume_vis_tup.glyph_context,
_price_list,
vapls,
_settings.row_height,
security.ContractSpec,
_settings.volume_column,
last_index,
_get_last_arrow_string(),
_fmt_mkt_quantity);
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
_old_last_trade = _last_trade;
}
@@ -978,7 +920,7 @@ namespace mbts.spark {
}
// regardless of if we've arrived here via a new Last (and new direction arrow)
// or we're reusing the old one, this format bit is the same
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
return $"{_direction_arrow} {_last_trade.size} ";
}
}
@@ -1083,7 +1025,7 @@ namespace mbts.spark {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return double.IsNaN(best_bid) ?
double.IsNaN(best_ask) ? _vm.settlement : security.ContractSpec.tickdown(best_ask) :
double.IsNaN(best_ask) ? _vm.low : security.ContractSpec.tickdown(best_ask) :
best_bid;
}
@@ -1091,7 +1033,7 @@ namespace mbts.spark {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return double.IsNaN(best_ask) ?
double.IsNaN(best_bid) ? _vm.settlement : best_bid :
double.IsNaN(best_bid) ? security.ContractSpec.tickdown(_vm.high) : best_bid :
security.ContractSpec.tickdown(best_ask);
}
@@ -1099,7 +1041,7 @@ namespace mbts.spark {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return double.IsNaN(best_bid) ?
double.IsNaN(best_ask) ? _vm.settlement : security.ContractSpec.tickdown(best_ask) :
double.IsNaN(best_ask) ? 0.5 * (_vm.low + _vm.high) : security.ContractSpec.tickdown(best_ask) :
double.IsNaN(best_ask) ? best_bid : TickUtils.mid_tick(security.ContractSpec, best_bid, best_ask);
}
@@ -1136,9 +1078,7 @@ namespace mbts.spark {
GlyphContext glyph_context(LadderColumnSettings cs) =>
GlyphContext.create(cs.font_family, cs.font_weight);
var mkt_qty_display_divisor = _vm?.display_quantity_divisor ?? 1;
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(_settings.size_display, mkt_qty_display_divisor);
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(mkt_qty_display_divisor);
_fmt_size = MDDisplayHelper.get_formatter_nps(_settings.size_display);
_set_columns();
_apply_column_visual_offset();
if (prev_row_height != _settings.row_height)
@@ -1187,22 +1127,6 @@ namespace mbts.spark {
}
visible_columns_changed?.Invoke();
}
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
string _fmt_mkt_depth_item(NativePriceSize value) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_depth_item_impl;
return impl is null ? MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default())?.ToString() ?? "Error" : impl(value);
}
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
string _fmt_mkt_quantity(uint value) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_quantity_impl;
return impl is null ? value.ToString() : impl(value);
}
void _init_tactile_adorner() {
Disposable.dispose(ref _tactile_adorner);
@@ -12,24 +12,20 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using bts.utils;
namespace mbts.spark {
static class BadgeRender {
// IMPORTANT: the return values for this method MUST BE IN SYNC with the "format" method, else "measure_height" and "LadderRender.measure_row_height" will be broken.
static int _line_count_for_style(LadderSettings.BadgeStyle badge_style) => badge_style == LadderSettings.BadgeStyle.Alternate ? 2 : 1;
// IMPORTANT: at the risk of being redundant, see above comment.
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style, Func<uint, string> fmt_value = null) {
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style) {
switch (style) {
case LadderSettings.BadgeStyle.Alternate:
var working_qty = quantity > filled ? quantity - filled : 0;
return new[] {
$"{side.abbreviate()}:{fmt_value(filled)}",
$"W:{fmt_value(working_qty)}"
$"{side.abbreviate()}:{filled}",
$"W:{quantity - filled}"
};
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
default: return new[] { $"{filled}/{quantity}" };
}
}
@@ -197,17 +193,16 @@ namespace mbts.spark {
}
public static void render_volumes(
DrawingVisual dv,
GlyphContext glyph_context,
double[] prices,
IEnumerable<VolumeAtPriceLevelImmutable> data,
double row_height,
ContractSpecificationImmutable spec,
VolumeColumnSettings settings,
int last_index,
string last_size_and_direction,
Func<uint, string> fmt_value) {
//
DrawingVisual dv,
GlyphContext glyph_context,
double[] prices,
IEnumerable<VolumeAtPriceLevelImmutable> data,
double row_height,
ContractSpecificationImmutable spec,
VolumeColumnSettings settings,
int last_index,
string last_size_and_direction
) {
if (!data.Any()) {
_clear_visual(dv);
return;
@@ -229,8 +224,6 @@ namespace mbts.spark {
_clear_visual(dv);
return;
}
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
// inflate the max so that the graph never fills the available space
double max = data.Max(v => v.Volume) * 1.1;
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
@@ -262,11 +255,11 @@ namespace mbts.spark {
}
x_bar += width;
if (show_numbers)
numbers_text.Add(fmt_value(v));
numbers_text.Add(v.ToString());
}
if (settings.total.show_numbers) {
numbers_text.Add(fmt_value(vap.Volume));
numbers_text.Add(vap.Volume.ToString());
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
@@ -316,33 +309,19 @@ namespace mbts.spark {
}
}
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings, Func<uint, string> fmt_value) {
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings) {
using (var dc = dv.RenderOpen()) {
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
var gr = format_text(last.size.ToString(), index, row_height, settings, glyph_context);
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
}
}
// this is basically a copy of redner_sizes up above
internal static void render_piqs(
DrawingVisual dv,
GlyphContext glyph_context,
int origin_index,
double[] prices,
IEnumerable<(double price, uint piq)> pvp,
double row_height,
ContractSpecificationImmutable spec,
LadderColumnSettings settings,
Func<uint, string> fmt_value) {
//
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, IEnumerable<(double price, uint piq)> pvp, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
using (var dc = dv.RenderOpen()) {
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
return;
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
@@ -353,7 +332,7 @@ namespace mbts.spark {
curr_index += tick_diff;
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Length) {
var gr = format_text(fmt_value(entry.piq), curr_index, row_height, settings, glyph_context);
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
@@ -392,4 +371,4 @@ namespace mbts.spark {
using (dv.RenderOpen()) { }
}
}
}
}
@@ -1019,47 +1019,45 @@
<Grid HorizontalAlignment="Left" Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
<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>
<!-- market cross -->
<CheckBox Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding show_warn_on_cross}">
<TextBlock
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding show_warn_on_cross}"
Text="Warn on 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 Grid.Row="2" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_clip}">
<TextBlock
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding enforce_max_clip}"
Text="Max Order Quantity"
/>
<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>
<c:TickUpDownUInt
Grid.Row="2"
Grid.Column="2"
IsEnabled="{Binding enforce_max_clip}"
Value="{Binding max_clip}"
Width="60"
/>
<CheckBox Grid.Row="4" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_settlement_cross_ticks}">
<TextBlock
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding enforce_max_settlement_cross_ticks}"
Text="Max Ticks from Settlement"
/>
</CheckBox>
<c:TickUpDownInt Grid.Row="4" Grid.Column="2" IsEnabled="{Binding enforce_max_settlement_cross_ticks}" Value="{Binding max_settlement_cross_ticks}"/>
<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">
@@ -426,26 +426,9 @@
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
Margin="0,0,0,4"
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock
Grid.Column="2"
Grid.Row="5"
FontWeight="Bold"
Margin="0,0,0,4"
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"
>
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Converter>
<converters:MultiValueConverterPipe>
<x:Static MemberType="{x:Type converters:DynamicDoubleDivisorConverter}" Member="Instance"/>
<x:Static MemberType="{x:Type converters:ValueConverters}" Member="SimpleString"/>
</converters:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="volume" Mode="OneWay"/>
<Binding Path="display_quantity_divisor" Mode="OneTime"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Grid.Column="2" Grid.Row="5" Text="{Binding volume, Mode=OneWay}" FontWeight="Bold"
Margin="0,0,0,4"
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
<!-- Account combobox -->
<ComboBox DataContext="{Binding order_properties_vm}" Margin="2,10,2,2"
@@ -524,7 +507,7 @@
mbts:TextBoxBehavior.IncrementDecrementOnMouseClick="True"
HorizontalContentAlignment="Right"
Margin="2"
Text="{Binding size_calculator.display_value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding size_calculator.value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ContextMenu="{x:Null}"
/>
<!-- Qty quick buttons -->
@@ -539,14 +522,7 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" Content="{Binding}" Focusable="False">
<Button.CommandParameter>
<MultiBinding Converter="{x:Static converters:DynamicDoubleMultiplierConverter.Instance}">
<Binding/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=spark:LadderView}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Content="{Binding}" Focusable="False"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
@@ -93,7 +93,6 @@ namespace mbts.spark {
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
this.PreviewMouseDown += LadderView_PreviewMouseDown;
PreviewKeyDown += (s, e) => vm.key_down(e);
}
IDisposable _connect_left_panel_scroll() {
@@ -3,7 +3,6 @@ using bts.api.position;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
@@ -26,27 +25,24 @@ namespace bts.shell.manual_fills {
readonly Property<bool> _is_dirty;
readonly Property<EditableItemState> _editable_item_state;
readonly DelegateProperty<bool> _is_valid;
readonly DisplayQuantityMultiplier<uint> _size = DisplayQuantityMultiplier.create_uint();
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
SecurityImmutable? _security;
AccountTuple? _account;
UserTuple? _user;
Side _side;
uint _size;
double _price;
bool _is_selected;
ManualFillData? _data;
IDisposable _committed_snapshot;
public ManualFillRow(ManualFillData data, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
public ManualFillRow(ManualFillData data) {
_data = data;
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
key = data.key;
_security = data.value.security;
_account = data.value.account;
_user = data.value.user;
_side = data.value.manual_fill.Side;
_size.actual_value = data.value.manual_fill.Quantity;
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(data.value.security), QuantityConversionData.OpType.DivideActual);
_size = data.value.manual_fill.Quantity;
_price = data.value.manual_fill.Price;
if (placeholder_key.HasValue) {
_is_dirty = backing(nameof(is_dirty), false);
@@ -57,7 +53,6 @@ namespace bts.shell.manual_fills {
_editable_item_state = backing(nameof(editable_item_state), _is_dirty, ch => ch.newval ? EditableItemState.Modified : EditableItemState.None);
}
_is_valid = backing(nameof(is_valid), () => _validate(this));
_dispose = _size.connect_owner(this, nameof(size), nameof(display_size));
}
public event Action<bool> is_selected_changed;
@@ -70,10 +65,8 @@ namespace bts.shell.manual_fills {
public SecurityImmutable? security {
get => _security;
set {
if (_set(ref _security, value)) {
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(value), QuantityConversionData.OpType.DivideActual);
if (_set(ref _security, value))
RaisePropertyChanged(nameof(spec));
}
}
}
public ContractSpecificationImmutable? spec => security.spec_or_default();
@@ -82,8 +75,7 @@ namespace bts.shell.manual_fills {
public UserTuple? user { get => _user; set => _set(ref _user, value); }
// side/size/price
public Side side { get => _side; set => _set(ref _side, value); }
public uint size { get => _size.actual_value; set => _size.actual_value = value; }
public uint display_size { get => _size.display_value; set => _size.display_value = value; }
public uint size { get => _size; set => _set(ref _size, value); }
public double price { get => _price; set => _set(ref _price, value); }
// is selected
public bool is_selected {
@@ -71,7 +71,7 @@
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding display_size}"/>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
</Grid>
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
@@ -1,5 +1,4 @@
using bts.api;
using bts.api.core;
using bts.api.core;
using bts.api.position;
using bts.collections;
using mbts.spark;
@@ -104,8 +103,5 @@ namespace bts.shell.manual_fills {
public async Task<bool> update_fills(IEnumerable<ManualFill> fills) =>
await _model.update_manual_fills(fills);
public uint get_display_qty_divisor(SecurityImmutable? security) =>
_model.security_auxiliary_model.get_display_quantity_divisor(security);
}
}
@@ -68,7 +68,7 @@ namespace bts.shell.manual_fills {
RXCollection.connect(_persisted_rows,
_model.manual_fills.OnDispatcher(),
Observable.Return(Comparer<ManualFillData>.Create((a, b) => a.key.CompareTo(b.key))),
mfd => new ManualFillRow(mfd, model.get_display_qty_divisor),
mfd => new ManualFillRow(mfd),
mfr => mfr.data),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
@@ -126,8 +126,7 @@ namespace bts.shell.manual_fills {
new ManualFillKey(_placeholder_id_counter++),
new ManualFillTuple(new ManualFillImmutable(new ManualFill {
Side = Side.Buy
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())),
_model.get_display_qty_divisor);
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())));
void _add_fill(ManualFillRow new_row) =>
_placeholder_rows.InsertSorted((new_row ?? (new_row = row_factory())).placeholder_key.HasValue ?
@@ -211,20 +210,11 @@ namespace bts.shell.manual_fills {
} else {
add_imported_fills();
}
void add_imported_fills() {
_placeholder_rows.InsertSorted(parsed_fills.Select(mk_row), _row_key_comparer);
ManualFillRow mk_row(ParsedManualFill pmf) {
var data = new ManualFillData(
new ManualFillKey(_placeholder_id_counter++),
new ManualFillTuple(
new ManualFillImmutable(pmf.result),
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
users.FirstOrDefault(a => a?.id == pmf.result.UserId)));
return new ManualFillRow(data, _model.get_display_qty_divisor);
}
}
void add_imported_fills() =>
_placeholder_rows.InsertSorted(parsed_fills.Select(pmf => new ManualFillRow(new ManualFillData(new ManualFillKey(_placeholder_id_counter++), new ManualFillTuple(new ManualFillImmutable(pmf.result),
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
users.FirstOrDefault(a => a?.id == pmf.result.UserId))))), _row_key_comparer);
} catch (Exception ex) {
BTSMessageBox.Error($"Error importing fills: {ex}.", ManualFillsWindowFactory.window_name);
}
@@ -212,12 +212,12 @@
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Size, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Qty" SortMemberPath="side">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding display_size}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding size}" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<c:TickUpDownUInt MinWidth="40" Value="{Binding display_size, UpdateSourceTrigger=PropertyChanged}"/>
<c:TickUpDownUInt MinWidth="40" Value="{Binding size, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
@@ -597,19 +597,19 @@ namespace mbts.spark {
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
: base(Columns.Size, "Qty", r => r.display_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.display_quantity.ToString(), position, row_height,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.display_filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.display_filled_quantity.ToString(), position, row_height,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
@@ -1163,7 +1163,7 @@ namespace mbts.spark {
public override FrameworkElement view_factory() {
var tick_up_down = new TickUpDownNullableUIntCommit();
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.SourceValueProperty, nameof(OrderBookRowVM.prospective_display_quantity));
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.SourceValueProperty, nameof(OrderBookRowVM.prospective_quantity));
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.OnCommitProperty, nameof(OrderBookRowVM.modify_order_quantity_andor_price));
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Enter, nameof(OrderBookRowVM.modify_order_quantity_andor_price)));
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Escape, nameof(OrderBookRowVM.revert_prospective_order_quantity_andor_price)));
@@ -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;
@@ -29,16 +31,16 @@ namespace mbts.spark {
int _depth_level;
SecurityImmutable? _security => order_tuple.order.security;
// for mini-ob
DisplayQuantityMultiplier<uint> _prospective_qty_translator_backing;
uint _display_qty_divisor_backing;
bool _display_qty_divisor_dirty = true;
uint _prospective_quantity;
double _prospective_price;
Action<bool> _on_selection_changed;
public OrderBookRowVM(OrderBookVM owner, OrderSecurityRootPair order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
_owner = owner;
this.order_tuple = order_tuple;
is_visible = !is_child;
_is_expanded = is_expanded;
prospective_quantity = this.quantity;
prospective_price = this.price;
_on_selection_changed = on_selection_changed;
}
@@ -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;
@@ -95,18 +126,14 @@ namespace mbts.spark {
public string last_price_text => _fmt_price(last_price);
public string avg_price_text => _fmt_price(avg_price);
public uint quantity => order.quantity();
public uint display_quantity => quantity.safe_divide(_display_qty_divisor);
public uint filled_quantity => order.filled();
public uint display_filled_quantity => filled_quantity.safe_divide(_display_qty_divisor);
public string last_msg => order.last_msg();
// piq
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.safe_divide(_display_qty_divisor).ToString();
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
public bool is_non_terminal => order.is_non_terminal();
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
public string filled_status_desc => order.filled_status_description(security.spec_or_default(), _display_qty_divisor);
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
// Root Algo
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
@@ -117,6 +144,7 @@ namespace mbts.spark {
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
public bool is_child => order.has_parent();
public bool is_visible { get; set; }
public bool is_expanded {
get => _is_expanded;
set {
@@ -134,12 +162,12 @@ namespace mbts.spark {
}
}
public uint prospective_quantity {
get => _prospective_qty_translator.actual_value;
set => _prospective_qty_translator.actual_value = value;
}
public uint prospective_display_quantity {
get => _prospective_qty_translator.display_value;
set => _prospective_qty_translator.display_value = value;
get => _prospective_quantity;
set {
if (Set(ref _prospective_quantity, value)) {
RaisePropertyChanged(nameof(is_qty_dirty));
}
}
}
public double prospective_price {
get => _prospective_price;
@@ -229,28 +257,6 @@ namespace mbts.spark {
}
}
uint _display_qty_divisor {
get {
_sync_display_qty_if_dirty();
return _display_qty_divisor_backing;
}
}
IDisplayTranslator<uint> _prospective_qty_translator {
get {
if (_prospective_qty_translator_backing == null) {
_prospective_qty_translator_backing = DisplayQuantityMultiplier.create_uint();
_prospective_qty_translator_backing.conversion_data = new QuantityConversionData(_display_qty_divisor, QuantityConversionData.OpType.DivideActual);
_prospective_qty_translator_backing.actual_value = quantity;
_prospective_qty_translator.value_changed += () =>
RaisePropertyChanged(nameof(is_qty_dirty));
} else {
_sync_display_qty_if_dirty();
}
return _prospective_qty_translator_backing;
}
}
#region IHierarchicalRow
bool IHierarchicalRow.is_parent => is_parent;
bool IHierarchicalRow.is_expanded => is_expanded;
@@ -294,35 +300,22 @@ namespace mbts.spark {
if ((Columns.OrderType & dirty) != 0)
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
// quantity
if ((Columns.Size & dirty) != 0) {
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
// via the Mini OB update column mechanism (vs the EditableQty column)
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
// row will no longer match the filter
prospective_quantity = quantity;
}
// price
if ((Columns.Price & dirty) != 0) {
RaisePropertyChanged(nameof(price));
RaisePropertyChanged(nameof(is_price_dirty));
}
// security
if ((Columns.Security & dirty) != 0) {
_display_qty_divisor_dirty = true;
RaisePropertyChanged(
// security props
nameof(security),
nameof(security_text),
// qty props
nameof(display_quantity),
nameof(prospective_quantity),
nameof(prospective_display_quantity),
nameof(is_qty_dirty),
//
nameof(piq_text),
nameof(display_filled_quantity));
}
if ((Columns.Size & dirty) != 0) {
RaisePropertyChanged(nameof(quantity), nameof(display_quantity));
if (_prospective_qty_translator_backing != null)
_prospective_qty_translator_backing.actual_value = quantity;
}
if ((Columns.Filled & dirty) != 0) {
RaisePropertyChanged(nameof(filled_quantity), nameof(display_filled_quantity));
}
if ((Columns.Security & dirty) != 0)
RaisePropertyChanged(nameof(security), nameof(security_text));
// badge fill
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
// invalidate by setting to null (will be re-instantiated in the public getter)
@@ -399,15 +392,6 @@ namespace mbts.spark {
security.Value.ContractSpec.format(v) :
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
void _sync_display_qty_if_dirty() {
if (_display_qty_divisor_dirty) {
_display_qty_divisor_dirty = false;
_display_qty_divisor_backing = _owner.get_display_qty_divisor(security);
if (_prospective_qty_translator_backing != null)
_prospective_qty_translator_backing.conversion_data = new QuantityConversionData(_display_qty_divisor_backing, QuantityConversionData.OpType.DivideActual);
}
}
void RaisePropertyChanged(params string[] props) {
var pc = PropertyChanged;
if (pc == null)
@@ -446,7 +430,6 @@ namespace mbts.spark {
Columns.LastMessage |
Columns.Piq,
filled_status_desc_mask =
Columns.Security |
Columns.Filled |
Columns.AvgFilled |
Columns.Status;
@@ -51,7 +51,6 @@ namespace mbts.spark {
readonly OrderParamsAggregator _selected_params_mon;
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly RightPanelModel _right_panel = new RightPanelModel();
readonly CustomStateSynchronizer _custom_state_sync;
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
double
_raw_right_panel_width = OrderBookStartupData.Default.right_panel_width,
@@ -86,7 +85,7 @@ namespace mbts.spark {
_model = model;
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle, k => _cached_orders.TryGetValue(k, out var o) ? o.order : (OrderSecurityAlgo?)null);
ladder_vm = new LadderVMContainer(bus, model);
order_mod_vm = new OrderParamsVM(this, AlgoEditingPermissions.AllowEditValues);
order_mod_vm = new OrderParamsVM(this, AlgoFormats.Exchange, AlgoEditingPermissions.AllowEditValues);
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
order_mod_vm.algo_vm.algo_root = null;
_show_order_params = backing(nameof(show_order_params), () => (_right_panel.visible_components & OrderBookRightPanelSettings.Components.Params) != 0);
@@ -99,7 +98,7 @@ namespace mbts.spark {
};
_can_attach_selected_orders = backing(nameof(can_attach_selected_orders), () => _attachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
_can_detach_selected_orders = backing(nameof(can_detach_selected_orders), () => _detachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
attach_vm = new OrderParamsVM(this, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
attach_vm = new OrderParamsVM(this, AlgoFormats.Attachable, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
_settings_manager = settings_manager;
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
@@ -125,8 +124,6 @@ namespace mbts.spark {
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
_current_row = backing<OrderBookRowVM>(nameof(current_row));
_columns.filter_changed += _on_filter_changed;
_custom_state_sync = new CustomStateSynchronizer(this);
// commands
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
var modify_selected_orders_async_cmd = new AsyncCommand(_modify_selected_orders_async, () => _selected_params_mon.all_active && order_mod_vm.is_dirty);
@@ -191,7 +188,6 @@ namespace mbts.spark {
security_plurality = p;
RaisePropertyChanged(nameof(security_plurality));
}),
_current_row.Subscribe(_ => _custom_state_sync.sync()),
orders_source.OnDispatcher().Subscribe(t => {
if (_selected_rows.Count == 0) {
_cached_orders = t.state;
@@ -211,8 +207,6 @@ namespace mbts.spark {
}
}
}
_custom_state_sync.sync(true);
orders_changed?.Invoke();
}),
model.on_orders_can_attach.OnDispatcher().Subscribe(table => {
_cached_attachable = new MappedDictionary<uint, KeyValuePair<uint, bool>, bool>(table.state, kv => kv.Value);
@@ -244,22 +238,7 @@ namespace mbts.spark {
}),
grid_settings_manager,
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(
/// This is not the same security source used to populate <see cref="OrderParamsVM.security_md"/>.
/// The latter is always synced with the root of the selected row(s),
/// while this source is synced with that row with one exception:
/// If the root row's algo has a spreader algo (possibly nested),
/// the synthetic security is taken instead of the root security.
/// This has the affect of passing the synthetic security to the spreader algo viewmodel,
/// which then uses the security to populate the synthetic params ui.
/// TODO (maybe): clean this design up; the different security sources make it precarious.
/// One idea is to pass a special data type to the algo vm (which contains the spreader
/// security and execution strategy), which is only observed by the spreader algo vm.
_selected_params_mon.subscribe_security().StartWith((SecurityImmutable?)null).CombineLatest(
_selected_params_mon.subscribe_spreader_security().StartWith((SecurityImmutable?)null),
(root_sec, spreader_sec) => (root_sec, spreader_sec))
.Select(tup => tup.spreader_sec ?? tup.root_sec)
.OnDispatcher()),
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
order_mod_vm,
attach_vm,
_show_fills.observe_current(true)
@@ -344,32 +323,32 @@ namespace mbts.spark {
}
void on_begin_attaching() {
Disposable.dispose(ref attach_vm_hooks);
attach_vm_hooks = Disposable.Create(
_selected_security_mon.SubscribeThrottled(_selection_change_throttle).to_security_md(bus).OnDispatcher().SubscribeEach(newval => {
attach_vm.security_md = newval;
if (newval.HasValue) {
return _model.subscribe_algos(newval.Value.security, true)
.OnDispatcher()
.Subscribe(t => ((IAlgoSelector)attach_vm.algo_vm).load_algo_data(t.state, null));
}
return null;
}),
connect_common_selected_params(attach_vm),
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
AlgoParamPermissions bool_to_perm(bool b) =>
b ? AlgoParamPermissions.ReadWrite : AlgoParamPermissions.None;
}));
Disposable.replace(ref attach_vm_hooks,
Disposable.Create(
connect_common_selected_params(attach_vm),
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
AlgoParamPermissions bool_to_perm(bool b) =>
b ? AlgoParamPermissions.ReadWrite : AlgoParamPermissions.None;
}),
// custom templates (created by user)
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_algo_def_vm))));
// vm factory
AlgoDefinitionVM mk_algo_def_vm(AlgoData fd) {
var vm = AlgoDefinitionVM.create(_model, fd, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
return vm;
}
}
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
@@ -380,11 +359,11 @@ namespace mbts.spark {
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
revert_selected_orders_cmd.RaiseCanExecuteChanged();
}),
_selected_params_mon.subscribe_security().to_security_md(bus).OnDispatcher().Subscribe(newval => order_mod_vm.security_md = newval),
connect_common_selected_params(order_mod_vm));
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
@@ -396,7 +375,7 @@ namespace mbts.spark {
}
public event Action<OrderRecordImmutable?> selected_order_changed;
public event Action begin_attaching, end_attaching, orders_changed;
public event Action begin_attaching, end_attaching;
public event Action<(IEnumerable, bool)> request_selection;
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
@@ -416,7 +395,6 @@ namespace mbts.spark {
}
}
public OrderRecordImmutable? current_order => current_row?.order;
public ObservableCollection<NamedValueVM> current_algo_custom_state { get; } = new ObservableCollection<NamedValueVM>();
public AlgoDefinitionVM algo_vm => order_mod_vm.algo_vm;
public OrderParamsVM order_mod_vm { get; }
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
@@ -496,28 +474,6 @@ namespace mbts.spark {
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
public IObservable<bool> subscribe_show_order_params() => _show_order_params.observe_current(true);
public IObservable<OrderSecurityAlgo?> subscribe_single_selected_order() =>
Observable.Create<OrderSecurityAlgo?>(o => {
OrderSecurityAlgo? last_fired = null;
orders_changed += notify;
_current_row.Changed += on_current_row_changed;
notify();
return Disposable.Create(dispose);
// local methods
void notify() {
var next = current_row == null ? (OrderSecurityAlgo?)null : current_row.order_tuple.order;
if (next != last_fired)
o.OnNext(last_fired = next);
}
void on_current_row_changed(OrderBookRowVM oldval, OrderBookRowVM newval) =>
notify();
void dispose() {
orders_changed -= notify;
_current_row.Changed -= on_current_row_changed;
}
});
public void row_selection_changed(SelectionChangedEventArgs e) {
try {
@@ -547,7 +503,6 @@ namespace mbts.spark {
_can_attach_selected_orders.refresh();
_can_detach_selected_orders.refresh();
selected_order_changed?.Invoke(current_order);
_custom_state_sync.sync();
} catch (Exception ex) {
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
@@ -654,9 +609,6 @@ namespace mbts.spark {
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
internal uint get_display_qty_divisor(SecurityImmutable? security) =>
_model.security_auxiliary_model.get_display_quantity_divisor(security);
internal void show_order_modify(OrderBookRowVM row) =>
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
@@ -771,62 +723,24 @@ namespace mbts.spark {
}
}
class CustomStateSynchronizer {
readonly OrderBookVM _vm;
OrderBookRowVM _synced_row;
public CustomStateSynchronizer(OrderBookVM vm) =>
_vm = vm;
public void sync(bool force_refresh = false) {
var row = _vm.current_row != null &&
_vm.selected_rows.Select(r => r.order_tuple.root.order.Key).Distinct().TryGetSingle(out uint droot) &&
_vm.current_row.order_tuple.root.order.Key == droot ?
_vm.current_row : null;
if (force_refresh || row != _synced_row) {
_synced_row = row;
_refresh();
}
}
void _refresh() {
_vm.current_algo_custom_state.Clear();
if (_synced_row == null)
return;
var dcs = _synced_row.order.dynamic_custom_state();
// while dcs is not expected to be null, best to play it safe and double check.
if (dcs.is_null_or_empty()) {
var descendent_rows = _vm._row_collection.get_descendent_rows(_synced_row.order_key);
if (_vm._row_collection.filter != null)
descendent_rows = descendent_rows.Where(child => _vm._row_collection.filter(child.order_tuple));
foreach (var dr in descendent_rows) {
dcs = dr.order.dynamic_custom_state();
if (!dcs.is_null_or_empty())
break;
}
}
foreach (var p in dcs.empty_if_null().OrderBy(kv => kv.Key))
_vm.current_algo_custom_state.Add(new NamedValueVM(p.Key, DynamicOrderHelper.fmt_param(p.Value)));
}
}
public class OrderParamsVM : DisposableViewModelBase {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly OrderBookVM _owner;
readonly Property<bool> _is_dirty;
IRaiseCanExecuteChangedCommand _payup_bid_cmd, _payup_ask_cmd;
IAlgoParamVM<double> _price_param;
AlgoParamVM<uint?> _size_param;
IAlgoParamVM<uint?> _size_param;
AlgoParamPermissions
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
SecurityMD? _security_md;
public OrderParamsVM(OrderBookVM owner, AlgoEditingPermissions init_permissions) {
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
_owner = owner;
algo_vm = AlgoDefinitionVM.create(
owner._model,
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
init_format: init_format,
editing_permissions: init_permissions);
_accounts = backing(
nameof(selected_account),
@@ -852,9 +766,7 @@ namespace mbts.spark {
_price_param = null;
_size_param = null;
} else {
_size_param = new AlgoParamVM<uint?>("size", "Size", owner._selected_params_mon.size, EqualityComparer<uint?>.Default,
to_display_value: v => v.HasValue ? v.Value / display_quantity_divisor : v,
from_display_value: v => v.HasValue ? v.Value * display_quantity_divisor : v) {
_size_param = new AlgoParamVM<uint?>("size", "Size", owner._selected_params_mon.size) {
permissions = _size_permissions & _param_permissions
};
var price_param = new AlgoParamVM<double>("price", "Price", owner._selected_params_mon.price, spec?.tick_equality_comparer() ?? MathUtils.epsilon_eq_comparer) {
@@ -893,16 +805,14 @@ namespace mbts.spark {
public ReadOnlyObservableCollection<AccountTuple> accounts => _accounts.items;
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
public SecurityImmutable? security => _owner._selected_params_mon.security;
/// <see cref="security_md"/> should be synced with the root of the selected row(s),
public SecurityMD? security_md {
get => _security_md;
set {
if (Set(ref _security_md, value)) {
RaisePropertyChanged(nameof(security), nameof(spec), nameof(display_quantity_divisor));
RaisePropertyChanged(nameof(security), nameof(spec));
bbo.security_md = value;
_payup_bid_cmd.RaiseCanExecuteChanged();
_payup_ask_cmd.RaiseCanExecuteChanged();
_size_param?.refresh_display_value();
}
}
}
@@ -924,7 +834,6 @@ namespace mbts.spark {
public AlgoDefinitionVM algo_vm { get; }
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
public BBOVM bbo { get; }
public uint display_quantity_divisor => _owner.get_display_qty_divisor(security);
public ICommand send_order { get; }
public ICommand payup_bid_cmd => _payup_bid_cmd;
public ICommand payup_ask_cmd => _payup_ask_cmd;
@@ -1,17 +1,15 @@
using bts;
using bts.api;
using bts.api;
using bts.api.orderman;
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.spark.utilities;
using mbts.ui;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -27,10 +25,7 @@ namespace mbts.spark {
const string _window_type = "OrderBook", _default_title = "Order Book";
readonly INotificationService _notification_service;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly bool _can_edit_specific_algo_params;
IDisposable _attachment_exit_sub;
// initialized in thread_init
List<IDisposable> _thread_init_resources;
OrderBookStartupData _state;
OrdersModel _model;
OrderBookVM _vm;
@@ -40,14 +35,13 @@ namespace mbts.spark {
SparkCommand[] _commands;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool can_edit_specific_algo_params)
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
_model = model;
_factory = factory;
_notification_service = notification_service;
_state = state;
_settings_manager = settings_manager;
_can_edit_specific_algo_params = can_edit_specific_algo_params;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
@@ -71,14 +65,12 @@ namespace mbts.spark {
public string thread_id { get; }
public override void Dispose() {
Disposable.dispose(_thread_init_resources);
_commands.OfType<IDisposable>().DisposeAll();
Disposable.dispose(ref _attachment_exit_sub);
Disposable.dispose(ref _vm);
}
public void thread_init() {
Log.assert(_thread_init_resources == null, $"{nameof(_thread_init_resources)} must be initialized in thread_init.");
_thread_init_resources = new List<IDisposable>();
_menu_display_vm = new SparkMenuDisplayVM();
if (_state != null)
_menu_display_vm.load_from(_state.menu_display);
@@ -156,15 +148,6 @@ namespace mbts.spark {
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
);
recreate_selected_orders_command.group = CommandGroups.Modify;
ICommand edit_algo_params_cmd;
if (_can_edit_specific_algo_params) {
_thread_init_resources.Add(CommandHelper.bind_param_source(
new DialogCommand<OrderSecurityAlgo?, Window>(mk_algo_params_dialog, has_algo_params),
_vm.subscribe_single_selected_order(),
out edit_algo_params_cmd, true));
} else {
edit_algo_params_cmd = DelegateCommand.empty;
}
var attach_selected_orders_command = new SparkCommandCommand(
"Attach To Selected",
@@ -212,22 +195,15 @@ namespace mbts.spark {
show_grid_settings_command.group = CommandGroups.Auxiliary;
_commands = new SparkCommand[] {
// modify commands
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,
recreate_selected_orders_command,
new SparkCommandCommand("Algo Params...", Icons.Algo, edit_algo_params_cmd) {
group = CommandGroups.Modify,
show_in_toolbar = false,
show_in_context_menu = _can_edit_specific_algo_params
},
// cancel commands
cancel_selected_orders_command,
cancel_buy_orders_command,
cancel_sell_orders_command,
cancel_all_orders_command,
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command,
@@ -236,12 +212,9 @@ namespace mbts.spark {
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
_menu_display_vm.spark_cmd
};
_thread_init_resources.AddRange(_commands.OfType<IDisposable>());
_thread_init_resources.Add(_vm.as_demuxer()
.subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title()));
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
_vm.selected_order_changed += vm_selected_order_changed;
_thread_init_resources.Add(Disposable.Create(() => _vm.selected_order_changed -= vm_selected_order_changed));
// local methods
string get_toggle_show_right_panel_cmd_title() =>
_vm.show_right_panel ? OrderBookView.CommandDescriptions.HideRightPanel : OrderBookView.CommandDescriptions.ShowRightPanel;
@@ -263,36 +236,6 @@ namespace mbts.spark {
attach_selected_orders_command.raiseCanExecuteChanged();
detach_selected_orders_command.raiseCanExecuteChanged();
}
bool has_algo_params(OrderSecurityAlgo? order) =>
order.HasValue && !OrderDataHelper.get_metadata(order.Value.algo.value.CustomCase).NoProps;
Window mk_algo_params_dialog(OrderSecurityAlgo? order) {
if (!has_algo_params(order))
return null;
var algo_def = AlgoDefinitionVM.create(_model, order.Value.algo, editing_permissions: AlgoEditingPermissions.AllowEditValues);
var algo_params_vm = new OrderBookAlgoParamsVM(algo_def, order.Value.order.is_non_terminal());
var dialog = new OrderBookAlgoParamsDialog {
Content = algo_params_vm
};
ui.WindowStyle.SetTitleContent(dialog, algo_def);
dialog.Closed += (s, e) => {
Disposable.dispose(ref algo_params_vm);
dialog = null;
};
algo_params_vm.RequestCommit += commit;
algo_params_vm.RequestClose += (s, e) => close_dialog();
return dialog;
void close_dialog() {
dialog?.Close();
dialog = null;
}
void commit(CustomOrderDefinitionImmutable value) {
_model.update_order(order.Value.order, new_algo: value).ignore();
close_dialog();
}
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) {
@@ -1,7 +1,6 @@
using bts.api;
using bts.shell.orderbook;
using bts.spark;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
@@ -16,11 +15,8 @@ namespace bts.shell {
readonly INotificationServiceDataCache _notification_service_data_cache;
readonly OrdersModel _model;
readonly UserSettingsHub _settings_hub;
readonly OrderBookConfig _config;
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model, UserSettingsHub settings_hub) : base(owner, "SparkOrderBookWindow") {
var raw_config = Configuration.get<bts.config.OrderBookOptions>();
_config = new OrderBookConfig(raw_config.can_edit_specific_algo_params);
_bus = bus;
_notification_service = notification_service;
_notification_service_data_cache = notification_service_data_cache;
@@ -33,8 +29,7 @@ namespace bts.shell {
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
(us, s) => us.with(order_book_grid: s));
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state,
SparkCommands.ShowOrders.icon, settings_manager.as_non_shared(), _config.can_edit_specific_algo_params);
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
return Task.FromResult<IShellWindow>(w);
}
@@ -42,12 +37,5 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
readonly struct OrderBookConfig {
public readonly bool can_edit_specific_algo_params;
public OrderBookConfig(bool can_edit_specific_algo_params) =>
this.can_edit_specific_algo_params = can_edit_specific_algo_params;
}
}
}
@@ -1,40 +0,0 @@
<Window
x:Class="bts.shell.orderbook.OrderBookAlgoParamsDialog"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:ob="clr-namespace:bts.shell.orderbook"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
u:WindowStyle.SizeToContent="WidthAndHeight"
Margin="0"
Padding="0"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<usp:OrderTypeItem Margin="4,0" DisplayName="{Binding name, StringFormat='{}{0} Params'}" BadgeFill="{Binding badge_fill}"/>
</StackPanel>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
<Window.ContentTemplate>
<DataTemplate DataType="{x:Type ob:OrderBookAlgoParamsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<usp:AlgoEditorGridView
Width="390"
Margin="5"
AlgoParamRows="{Binding param_rows}"
CanUserChangeAlgoRoot="False"
/>
<c:DialogSkirt Grid.Row="1" RightItems="{Binding dialog_skirt}"/>
</Grid>
</DataTemplate>
</Window.ContentTemplate>
</Window>
@@ -1,49 +0,0 @@
using bts.api.orderman;
using bts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Windows;
namespace bts.shell.orderbook {
/// <summary>
/// Interaction logic for OrderBookAlgoParamsDialog.xaml
/// </summary>
partial class OrderBookAlgoParamsDialog : Window {
public OrderBookAlgoParamsDialog() =>
InitializeComponent();
}
class OrderBookAlgoParamsVM : DisposableViewModelBase, IRequestViewClose {
readonly ObservableAggregateCollection<AlgoParamRowVM> _param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly DialogSkirtItem _commit, _cancel;
public OrderBookAlgoParamsVM(AlgoDefinitionVM algo_definition, bool can_user_edit) {
this.algo_definition = algo_definition;
_dispose = AlgoParamRowsHelper.connect(algo_definition, _param_rows, false);
_commit = new DialogSkirtItem("OK", new DelegateCommand(commit, () => can_user_edit)) { is_default = true };
_cancel = new DialogSkirtItem("Cancel", new DelegateCommand(close)) { is_cancel = true };
// local methods
void commit() {
RequestCommit?.Invoke(new CustomOrderDefinitionImmutable(algo_definition.algo_root.created_assigned()));
close();
}
void close() => RequestClose?.Invoke(this, EventArgs.Empty);
}
public event EventHandler RequestClose;
public event Action<CustomOrderDefinitionImmutable> RequestCommit;
public AlgoDefinitionVM algo_definition { get; }
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows => _param_rows;
public IEnumerable<DialogSkirtItem> dialog_skirt {
get {
yield return _commit;
yield return _cancel;
}
}
}
}
@@ -62,7 +62,7 @@
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_actual_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
@@ -82,7 +82,7 @@
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_actual_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
@@ -101,7 +101,7 @@
<TextBlock.Inlines>
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
<Run Text="{Binding side, Mode=OneWay}" Foreground="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
<Run Text="{Binding display_quantity, Mode=OneWay}"/>
<Run Text="{Binding quantity, Mode=OneWay}"/>
<Run>@</Run>
<Run>
<Run.Text>
@@ -484,7 +484,6 @@
<Border Padding="12,12,12,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
@@ -496,45 +495,7 @@
AlgoParamRows="{Binding param_rows}"
IsReadOnly="{Binding DataContext.can_user_modify_selected_orders, ElementName=Root, Converter={StaticResource NotConverter}}"
/>
<c:ExpanderToggleButton
x:Name="algo_state_toggle"
Grid.Row="1"
BorderThickness="0"
Content="Algo State"
HorizontalContentAlignment="Left"
Margin="0,4,0,0"
VerticalAlignment="Top"
Visibility="{Binding current_algo_custom_state.Count, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:DIYGrid Grid.Row="1" Rows="{Binding current_algo_custom_state}" Margin="0,28,0,4">
<c:DIYGrid.Visibility>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:BooleanAndConverter/>
<cv:BooleanToVisibilityConverter/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="current_algo_custom_state.Count" Mode="OneWay" Converter="{StaticResource IntNotZeroConverter}"/>
<Binding Path="IsChecked" ElementName="algo_state_toggle"/>
</MultiBinding>
</c:DIYGrid.Visibility>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<c:DIYGridColumn id="Name" Header="Param" Width="176"/>
<c:DIYGridColumn id="Value" Header="Value" Width="176"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<c:TextCell c:DIYGrid.ColumnKey="Name" Text="{Binding name}"/>
<c:TextCell c:DIYGrid.ColumnKey="Value" Text="{Binding value}"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StackPanel Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
<StackPanel Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
<Button x:Name="modify_orders_button" Command="{Binding modify_selected_orders_cmd}">
<Button.Margin>
<Thickness Right="{StaticResource DialogSkirtButtonPadding.Right}"/>
@@ -5,7 +5,6 @@ using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.utils;
using System;
@@ -141,18 +140,15 @@ namespace bts.shell.pnl {
public PositionPriceSize summary =>
new PositionPriceSize(long_data.working_total, long_data.avg_price, short_data.working_total, short_data.avg_price);
public static WorkingOrderData Create(IEnumerable<OrderRecordImmutable> orders, QuantityConversionData qty_conversion) =>
Create(orders.Select(o => (o, qty_conversion)));
public static WorkingOrderData Create(IEnumerable<OrderRecordImmutable> orders) =>
orders.Aggregate(Empty, (seed, order) => seed.Add(order));
public static WorkingOrderData Create(IEnumerable<(OrderRecordImmutable order, QuantityConversionData qty_conversion)> orders) =>
orders.Aggregate(Empty, (seed, tup) => seed.Add(tup.order, tup.qty_conversion));
public WorkingOrderData Add(OrderRecordImmutable order, QuantityConversionData qty_conversion) {
public WorkingOrderData Add(OrderRecordImmutable order) {
if (order.working_quantity() <= 0)
return new WorkingOrderData(long_data, short_data);
switch (order.side()) {
case Side.Buy: return new WorkingOrderData(long_data.Add(order, qty_conversion), short_data);
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order, qty_conversion));
case Side.Buy: return new WorkingOrderData(long_data.Add(order), short_data);
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order));
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
}
}
@@ -169,21 +165,18 @@ namespace bts.shell.pnl {
}
readonly struct WorkingOrderSideData {
public readonly double avg_price, display_avg_price;
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable order, QuantityConversionData qty_conversion)>> data;
public readonly double avg_price;
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data;
public readonly int working_total;
public readonly int display_working_total;
WorkingOrderSideData(double avg_price, int working_total, double display_avg_price, int display_working_total, ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>> data) {
WorkingOrderSideData(double avg_price, int working_total, ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data) {
this.avg_price = avg_price;
this.working_total = working_total;
this.display_avg_price = display_avg_price;
this.data = data;
this.display_working_total = display_working_total;
}
public static WorkingOrderSideData EmptyLong => new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(new ReverseComparer<double>(OrderDataHelper.PriceComparer)));
public static WorkingOrderSideData EmptyShort => new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(OrderDataHelper.PriceComparer));
public static WorkingOrderSideData EmptyLong => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(new ReverseComparer<double>(OrderDataHelper.PriceComparer)));
public static WorkingOrderSideData EmptyShort => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(OrderDataHelper.PriceComparer));
public double top_price =>
data.Count > 0 ?
@@ -192,35 +185,23 @@ namespace bts.shell.pnl {
public int top_volume =>
data.Count > 0 ?
data.Values.First().Values.Sum(o => o.order.working_quantity()) :
data.Values.First().Values.Sum(o => o.working_quantity()) :
0;
public int display_top_volume =>
data.Count > 0 ?
data.Values.First().Values.Sum(o => o.qty_conversion.to_display(o.order.working_quantity())) :
0;
public WorkingOrderSideData Add(OrderRecordImmutable order_record) =>
Add(order_record, new QuantityConversionData());
public WorkingOrderSideData Add(OrderRecordImmutable order_record, QuantityConversionData qty_conversion) {
public WorkingOrderSideData Add(OrderRecordImmutable order_record) {
uint order = order_record.Key;
int working_qty = order_record.working_quantity();
double price = order_record.price();
if (working_qty <= 0)
throw new ArgumentException(nameof(working_qty));
uint order = order_record.Key;
int dworking_qty = qty_conversion.to_display(working_qty);
double price = order_record.price();
if (working_total == 0) {
Log.assert(display_working_total == 0);
return new WorkingOrderSideData(price, working_qty, display_avg_price, dworking_qty, data.Add(price, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>.Empty.Add(order, (order_record, qty_conversion))));
return new WorkingOrderSideData(price, working_qty, data.Add(price, ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
} else return new WorkingOrderSideData(
avg_price + ((price - avg_price) * ((double)working_qty / (working_total + working_qty))),
working_total + working_qty,
display_avg_price + ((price - display_avg_price) * ((double)dworking_qty / (display_working_total + dworking_qty))),
display_working_total + dworking_qty,
data.SetItem(price, data.TryGetValue(price, out var curr) ?
curr.Add(order, (order_record, qty_conversion)) :
ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>.Empty.Add(order, (order_record, qty_conversion))));
curr.Add(order, order_record) :
ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
}
public WorkingOrderSideData Remove(uint order, double price) {
@@ -229,30 +210,22 @@ namespace bts.shell.pnl {
throw new KeyNotFoundException(nameof(price));
if (!wdata.TryGetValue(order, out var worder))
throw new KeyNotFoundException(nameof(order));
var wqty = worder.order.working_quantity();
var dwqty = worder.qty_conversion.to_display(wqty);
var wqty = worder.working_quantity();
wdata = wdata.Remove(order);
if (wdata.Count == 0)
tmp_data = tmp_data.Remove(price);
if (tmp_data.Count == 0)
return new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(data.KeyComparer));
return new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(data.KeyComparer));
return new WorkingOrderSideData(
avg_price - ((price - avg_price) * (wqty / (working_total - wqty))),
working_total - wqty,
display_avg_price - ((price - display_avg_price) * (dwqty / (display_working_total - dwqty))),
display_working_total - dwqty,
tmp_data);
}
public override string ToString() {
if (data.Count > 0) {
var best_order = data.Values.First().Values.First();
return OrderUtils.order_collection_summary(
best_order.order.price(),
display_avg_price,
best_order.qty_conversion.to_display(best_order.order.working_quantity()),
display_working_total,
SecurityDataHelper.Defaults.ContractSpecificationImmutable);
return OrderUtils.order_collection_summary(best_order.price(), avg_price, best_order.working_quantity(), working_total, SecurityDataHelper.Defaults.ContractSpecificationImmutable);
}
return string.Empty;
}
@@ -275,25 +248,21 @@ namespace bts.shell.pnl {
public readonly PNLKey key;
public readonly SecurityImmutable? security;
public readonly QuantityConversionData qty_conversion;
public readonly string account_name;
public readonly PNLCalculationType calc_type;
public readonly PositionPriceSize day, raw_day, sod;
public readonly WorkingOrderData working;
public readonly double
default_cpp,
cpp,
bid_price,
ask_price,
last_price,
settlement_price;
public readonly double? override_cpp;
public PNLRowData(
PNLKey key,
double default_cpp = 0,
double? override_cpp = null,
double cpp = 0,
double bid_price = 0,
double ask_price = 0,
double last_price = 0,
@@ -304,12 +273,10 @@ namespace bts.shell.pnl {
WorkingOrderData? working = null,
SecurityImmutable? security = null,
string account_name = null,
PNLCalculationType calc_type = PNLCalculationType.Mid,
QuantityConversionData? qty_conversion = null) {
PNLCalculationType calc_type = PNLCalculationType.Mid) {
//
this.key = key;
this.default_cpp = default_cpp;
this.override_cpp = override_cpp;
this.cpp = cpp;
this.bid_price = bid_price;
this.ask_price = ask_price;
this.last_price = last_price;
@@ -321,7 +288,6 @@ namespace bts.shell.pnl {
this.security = security;
this.account_name = account_name;
this.calc_type = calc_type;
this.qty_conversion = qty_conversion ?? new QuantityConversionData();
}
[Flags]
@@ -339,9 +305,6 @@ namespace bts.shell.pnl {
AskLTESettle = Ask | Settle | 1 << 7
}
public string symbol => security?.pnl_symbol() ?? string.Empty;
public double effective_cpp => override_cpp ?? default_cpp;
/// Market/Calc
public double mid_price => (bid_price + ask_price) / 2;
public double pnl_price {
@@ -381,155 +344,117 @@ namespace bts.shell.pnl {
}
}
/// Sod
public double sod_pnl => effective_cpp *
public double sod_pnl => cpp *
DoubleUtil.nan_to_zero_sum(
sod_long_avg_price_delta_raw * sod_long_pos,
sod_short_avg_price_delta_raw * sod_short_pos
);
// long
public double sod_long_pos => sod.long_pos;
public double sod_long_avg_price => effective_cpp * sod.long_avg;
public double sod_long_avg_price => cpp * sod.long_avg;
public double sod_long_avg_price_raw => sod.long_avg;
public double sod_long_avg_price_delta => effective_cpp * sod_long_avg_price_delta_raw;
public double sod_long_avg_price_delta => cpp * sod_long_avg_price_delta_raw;
public double sod_long_avg_price_delta_raw => sod.long_pos == 0 ? double.NaN : long_pnl_price - sod.long_avg;
// short
public double sod_short_pos => sod.short_pos;
public double sod_short_avg_price => effective_cpp * sod.short_avg;
public double sod_short_avg_price => cpp * sod.short_avg;
public double sod_short_avg_price_raw => sod.short_avg;
public double sod_short_avg_price_delta => effective_cpp * sod_short_avg_price_delta_raw;
public double sod_short_avg_price_delta => cpp * sod_short_avg_price_delta_raw;
public double sod_short_avg_price_delta_raw => sod.short_pos == 0 ? double.NaN : sod.short_avg - short_pnl_price;
// net
public double sod_net_pos => sod.net_pos;
public double sod_net_avg_price_delta => effective_cpp * sod_net_avg_price_delta_raw;
public double sod_net_avg_price_delta => cpp * sod_net_avg_price_delta_raw;
public double sod_net_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_long_avg_price_delta_raw, sod.long_pos,
sod_short_avg_price_delta_raw, sod.short_pos);
// display
public double display_sod_net_pos => qty_conversion.to_display(sod_net_pos);
public double display_sod_long_pos => qty_conversion.to_display(sod_long_pos);
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
/// Day
public double day_pnl => effective_cpp *
public double day_pnl => cpp *
DoubleUtil.nan_to_zero_sum(
day_long_avg_price_delta_raw * day_long_pos,
day_short_avg_price_delta_raw * day_short_pos
);
// long
public double day_long_pos => day.long_pos;
public double day_long_avg_price => effective_cpp * day.long_avg;
public double day_long_avg_price => cpp * day.long_avg;
public double day_long_avg_price_raw => day.long_avg;
public double day_long_avg_price_delta => effective_cpp * day_long_avg_price_delta_raw;
public double day_long_avg_price_delta => cpp * day_long_avg_price_delta_raw;
public double day_long_avg_price_delta_raw => day.long_pos == 0 ? double.NaN : long_pnl_price - day.long_avg;
// short
public double day_short_pos => day.short_pos;
public double day_short_avg_price => effective_cpp * day.short_avg;
public double day_short_avg_price => cpp * day.short_avg;
public double day_short_avg_price_raw => day.short_avg;
public double day_short_avg_price_delta => effective_cpp * day_short_avg_price_delta_raw;
public double day_short_avg_price_delta => cpp * day_short_avg_price_delta_raw;
public double day_short_avg_price_delta_raw => day.short_pos == 0 ? double.NaN : day.short_avg - short_pnl_price;
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
// net
public double day_net_pos => day.net_pos;
public double day_net_avg_price_delta => effective_cpp * day_net_avg_price_delta_raw;
public double day_net_avg_price_delta => cpp * day_net_avg_price_delta_raw;
public double day_net_avg_price_delta_raw =>
MathUtils.weighted_average(
day_long_avg_price_delta_raw, day.long_pos,
day_short_avg_price_delta_raw, day.short_pos);
// display
public double display_day_net_pos => qty_conversion.to_display(day_net_pos);
public double display_day_long_pos => qty_conversion.to_display(day_long_pos);
public double display_day_short_pos => qty_conversion.to_display(day_short_pos);
/// Raw Day
public double raw_day_long_pos => raw_day.long_pos;
public double raw_day_short_pos => raw_day.short_pos;
public double raw_day_net_pos => raw_day.net_pos;
// display
public double display_raw_day_net_pos => qty_conversion.to_display(raw_day_net_pos);
public double display_raw_day_long_pos => qty_conversion.to_display(raw_day_long_pos);
public double display_raw_day_short_pos => qty_conversion.to_display(raw_day_short_pos);
/// Total
public double total_pnl => DoubleUtil.nan_to_zero_sum(sod_pnl, day_pnl);
// long
public double total_long_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, day.long_pos);
public double total_long_avg_price => effective_cpp * total_long_avg_price_raw;
public double total_long_avg_price => cpp * total_long_avg_price_raw;
public double total_long_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.long_avg, day.long_avg);
public double total_long_avg_price_delta => effective_cpp * total_long_avg_price_delta_raw;
public double total_long_avg_price_delta => cpp * total_long_avg_price_delta_raw;
public double total_long_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_long_avg_price_delta_raw, sod.long_pos,
day_long_avg_price_delta_raw, day.long_pos);
// short
public double total_short_pos => DoubleUtil.nan_to_zero_sum(sod.short_pos, day.short_pos);
public double total_short_avg_price => effective_cpp * total_short_avg_price_raw;
public double total_short_avg_price => cpp * total_short_avg_price_raw;
public double total_short_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.short_avg, day.short_avg);
public double total_short_avg_price_delta => effective_cpp * total_short_avg_price_delta_raw;
public double total_short_avg_price_delta => cpp * total_short_avg_price_delta_raw;
public double total_short_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_short_avg_price_delta_raw, sod.short_pos,
day_short_avg_price_delta_raw, day.short_pos);
// net
public double total_net_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos);
public double total_net_avg_price_delta => effective_cpp * total_net_avg_price_delta_raw;
public double total_net_avg_price_delta => cpp * total_net_avg_price_delta_raw;
public double total_net_avg_price_delta_raw =>
MathUtils.weighted_average(
total_long_avg_price_delta_raw, total_long_pos,
total_short_avg_price_delta_raw, total_short_pos);
// display
public double display_total_net_pos => qty_conversion.to_display(total_net_pos);
public double display_total_long_pos => qty_conversion.to_display(total_long_pos);
public double display_total_short_pos => qty_conversion.to_display(total_short_pos);
#region Working
// long
public double working_long_pos => working.long_data.working_total;
public double working_long_avg_price => effective_cpp * working_long_avg_price_raw;
public double working_long_top_price => effective_cpp * working_long_top_price_raw;
public double working_long_avg_price => cpp * working_long_avg_price_raw;
public double working_long_top_price => cpp * working_long_top_price_raw;
public double working_long_top_price_raw => working.top_bid;
public double working_long_avg_price_raw => working.long_data.avg_price;
public double working_long_avg_price_delta => effective_cpp * working_long_avg_price_delta_raw;
public double working_long_avg_price_delta => cpp * working_long_avg_price_delta_raw;
public double working_long_avg_price_delta_raw => working_long_pos == 0 ? double.NaN : long_pnl_price - working_long_avg_price_raw;
// short
public double working_short_pos => working.short_data.working_total;
public double working_short_avg_price => effective_cpp * working_short_avg_price_raw;
public double working_short_top_price => effective_cpp * working_short_top_price_raw;
public double working_short_avg_price => cpp * working_short_avg_price_raw;
public double working_short_top_price => cpp * working_short_top_price_raw;
public double working_short_top_price_raw => working.top_ask;
public double working_short_avg_price_raw => working.short_data.avg_price;
public double working_short_avg_price_delta => effective_cpp * working_short_avg_price_delta_raw;
public double working_short_avg_price_delta => cpp * working_short_avg_price_delta_raw;
public double working_short_avg_price_delta_raw => working_short_pos == 0 ? double.NaN : working_short_avg_price_raw - short_pnl_price;
// net
public double working_net_pos => working_long_pos - working_short_pos;
public double working_net_avg_price_delta => effective_cpp * working_net_avg_price_delta_raw;
public double working_net_avg_price_delta => cpp * working_net_avg_price_delta_raw;
public double working_net_avg_price_delta_raw =>
MathUtils.weighted_average(
working_long_avg_price_delta_raw, working_long_pos,
working_short_avg_price_delta_raw, working_short_pos);
// display
public double display_working_net_pos => qty_conversion.to_display(working_net_pos);
public double display_working_long_pos => qty_conversion.to_display(working_long_pos);
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
#endregion
public PNLRowData with(SecurityImmutable? security = null, string account_name = null, WorkingOrderData? working = null, PNLCalculationType? calc_type = null) =>
new PNLRowData(
key,
default_cpp,
override_cpp,
bid_price,
ask_price,
last_price,
settlement_price,
sod,
day,
raw_day,
working ?? this.working,
security ?? this.security,
account_name ?? this.account_name,
calc_type ?? this.calc_type,
qty_conversion: qty_conversion);
WaterfallPriceFlags _waterfall_flags() {
var flags = WaterfallPriceFlags.None;
if (!double.IsNaN(bid_price)) flags |= WaterfallPriceFlags.Bid;
@@ -571,8 +496,7 @@ namespace bts.shell.pnl {
public bool Equals(PNLRowData other) =>
key == other.key &&
default_cpp == other.default_cpp &&
override_cpp == other.override_cpp &&
cpp == other.cpp &&
security.HasValue == other.security.HasValue &&
account_name == other.account_name &&
sod == other.sod &&
@@ -584,8 +508,7 @@ namespace bts.shell.pnl {
settlement_price.Equals(other.settlement_price) &&
calc_type.Equals(other.calc_type) &&
raw_day == other.raw_day &&
working.summary == other.working.summary &&
qty_conversion == other.qty_conversion;
working.summary == other.working.summary;
public override bool Equals(object obj) =>
obj is PNLRowData other && Equals(other);
@@ -600,12 +523,9 @@ namespace bts.shell.pnl {
}
static class PNLRowDataFactory {
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value, QuantityConversionData qty_conversion) t) {
var (default_cpp, override_cpp) = t.value.pnl_cpp_tuple(t.sec);
return new PNLRowData(
new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
default_cpp,
override_cpp,
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value) t) =>
new PNLRowData(new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
t.value.CashValue,
t.value.BidPrice,
t.value.AskPrice,
t.value.LastPrice,
@@ -615,27 +535,25 @@ namespace bts.shell.pnl {
t.value.Pos.raw_day_pps(),
WorkingOrderData.Empty,
t.sec,
t.value.Pos.Account,
qty_conversion: t.qty_conversion);
}
t.value.Pos.Account);
}
static class PNLDataExtensions {
public static string pnl_symbol(this SecurityImmutable security) {
return bts.api.ExchangeSpecificUitlities.get_pnl_symbol(security);
}
// while override_cpp could use double.NaN to indicate the lack of an override,
// the nullable double makes it clear that it is an optional value.
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable? security) =>
security.HasValue ?
pnl_cpp_tuple(pnl_unit, security.Value) :
(pnl_unit.CashValue, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable security) {
// TODO: actual impl
double calced_pnl = bts.api.ExchangeSpecificUitlities.get_cpp(pnl_unit,security);
return (calced_pnl, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
}
// Note: PNLRowData is passed by ref because it is fairly large struct, and since it is a readonly struct, we know it cannot be modified (so might as well avoid excessive copying).
public static PNLRowData with(this PNLRowData pnl_data, SecurityImmutable? security = null, string account_name = null, WorkingOrderData? working = null, PNLCalculationType? calc_type = null) =>
new PNLRowData(
pnl_data.key,
pnl_data.cpp,
pnl_data.bid_price,
pnl_data.ask_price,
pnl_data.last_price,
pnl_data.settlement_price,
pnl_data.sod,
pnl_data.day,
pnl_data.raw_day,
working ?? pnl_data.working,
security ?? pnl_data.security,
account_name ?? pnl_data.account_name,
calc_type ?? pnl_data.calc_type);
}
}
}
@@ -16,15 +16,7 @@ namespace bts.shell.pnl {
decimal sod_long_pos,
decimal sod_short_pos,
decimal working_long_pos = 0,
decimal working_short_pos = 0,
decimal? display_day_long_pos = null,
decimal? display_day_short_pos = null,
decimal? display_raw_day_long_pos = null,
decimal? display_raw_day_short_pos = null,
decimal? display_sod_long_pos = null,
decimal? display_sod_short_pos = null,
decimal? display_working_long_pos = null,
decimal? display_working_short_pos = null) {
decimal working_short_pos = 0) {
//
this.day_pnl = day_pnl;
this.day_long_pos = day_long_pos;
@@ -36,15 +28,6 @@ namespace bts.shell.pnl {
this.sod_short_pos = sod_short_pos;
this.working_long_pos = working_long_pos;
this.working_short_pos = working_short_pos;
// display
this.display_day_long_pos = display_day_long_pos ?? day_long_pos;
this.display_day_short_pos = display_day_short_pos ?? day_short_pos;
this.display_raw_day_long_pos = display_raw_day_long_pos ?? raw_day_long_pos;
this.display_raw_day_short_pos = display_raw_day_short_pos ?? raw_day_short_pos;
this.display_sod_long_pos = display_sod_long_pos ?? sod_long_pos;
this.display_sod_short_pos = display_sod_short_pos ?? sod_short_pos;
this.display_working_long_pos = display_working_long_pos ?? working_long_pos;
this.display_working_short_pos = display_working_short_pos ?? working_short_pos;
}
// sod
@@ -52,48 +35,24 @@ namespace bts.shell.pnl {
public decimal sod_long_pos { get; }
public decimal sod_short_pos { get; }
public decimal sod_net_pos => sod_long_pos - sod_short_pos;
// sod display
public decimal display_sod_long_pos { get; }
public decimal display_sod_short_pos { get; }
public decimal display_sod_net_pos => display_sod_long_pos - display_sod_short_pos;
// day
public decimal day_pnl { get; }
public decimal day_long_pos { get; }
public decimal day_short_pos { get; }
public decimal day_net_pos => day_long_pos - day_short_pos;
// display
public decimal display_day_long_pos { get; }
public decimal display_day_short_pos { get; }
public decimal display_day_net_pos => display_day_long_pos - display_day_short_pos;
// raw day
public decimal raw_day_long_pos { get; }
public decimal raw_day_short_pos { get; }
public decimal raw_day_net_pos => raw_day_long_pos - raw_day_short_pos;
// display
public decimal display_raw_day_long_pos { get; }
public decimal display_raw_day_short_pos { get; }
public decimal display_raw_day_net_pos => display_raw_day_long_pos - display_raw_day_short_pos;
// total
public decimal total_pnl => sod_pnl + day_pnl;
public decimal total_net_pos => total_long_pos - total_short_pos;
public decimal total_long_pos => sod_long_pos + day_long_pos;
public decimal total_short_pos => sod_short_pos + day_short_pos;
// display
public decimal display_total_long_pos => display_sod_long_pos + display_day_long_pos;
public decimal display_total_short_pos => display_sod_short_pos + display_day_short_pos;
public decimal display_total_net_pos => display_total_long_pos - display_total_short_pos;
// working
public decimal working_long_pos { get; }
public decimal working_short_pos { get; }
public decimal working_net_pos => working_long_pos - working_short_pos;
// display
public decimal display_working_long_pos { get; }
public decimal display_working_short_pos { get; }
public decimal display_working_net_pos => display_working_long_pos - display_working_short_pos;
public decimal working_short_pos { get; }
public bool Equals(PNLNormalizedData other) =>
day_pnl == other.day_pnl &&
@@ -105,17 +64,6 @@ namespace bts.shell.pnl {
sod_long_pos == other.sod_long_pos &&
sod_short_pos == other.sod_short_pos &&
working_long_pos == other.working_long_pos &&
working_short_pos == other.working_short_pos &&
// display
sod_long_pos == other.sod_long_pos &&
sod_short_pos == other.sod_short_pos &&
day_long_pos == other.day_long_pos &&
day_short_pos == other.day_short_pos &&
raw_day_long_pos == other.raw_day_long_pos &&
raw_day_short_pos == other.raw_day_short_pos &&
total_long_pos == other.total_long_pos &&
total_short_pos == other.total_short_pos &&
working_long_pos == other.working_long_pos &&
working_short_pos == other.working_short_pos;
public override bool Equals(object obj) =>
@@ -154,16 +102,7 @@ namespace bts.shell.pnl {
a.sod_long_pos + b.sod_long_pos,
a.sod_short_pos + b.sod_short_pos,
a.working_long_pos + b.working_long_pos,
a.working_short_pos + b.working_short_pos,
// display
a.display_day_long_pos + b.display_day_long_pos,
a.display_day_short_pos + b.display_day_short_pos,
a.display_raw_day_long_pos + b.display_raw_day_long_pos,
a.display_raw_day_short_pos + b.display_raw_day_short_pos,
a.display_sod_long_pos + b.display_sod_long_pos,
a.display_sod_short_pos + b.display_sod_short_pos,
a.display_working_long_pos + b.display_working_long_pos,
a.display_working_short_pos + b.display_working_short_pos);
a.working_short_pos + b.working_short_pos);
public static PNLNormalizedData operator -(PNLNormalizedData a, PNLNormalizedData b) =>
new PNLNormalizedData(
@@ -176,15 +115,6 @@ namespace bts.shell.pnl {
a.sod_long_pos - b.sod_long_pos,
a.sod_short_pos - b.sod_short_pos,
a.working_long_pos - b.working_long_pos,
a.working_short_pos - b.working_short_pos,
// display
a.display_day_long_pos - b.display_day_long_pos,
a.display_day_short_pos - b.display_day_short_pos,
a.display_raw_day_long_pos - b.display_raw_day_long_pos,
a.display_raw_day_short_pos - b.display_raw_day_short_pos,
a.display_sod_long_pos - b.display_sod_long_pos,
a.display_sod_short_pos - b.display_sod_short_pos,
a.display_working_long_pos - b.display_working_long_pos,
a.display_working_short_pos - b.display_working_short_pos);
a.working_short_pos - b.working_short_pos);
}
}
@@ -1,5 +1,4 @@
using bts.api;
using bts.api.position;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
@@ -12,7 +11,6 @@ using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.property_editor;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -24,7 +22,6 @@ using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
@@ -37,13 +34,11 @@ namespace bts.shell {
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
readonly IPositionModel _position_model;
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model, IPositionModel position_model) : base(owner, _window_type) {
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_position_model = position_model;
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
@@ -110,8 +105,6 @@ namespace bts.shell {
menu_display: menu_display_vm.create());
return new PNLSaveState { use_defaults = false, settings = state };
};
IDisposable cpp_override_dialog = null;
IEnumerable<SparkCommand> commands = new[] {
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
@@ -123,26 +116,6 @@ namespace bts.shell {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Override CPP...", Icons.CurrencyExchange, new DelegateCommand(() => {
Disposable.dispose(ref cpp_override_dialog);
var security_keys = vm.selected_rows.Select(r => r.security_key.value).Distinct().ToList();
double initial_cpp = vm.selected_rows.Select(r => r.cpp.value).Distinct().SafeSingleOrDefault();
override_cpp_async().ignore();
async Task override_cpp_async() {
try {
var cts = new CancellationTokenSource();
cpp_override_dialog = Disposable.Create(new Action(cts.Cancel));
var (cpp, committed) = await PropertyEditor.get_double_async("Override CPP", "value", cts.Token, initial_cpp);
if (committed && cpp.is_real_positive())
await _position_model.override_cpp_async(security_keys, cpp);
} catch (Exception ex) {
Log.Ui.warn($"Error overriding cpp: {ex}.");
}
}
})) {
show_in_context_menu = true,
show_in_toolbar = false
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
@@ -217,7 +190,6 @@ namespace bts.shell {
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
Disposable.Create(() => Disposable.dispose(ref cpp_override_dialog)),
menu_display_vm,
vm);
return (content, commands, get_save_state, dispose);
@@ -412,17 +384,5 @@ namespace bts.shell {
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class InitializeDataFormattingMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && settings.data_formatting == null) {
new_settings = settings.with(data_formatting: PNLSettings.Default.data_formatting);
return true;
}
new_settings = null;
return false;
}
}
}
}
@@ -204,11 +204,11 @@ namespace bts.shell.pnl {
// impl (add all)
bd.add_discrete_values(fs.account, d => d.key.account_id);
bd.add_discrete_values(fs.exchange, d => d.security.exchange_or_default());
bd.add_discrete_values(fs.symbol, d => d.symbol);
bd.add_discrete_values(fs.symbol, d => d.security.symbol_or_default());
bd.add_discrete_values(fs.security_type, d => d.security.HasValue ? d.security.Value.ContractCase : Security.ContractOneofCase.None);
bd.add_regex(fs.security, d => d.security.ToString());
bd.add_regex(fs.security_key, d => d.key.security_id.ToString());
bd.add_numeric(fs.cpp, d => d.effective_cpp);
bd.add_numeric(fs.cpp, d => d.cpp);
/// sod
bd.add_numeric(fs.sod_pnl, d => d.sod_pnl);
bd.add_numeric(fs.sod_long_pos, d => d.sod_long_pos);
File diff suppressed because it is too large. Load diff
@@ -1,52 +0,0 @@
using bts.utils;
using mbts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
namespace bts.shell.pnl {
class PNLDataFormattingVM : ViewModelBase, IValueFactory<PNLDataFormatting> {
const uint _min_decimal_places = 1, _max_decimal_places = 6;
uint _market_price_decimal_places, _fill_price_decimal_places;
public uint market_price_decimal_places {
get => _market_price_decimal_places;
set => Set(ref _market_price_decimal_places, value);
}
public uint fill_price_decimal_places {
get => _fill_price_decimal_places;
set => Set(ref _fill_price_decimal_places, value);
}
public IEnumerable<uint> decimal_place_options {
get {
for (uint n = _min_decimal_places; n <= _max_decimal_places; n++)
yield return n;
}
}
public PNLDataFormatting create() =>
new PNLDataFormatting {
fill_price_display = new DoubleFormatting {
tick_factors = new mbts.user.settings.TickFactors(
Math.Pow(10, MathUtils.within_range(_min_decimal_places, _max_decimal_places, fill_price_decimal_places)),
1)
},
market_price_display = new DoubleFormatting {
tick_factors = new mbts.user.settings.TickFactors(
Math.Pow(10, MathUtils.within_range(_min_decimal_places, _max_decimal_places, market_price_decimal_places)),
1)
}
};
public void load_from(PNLDataFormatting value) {
if (value != null) {
if (value.market_price_display?.tick_factors != null) {
market_price_decimal_places = (uint)Math.Log10(value.market_price_display.tick_factors.tick_base);
}
if (value.fill_price_display?.tick_factors != null) {
fill_price_decimal_places = (uint)Math.Log10(value.fill_price_display.tick_factors.tick_base);
}
}
}
}
}
@@ -14,6 +14,7 @@ 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;
@@ -51,7 +52,6 @@ namespace bts.shell.pnl {
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLDataFormattingVM data_formatting_vm { get; } = new PNLDataFormattingVM();
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); }
@@ -63,7 +63,6 @@ namespace bts.shell.pnl {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
s.data_formatting = data_formatting_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
List<GridColumnSettings> new_cols = null;
IEnumerable<GridColumnSettings> cols = s.grid.columns;
@@ -137,7 +136,6 @@ namespace bts.shell.pnl {
_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())));
data_formatting_vm.load_from(value.data_formatting);
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);
@@ -156,7 +154,7 @@ namespace bts.shell.pnl {
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);
@@ -14,7 +14,7 @@ namespace bts.shell.pnl {
public readonly DIYGridAnimationModel<PNLView.Columns, TRow> animation_model = new DIYGridAnimationModel<PNLView.Columns, TRow>(TimeSpan.FromSeconds(0.3));
static readonly Func<WorkingOrderSideData, string> _fmt_working_order_data =
d => OrderUtils.order_collection_summary(d.top_price, d.display_top_volume, d.display_working_total);
d => OrderUtils.order_collection_summary(d.top_price, d.top_volume, d.working_total);
static readonly IComparer<WorkingOrderSideData> _working_total_comparer =
Comparer<int>.Default.wrap<int, WorkingOrderSideData>(d => d.working_total);
@@ -55,11 +55,11 @@ namespace bts.shell.pnl {
}
class PNLCellVM<TRow, TValue> : ViewModelBase, IObserver<PNLAnimation>, IPNLCellVM<TRow>, IComparable<PNLCellVM<TRow, TValue>>, IComparable {
readonly Func<TValue, string> _fmt;
readonly Func<TRow, TValue> _get;
readonly IEqualityComparer<TValue> _eq_comparer;
readonly IComparer<TValue> _comparer;
readonly PNLView.Columns _column;
Func<TValue, string> _fmt;
IComparer _value_comparer;
Color
_foreground = Colors.Black,
@@ -123,15 +123,6 @@ namespace bts.shell.pnl {
set => Set(ref _font_weight, value);
}
public Func<TValue, string> format {
get => _fmt;
set {
_fmt = value;
_text = null;
RaisePropertyChanged(nameof(text));
}
}
public void on_row(TRow row) =>
value = _get(row);
@@ -22,13 +22,7 @@ namespace bts.shell.pnl {
bool _refresh_normalized_data = true, _is_selected;
PNLNormalizedData _normalized_data;
public PNLRowVM(
PNLRowData data,
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
Func<double, string> fmt_fill_price,
Func<double, string> fmt_market_price) {
//
public PNLRowVM(PNLRowData data, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
this.data = data;
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
_animator.on_row(data);
@@ -36,41 +30,41 @@ namespace bts.shell.pnl {
account_name_cell = _cell_map.add(ColumnKeys.Account, d => d.account_name);
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
security_cell = _cell_map.add(ColumnKeys.Security, d => (d.security), null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.security?.Symbol ?? string.Empty);
security_cell = _cell_map.add(ColumnKeys.Security, d => d.security, null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
cpp = _cell_map.add(ColumnKeys.CPP, d => d.cpp);
// sod
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.display_sod_net_pos);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.display_sod_long_pos);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price_raw);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.display_sod_short_pos);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price_raw);
// day
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.display_day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.display_day_long_pos);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price_raw);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.display_day_short_pos);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price_raw);
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
// raw day
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.display_raw_day_net_pos);
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.display_raw_day_long_pos);
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.display_raw_day_short_pos);
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
// working
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.display_working_net_pos);
working_net_pos = _cell_map.add_net_pos(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
working_long_pos = _cell_map.add_working_long(ColumnKeys.WorkingLongPos, d => d.working.long_data, d => d.bid_price);
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price_raw);
working_short_pos = _cell_map.add_working_short(ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price);
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price_raw);
// total
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.display_total_net_pos);
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.display_total_long_pos);
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price_raw);
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.display_total_short_pos);
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price_raw);
// market
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
@@ -79,7 +73,6 @@ namespace bts.shell.pnl {
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
_cell_map.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
@@ -119,16 +112,7 @@ namespace bts.shell.pnl {
sod_long,
sod_short,
DoubleUtil.round_to_decimal(data.working_long_pos),
DoubleUtil.round_to_decimal(data.working_short_pos),
// display
DoubleUtil.round_to_decimal(data.display_day_long_pos),
DoubleUtil.round_to_decimal(data.display_day_short_pos),
DoubleUtil.round_to_decimal(data.display_raw_day_long_pos),
DoubleUtil.round_to_decimal(data.display_raw_day_short_pos),
DoubleUtil.round_to_decimal(data.display_sod_long_pos),
DoubleUtil.round_to_decimal(data.display_sod_short_pos),
DoubleUtil.round_to_decimal(data.display_working_long_pos),
DoubleUtil.round_to_decimal(data.display_working_short_pos));
DoubleUtil.round_to_decimal(data.working_short_pos));
if (_normalized_data != nd) {
_normalized_data = nd;
updated?.Invoke(nd);
@@ -192,7 +176,6 @@ namespace bts.shell.pnl {
public PNLCellVM<PNLRowData, double> pnl_price { get; }
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
public bool is_selected {
get => _is_selected;
set {
@@ -201,35 +184,6 @@ namespace bts.shell.pnl {
}
}
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
get {
yield return pnl_price;
yield return last_price;
yield return bid_price;
yield return mid_price;
yield return ask_price;
yield return settlement_price;
}
}
// includes sod, day, total, and working avg price columns
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
get {
// sod
yield return sod_long_avg_price;
yield return sod_short_avg_price;
// day
yield return day_long_avg_price;
yield return day_short_avg_price;
// total
yield return total_long_avg_price;
yield return total_short_avg_price;
// working
yield return working_long_avg_price;
yield return working_short_avg_price;
}
}
public void update(PNLRowData data) {
_refresh_normalized_data = true;
this.data = data;
@@ -238,10 +192,9 @@ namespace bts.shell.pnl {
_cell_map.set_appearances(_animator.cell_appearances);
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
_animator.on_settings(defaults, triggers);
_cell_map.set_appearances(_animator.cell_appearances);
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
}
public bool Equals(PNLRowVM other) =>
@@ -252,14 +205,5 @@ namespace bts.shell.pnl {
public override string ToString() =>
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
foreach (var cell in _fill_price_cells) {
cell.format = fmt_fill_price;
}
foreach (var cell in _market_price_cells) {
cell.format = fmt_market_price;
}
}
}
}
}
@@ -41,9 +41,6 @@ namespace bts.shell.pnl {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly IObservableCollectionAddRemoveOnly<PNLRowVM> _selected_rows = new ObservableSortedCollection<PNLRowVM>(PNLRowVM.key_comparer);
/// treat <see cref="_rows_data_wrapper"/> and <see cref="_rows_key_wrapper"/>
/// as IReadOnlyLists - don't call any mutation methods.
/// The only reason they are ILists is for the binary_search methods.
readonly IList<PNLRowData> _rows_data_wrapper;
readonly IList<PNLKey> _rows_key_wrapper;
readonly DeferrableActionWrapper _refresh_filter;
@@ -54,7 +51,6 @@ namespace bts.shell.pnl {
IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> _sorting_hierarchy;
Brush _selection_highlight;
Color _grid_background_color = Colors.Transparent;
Func<double, string> _fmt_fill_price_backing, _fmt_market_price_backing;
bool _is_paused;
// default appearances
ImmutableDictionary<ColumnKeys, PNLCellAppearance>
@@ -78,7 +74,7 @@ namespace bts.shell.pnl {
PNLVM(IBus bus, OrdersModel orders_model, PNLSettings settings, IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) {
_bus = bus;
_orders_model = orders_model;
_rows_data_wrapper = rows.WrapSelector(r => r.data, null);
_rows_data_wrapper = rows.WrapSelector(r => r.data, d => new PNLRowVM(d, _default_appearances, _triggers));
_rows_key_wrapper = rows.WrapSelector(r => r.key, null);
_rows_view = new PNLRowCollectionView(rows, _default_group_appearances, _group_triggers);
_calculation_type = backing(nameof(calculation_type), PNLCalculationType.Waterfall);
@@ -92,12 +88,6 @@ namespace bts.shell.pnl {
});
// filters
account_filter = DiscreteValuesFilterVM.Common.account<PNLRowData>(orders_model, r => new AccountTuple(r.key.account_id, r.account_name), nameof(PNLRowVM.account_name));
symbol_filter = DiscreteValuesFilterVM.CreateTableUpdate<PNLRowData, string, string>(
r => r.symbol,
sym => sym,
out var symbol_filter_items_observer,
out var symbol_filter_items_view);
symbol_filter_items_view.CustomSort = Comparer<string>.Default.to_safe().to_non_generic();
_refresh_filter = new DeferrableActionWrapper(() => _filter_changed?.Invoke(get_filters_states().build_predicate()));
var filter_src = Observable.Create<Func<PNLRowData, bool>>(o => {
o.OnNext(get_filters_states().build_predicate());
@@ -118,11 +108,9 @@ namespace bts.shell.pnl {
).FilterWithRaw(filter_src, PNLRowData.key_fn, null)
.OnDispatcher()
.Subscribe(_update),
_symbols_source(pnl_source).OnDispatcher().Subscribe(symbol_filter_items_observer),
Disposable.Create(() => {
foreach (var f in _all_filters)
f.filter_changed -= _refresh_filter.execute;
_all_filters.OfType<IDisposable>().DisposeAll();
rows.DisposeAll();
_rows_view.Groups.OfTypeSafe<IDisposable>().DisposeAll();
}));
@@ -199,14 +187,14 @@ namespace bts.shell.pnl {
/// security
public IDataGridColumnPersistentFilter<PNLRowData, uint[]> account_filter { get; }
public IDataGridColumnPersistentFilter<PNLRowData, Venue[]> exchange_filter { get; } = DiscreteValuesFilterVM.Common.exchange<PNLRowData>(r => r.security.exchange_or_default(), nameof(PNLRowVM.exchange));
public IDataGridColumnPersistentFilter<PNLRowData, string[]> symbol_filter { get; }
public IDataGridColumnPersistentFilter<PNLRowData, string[]> symbol_filter { get; } = DiscreteValuesFilterVM.Common.symbol<PNLRowData>(r => r.security.symbol_or_default(), nameof(PNLRowVM.symbol));
public IDataGridColumnPersistentFilter<PNLRowData, string> security_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.security?.ToString(), nameof(PNLRowData.security));
public IDataGridColumnPersistentFilter<PNLRowData, string> security_key_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.key.security_id.ToString());
public IDataGridColumnPersistentFilter<PNLRowData, SecurityType[]> security_type_filter { get; } = DiscreteValuesFilterVM.Create<PNLRowData, SecurityType>(
EnumHelper.get_values_except(SecurityType.Option, SecurityType.Synthetic).ToArray(),
r => r.security?.ContractCase ?? SecurityType.None,
nameof(PNLRowData.security));
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.effective_cpp);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.cpp);
/// sod
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_pnl);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.long_pos);
@@ -250,15 +238,6 @@ namespace bts.shell.pnl {
IEnumerable<IDataGridColumnFilter<PNLRowData>> _all_filters =>
_filter_getters.Select(get => get(this));
Func<double, string> _fmt_fill_price {
get => _fmt_fill_price_backing ?? FormattingDelegates.Price.Value;
set => _fmt_fill_price_backing = value;
}
Func<double, string> _fmt_market_price {
get => _fmt_market_price_backing ?? FormattingDelegates.Price.Value;
set => _fmt_market_price_backing = value;
}
public static IComparer<PNLRowVM> get_column_comparer(ColumnKeys column) =>
_get_column_comparer(column).row_comparer;
@@ -381,10 +360,8 @@ namespace bts.shell.pnl {
_triggers = settings.triggers();
_group_triggers = settings.group_triggers();
_header_triggers = settings.header_triggers();
_fmt_fill_price = settings.data_formatting?.fill_price_display?.get_formatting_delegate();
_fmt_market_price = settings.data_formatting?.market_price_display?.get_formatting_delegate();
foreach (var r in rows)
r.on_settings(_default_appearances, _triggers, _fmt_fill_price, _fmt_market_price);
r.on_settings(_default_appearances, _triggers);
total_aggregate.on_settings(settings.header_background_color, _default_header_appearances, _header_triggers);
selected_aggregate.on_settings(settings.header_background_color,_default_header_appearances, _header_triggers);
void map_appearances(ref ImmutableDictionary<ColumnKeys, PNLCellAppearance> from, IEnumerable<GridColumnSettings> col_settings, PNLCellAppearance fallback_appearance) {
@@ -565,16 +542,6 @@ namespace bts.shell.pnl {
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
}
// combines the known system symbols
static IObservable<TableUpdate<string, string>> _symbols_source(IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) =>
pnl_source
// ensure this fires once immediatey, because the known symbols are appended to the first update
// (observers shouldn't have to wait for the pnl source to tick to see them).
.StartWith(TableUpdate<PNLRowData, PNLKey>.mk_empty(PNLRowData.key_fn))
.distinct_on(PNLRowData.key_fn, r => r.symbol, s => s)
.Filter(sym => !string.IsNullOrEmpty(sym))
.AppendTable(mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList());
#region Fills Popup Utils
static void _append_key_filters(fills.FillsFilterStates f, ColumnKeys key) {
switch (key) {
@@ -767,7 +734,7 @@ namespace bts.shell.pnl {
}
}
foreach (var v in filtered.added) {
var r = new PNLRowVM(v.with(calc_type: calculation_type), _default_appearances, _triggers, _fmt_fill_price, _fmt_market_price);
var r = new PNLRowVM(v.with(calc_type: calculation_type), _default_appearances, _triggers);
rows.InsertSorted(r, PNLRowVM.key_comparer);
total_pnl += r.normalized_data;
}
@@ -112,27 +112,27 @@ namespace bts.shell.pnl {
});
// sod
sod_pnl = _cell_map.add_pnl(ColumnKeys.SODPNL, d => d.sod_pnl);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.display_sod_net_pos);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.display_sod_long_pos);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.display_sod_short_pos);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
// day
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.display_day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.display_day_long_pos);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.display_day_short_pos);
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
// raw day
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.display_raw_day_net_pos);
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.display_raw_day_long_pos);
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.display_raw_day_short_pos);
raw_day_net_pos = _cell_map.add_net_pos(ColumnKeys.RawDayNetPos, d => d.raw_day_net_pos);
raw_day_long_pos = _cell_map.add_long_pos(ColumnKeys.RawDayLongPos, d => d.raw_day_long_pos);
raw_day_short_pos = _cell_map.add_short_pos(ColumnKeys.RawDayShortPos, d => d.raw_day_short_pos);
// total
total_pnl = _cell_map.add(ColumnKeys.TotalPNL, d => d.total_pnl, FormattingDelegates.Price<decimal>.Value);
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.display_total_net_pos);
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.display_total_long_pos);
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.display_total_short_pos);
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.total_net_pos);
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.total_long_pos);
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.total_short_pos);
// working
working_net_pos = _cell_map.add(ColumnKeys.WorkingNetPos, d => d.display_working_net_pos);
working_long_pos = _cell_map.add(ColumnKeys.WorkingLongPos, d => d.display_working_long_pos);
working_short_pos = _cell_map.add(ColumnKeys.WorkingShortPos, d => d.display_working_short_pos);
working_net_pos = _cell_map.add(ColumnKeys.WorkingNetPos, d => d.working_net_pos);
working_long_pos = _cell_map.add(ColumnKeys.WorkingLongPos, d => d.working_long_pos);
working_short_pos = _cell_map.add(ColumnKeys.WorkingShortPos, d => d.working_short_pos);
_cell_map.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
@@ -619,7 +619,7 @@
<!--Symbol-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.symbol_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DiscreteValuesCollectionView}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DiscreteValues}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.symbol_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.symbol_filter.open, Source={u:DirectReference _grid}}"
key="Symbol"
@@ -1,5 +1,4 @@
using bts.api;
using bts.api.position;
using bts.shell.fills;
using bts.shell.manual_fills;
using bts.spark;
@@ -39,7 +38,6 @@ namespace bts.shell {
UserSettingsHub settings_hub,
OrdersModel orders_model,
md.MBOService mbo,
IPositionModel position_model,
ErrorReporter error_reporter) {
/// Unlike our <see cref="SparkCommand"/> members, <see cref="IShellWindowFactory"/> members must be instantiated immediately,
/// as they are responsible for launching windows in response to a SparkTag being published to the bus.
@@ -49,7 +47,7 @@ namespace bts.shell {
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
_pnl_conf = new PNLConfig(mbts.config.Configuration.get<bts.config.PNLOptions>());
if (_pnl_conf.show_new_pnl)
_plwf = new PNLWindowFactory(this, bus, settings_hub, orders_model, position_model);
_plwf = new PNLWindowFactory(this, bus, settings_hub, orders_model);
if (_pnl_conf.show_old_pnl)
_old_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
+1 -13
View File
@@ -10,7 +10,6 @@ namespace mbts.spark {
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged, ITemplateSelectorKey {
readonly IEqualityComparer<T> _comparer;
readonly string _template_key;
readonly Func<T, T> _to_display_value, _from_display_value;
AlgoParamPermissions _permissions;
bool _is_enabled, _has_value;
T _committed_value, _value;
@@ -21,15 +20,13 @@ namespace mbts.spark {
public AlgoParamVM(string id, string display_name, T value, string template_key = null)
: this(id, display_name, value, EqualityComparer<T>.Default, template_key) { }
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null, Func<T, T> to_display_value = null, Func<T, T> from_display_value = null) {
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null) {
this.id = id;
this.display_name = display_name;
_value = value;
_committed_value = value;
_comparer = comparer;
_template_key = template_key;
_to_display_value = to_display_value;
_from_display_value = from_display_value;
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -46,10 +43,6 @@ namespace mbts.spark {
_toggle_is_dirty();
}
}
public T display_value {
get => _to_display_value == null ? value : _to_display_value(value);
set => this.value = _from_display_value == null ? value : _from_display_value(value);
}
public AlgoParamPermissions permissions {
get => _permissions;
set {
@@ -61,7 +54,6 @@ namespace mbts.spark {
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
public bool is_dirty { get; private set; }
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
object IAlgoParamVM.display_value { get => display_value; set => display_value = (T)value; }
string ITemplateSelectorKey.key => _template_key;
@@ -72,9 +64,6 @@ namespace mbts.spark {
}
}
public void refresh_display_value() =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(display_value)));
public IObservable<T> subscribe_value() {
return Observable.Create<T>(o => {
o.OnNext(_value);
@@ -124,7 +113,6 @@ namespace mbts.spark {
value_changed(_value, _value = new_value);
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(value)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(display_value)));
}
void _toggle_is_dirty() {
@@ -18,7 +18,6 @@ namespace mbts.spark {
readonly Property<TimeInForce> _current_time_in_force;
readonly Property<CustomOrderDefinition.CustomOneofCase> _current_order_type;
readonly Property<bool> _price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty;
uint _display_quantity_divisor = 1;
public ExistingOrderTicketVM(OrdersModel model, IObservable<(SecurityMD? security_md, OrderRecordImmutable order)> order_source, IObservable<int[]> calculator_buttons) {
uint? key = null;
@@ -41,7 +40,7 @@ namespace mbts.spark {
(val, curr) => !double.IsNaN(val) && val != curr));
_size_dirty = backing(
nameof(size_dirty),
size_calculator.observe_actual_value()
size_calculator.observe_current(true)
.CombineLatest(_current_size.observe_current(true),
(val, curr) => val.HasValue && val.Value != curr));
_account_dirty = backing(
@@ -84,17 +83,14 @@ namespace mbts.spark {
_dispose = Disposable.Create(
size_calculator,
properties_vm,
security_md_source.OnDispatcher().Subscribe(smd => {
bbo.security_md = smd;
display_quantity_divisor = model.security_auxiliary_model.get_display_quantity_divisor(smd.security);
}),
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
bbo,
order_source.OnDispatcher().Subscribe(ort => {
key = ort.order.Key;
if (DoubleHelper.IsNaNOrInfinity(price))
price = ort.order.State.Price;
if (!size_calculator.value.HasValue || !size_calculator.value_setter_called)
size_calculator.bypass_value_setter(ort.order.State.Quantity, true);
size_calculator.bypass_value_setter(ort.order.State.Quantity);
}),
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().OnDispatcher().Subscribe(modify_order));
@@ -108,13 +104,6 @@ namespace mbts.spark {
public OrderPropertiesEditorVM properties_vm { get; }
public double price { get => _price.Value; set => _price.Value = value; }
public CalculatorLogic size_calculator { get; }
public uint display_quantity_divisor {
get => _display_quantity_divisor;
private set {
Set(ref _display_quantity_divisor, value);
size_calculator.display_divisor = value;
}
}
public double current_price => _current_price.Value;
public uint current_size => _current_size.Value;
public AccountTuple current_account => _current_account.Value;
@@ -148,4 +137,4 @@ namespace mbts.spark {
if (_order_type_dirty.Value) yield return "type";
}
}
}
}
+1 -12
View File
@@ -31,7 +31,6 @@ namespace mbts.spark {
class NewOrderTicketVM : DisposableViewModelBase, IRequestViewClose {
readonly Property<SecurityImmutable> _security;
uint _display_quantity_divisor = 1;
public NewOrderTicketVM(OrdersModel model, SecurityMD security_md, OrderDefinitionImmutable def, double price, uint size, IObservable<int[]> calculator_buttons)
: this(model, Observable.Return(security_md), Observable.Return(new InlineOrderTicketProperties(model, def, price, size)), calculator_buttons) { }
@@ -62,10 +61,7 @@ namespace mbts.spark {
set_price_command = new DelegateCommand<double>(set_price);
_dispose = Disposable.Create(
bbo,
security_md_source.OnDispatcher().Subscribe(smd => {
bbo.security_md = smd;
display_quantity_divisor = model.security_auxiliary_model.get_display_quantity_divisor(smd.security);
}),
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
size_calculator = new CalculatorLogic(0, calculator_buttons),
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
);
@@ -87,13 +83,6 @@ namespace mbts.spark {
public SecurityImmutable security => _security.Value;
public double price { get; set; }
public CalculatorLogic size_calculator { get; }
public uint display_quantity_divisor {
get => _display_quantity_divisor;
private set {
Set(ref _display_quantity_divisor, value);
size_calculator.display_divisor = value;
}
}
public OrderPropertiesEditorVM properties_vm { get; }
public BBOVM bbo { get; }
public bool close_on_send { get; set; }
+288 -70
View File
@@ -1,6 +1,5 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.spark;
@@ -27,13 +26,11 @@ namespace mbts.spark.utilities {
public readonly Func<CustomOrderDefinition, CustomOrderDefinition> get_child;
public readonly Action<CustomOrderDefinition, CustomOrderDefinition> assign_child;
public ChildAlgoData(
string name,
Func<IMessage, CustomOrderDefinition> get_child_from_impl,
Action<IMessage, CustomOrderDefinition> assign_child_to_impl,
Func<CustomOrderDefinition, CustomOrderDefinition> get_child,
Action<CustomOrderDefinition, CustomOrderDefinition> assign_child) {
//
public ChildAlgoData(string name, Func<IMessage, CustomOrderDefinition> get_child_from_impl,
Action<IMessage, CustomOrderDefinition> assign_child_to_impl,
Func<CustomOrderDefinition, CustomOrderDefinition> get_child,
Action<CustomOrderDefinition, CustomOrderDefinition> assign_child)
{
this.name = name;
this.get_child = get_child;
this.assign_child = assign_child;
@@ -49,19 +46,13 @@ namespace mbts.spark.utilities {
static readonly OrderMetadata _default_order_metadata = new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.default_order };
readonly AdminModel _admin_model;
readonly DynamicOrderHelper _dynamic_helper;
readonly IAlgoProvider _security_algo_provider;
readonly IReadOnlyDictionary<OneOf<CustomOrderDefinition.CustomOneofCase, string>, string> _algo_abbreviation_index;
readonly IReadOnlyDictionary<string, DynamicOrderDescriptor> _dynamic_descriptors;
IDisposable _dispose;
[ImportingConstructor]
public OrderFactory(OrderService service, AdminModel admin_model, DynamicOrderHelper dynamic_helper, IAlgoProvider security_algo_provider) {
public OrderFactory(AdminModel admin_model, DynamicOrderHelper dynamic_helper) {
_admin_model = admin_model;
_dynamic_helper = dynamic_helper;
_dynamic_descriptors = service.get_registered_dynamic_order_types(new VoidMessage())
.DynamicOrderDesc
.ToDictionary(d => d.DynamicOrderType);
_security_algo_provider = security_algo_provider;
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
// add standard types
algo_name_index.AddRange(
@@ -95,13 +86,66 @@ namespace mbts.spark.utilities {
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types() =>
OrderDataHelper.exchange_order_types;
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security) =>
security.HasValue ? default_order_types(security.Value) : OrderDataHelper.exchange_order_types;
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable security) {
switch (security.ContractCase) {
case Security.ContractOneofCase.Synthetic: return OrderDataHelper.spreader_order_types;
default: return OrderDataHelper.exchange_order_types;
}
}
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child = false) =>
security.HasValue ? default_order_types(security.Value, is_child) : default_order_types();
public IEnumerable<TimeInForce> default_time_in_forces() =>
EnumHelper.get_values_except(TimeInForce.Undefined);
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child = false) =>
default_time_in_forces();
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, CustomOrderDefinition.CustomOneofCase order_type, bool is_child = false) {
var tifs = EnumHelper.get_values_except(TimeInForce.Undefined);
var tifs = default_time_in_forces(security, is_child);
if (order_type != CustomOrderDefinition.CustomOneofCase.Limit) {
tifs = tifs.Except(new[] { TimeInForce.Ioc });
}
return tifs;
}
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child = false) =>
security.HasValue ? default_time_in_forces(security.Value, is_child) : default_time_in_forces();
public LadderSettings default_ladder_settings(SecurityImmutable security) {
var settings = LadderSettings.Default;
if (security.ContractCase == Security.ContractOneofCase.Synthetic)
settings.default_order_type = CustomOrderDefinition.CustomOneofCase.Spreader;
return settings;
}
public Dictionary<AlgoKey, AlgoData> default_algos() =>
default_algos((SecurityImmutable?)null);
public Dictionary<AlgoKey, AlgoData> default_algos(SecurityImmutable? security) =>
default_algos(default_order_types(security));
public Dictionary<AlgoKey, AlgoData> default_algos(AlgoFormats algo_format) =>
default_algos(OrderDataHelper.get_format_algo_types(algo_format));
public Dictionary<AlgoKey, AlgoData> default_algos(IEnumerable<CustomOrderDefinition.CustomOneofCase> types) {
var def_map = new Dictionary<AlgoKey, AlgoData>();
foreach (var ot in types.Except(new[] { CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite })) {
var data = AlgoData.from_custom_def(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(ot));
def_map[data.key] = data;
}
var dynamic_defs = new List<CustomOrderDefinitionImmutable>();
_dynamic_helper.add_dynamic_order_defs(dynamic_defs, custom_order);
foreach (var dd in dynamic_defs) {
var data = AlgoData.from_custom_def(dd);
def_map[data.key] = data;
}
return def_map;
}
#endregion
public OrderDefinitionImmutable order(SecurityImmutable security) =>
@@ -112,7 +156,7 @@ namespace mbts.spark.utilities {
SecurityKey = security.Id,
Side = Side.Buy,
TimeInForce = TimeInForce.Day,
CustomDef = OrderDataHelper.Defaults.CustomOrderDefinition(),
CustomDef = custom_order(),
CtiCode = CTICode.CtiCode1,
CustomerOrFirm = CustomerOrFirm.Firm,
// this will be the tag 50 on CME orders
@@ -129,6 +173,9 @@ namespace mbts.spark.utilities {
return rv;
}
public string abbreviate_algo(CustomOrderDefinitionImmutable defi) =>
abbreviate_algo(_simple_algo_id(defi));
public string abbreviate_algo(OneOf<CustomOrderDefinition.CustomOneofCase, string> id) {
if (id == null)
throw new ArgumentNullException(nameof(id));
@@ -140,6 +187,26 @@ namespace mbts.spark.utilities {
}
// mutable definitions
public CustomOrderDefinition custom_order() => OrderDataHelper.Defaults.CustomOrderDefinition();
public MarketOrderDefinition market_order() => OrderDataHelper.Defaults.MarketOrderDefinition();
public LimitOrderDefinition limit_order() => OrderDataHelper.Defaults.LimitOrderDefinition();
public StopOrderDefinition stop_order() => OrderDataHelper.Defaults.StopOrderDefinition();
public StopLimitOrderDefinition stop_limit_order() => OrderDataHelper.Defaults.StopLimitOrderDefinition();
public GLBXIcebergOrderDefinition glbx_iceberg_order() => OrderDataHelper.Defaults.GLBXIcebergOrderDefinition();
public IcebergOrderDefinition iceberg_order() => OrderDataHelper.Defaults.IcebergOrderDefinition();
public TimeBoxOrderDefinition time_box_order() => OrderDataHelper.Defaults.TimeBoxOrderDefinition();
public TickOrderDefinition tick_order() => OrderDataHelper.Defaults.TickOrderDefinition();
public LeanOrderDefinition lean_order() => OrderDataHelper.Defaults.LeanOrderDefinition();
public JoinOrderDefinition join_order() => OrderDataHelper.Defaults.JoinOrderDefinition();
public TopOrderDefinition top_order() => OrderDataHelper.Defaults.TopOrderDefinition();
public CoverOrderDefinition cover_order() => OrderDataHelper.Defaults.CoverOrderDefinition();
public OCOOrderDefinition oco_order() => OrderDataHelper.Defaults.OCOOrderDefinition();
public PricerOrderDefinition pricer_order() => OrderDataHelper.Defaults.PricerOrderDefinition();
public OffsetOrderDefinition offset_order() => OrderDataHelper.Defaults.OffsetOrderDefinition();
public TriggerOrderDefinition trigger_order() => OrderDataHelper.Defaults.TriggerOrderDefinition();
public DynamicOrderDefinition dynamic_order() => OrderDataHelper.Defaults.DynamicOrderDefinition();
public SpreaderOrderDefinition spreader_order() => OrderDataHelper.Defaults.SpreaderOrderDefinition();
public SplitterOrderDefinition splitter_order() => OrderDataHelper.Defaults.SplitterOrderDefinition();
public SpreaderOrderDefinition spreader_order(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
new SpreaderOrderDefinition {
Security = security.CloneToMutable(),
@@ -147,8 +214,40 @@ namespace mbts.spark.utilities {
};
// immutable definitions
public CustomOrderDefinitionImmutable custom_orderi(CustomOrderDefinition.CustomOneofCase order_type) => new CustomOrderDefinitionImmutable(custom_order(order_type));
public SpreaderOrderDefinitionImmutable spreader_orderi(SecurityImmutable security, ExecutionStrategyImmutable strategy) => new SpreaderOrderDefinitionImmutable(spreader_order(security, strategy));
public CustomOrderDefinition custom_order(CustomOrderDefinition.CustomOneofCase order_type) {
var rv = new CustomOrderDefinition();
switch (order_type) {
case CustomOrderDefinition.CustomOneofCase.Market: rv.Market = market_order(); break;
case CustomOrderDefinition.CustomOneofCase.Limit: rv.Limit = limit_order(); break;
case CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = stop_order(); break;
case CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = stop_limit_order(); break;
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = glbx_iceberg_order(); break;
case CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = iceberg_order(); break;
case CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = time_box_order(); break;
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
case CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = lean_order(); break;
case CustomOrderDefinition.CustomOneofCase.Join: rv.Join = join_order(); break;
case CustomOrderDefinition.CustomOneofCase.Top: rv.Top = top_order(); break;
case CustomOrderDefinition.CustomOneofCase.Cover: rv.Cover = cover_order(); break;
case CustomOrderDefinition.CustomOneofCase.Oco: rv.Oco = oco_order(); break;
case CustomOrderDefinition.CustomOneofCase.Pricer: rv.Pricer = pricer_order(); break;
case CustomOrderDefinition.CustomOneofCase.Offset: rv.Offset = offset_order(); break;
case CustomOrderDefinition.CustomOneofCase.Trigger: rv.Trigger = trigger_order(); break;
case CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = spreader_order(); break;
case CustomOrderDefinition.CustomOneofCase.Splitter: rv.Splitter = splitter_order(); break;
case CustomOrderDefinition.CustomOneofCase.Dynamic: rv.Dynamic = dynamic_order(); break;
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
default:
OrderTypeVMFactory.GenericOrderFactory.setup_generic_custom_order(rv, order_type);
break;
}
return rv;
}
public OrderMetadata get_metadata(IMessage order_type_def) => get_metadata(OrderDataHelper.get_order_type(order_type_def));
public OrderMetadata get_metadata(OrderRecordImmutable order) => get_metadata(order.Def.CustomDef.CustomCase);
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) {
switch (order_type) {
@@ -179,6 +278,14 @@ namespace mbts.spark.utilities {
public void Dispose() =>
Disposable.dispose(ref _dispose);
static OneOf<CustomOrderDefinition.CustomOneofCase, string> _simple_algo_id(CustomOrderDefinitionImmutable defi) {
switch (defi.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Dynamic:
return new OneOf<CustomOrderDefinition.CustomOneofCase, string>(defi.Dynamic.DynamicOrderType);
default: return new OneOf<CustomOrderDefinition.CustomOneofCase, string>(defi.CustomCase);
}
}
}
static class OrderDataHelper {
@@ -194,6 +301,8 @@ namespace mbts.spark.utilities {
}
}
public static OneofDescriptor oneof_descriptor => _oneof_desc;
const double _price_error_margin = 0.0000001;
const decimal _price_error_margin_decimal = 0.0000001m;
@@ -211,10 +320,10 @@ namespace mbts.spark.utilities {
});
public static readonly IComparer<decimal> PriceComparerDecimal = Comparer<decimal>.Create((a, b) => {
decimal diff = a - b;
if (diff <= _price_error_margin_decimal && diff >= -_price_error_margin_decimal)
return 0;
return diff > 0 ? 1 : -1;
decimal diff = a - b;
if (diff <= _price_error_margin_decimal && diff >= -_price_error_margin_decimal)
return 0;
return diff > 0 ? 1 : -1;
});
const string _primary_child_field = "child";
@@ -269,7 +378,7 @@ namespace mbts.spark.utilities {
switch (cd.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: studio = cd.Composite.AlgoTemplateKey; break;
case CustomOrderDefinition.CustomOneofCase.Dynamic: dyn = cd.Dynamic.DynamicOrderType; break;
case CustomOrderDefinition.CustomOneofCase.Spreader: spreader_sec = cd.Spreader.Security?.Id ?? 0; break;
case CustomOrderDefinition.CustomOneofCase.Spreader: spreader_sec = cd.Spreader.Security.Id; break;
}
yield return (cd.CustomCase, dyn, studio, spreader_sec);
}
@@ -357,6 +466,22 @@ namespace mbts.spark.utilities {
child_data.TryGetFirst(out best_match) ? best_match : (ChildAlgoData?)null;
}
public static CustomOrderDefinition create_algo(CustomOrderDefinition.CustomOneofCase algo_type) {
var algo = new CustomOrderDefinition();
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData desc))
desc.type_case_descriptor.Accessor.SetValue(algo, ProtoHelper.mutable_factory(ProtoHelper.get_field_type(desc.type_case_descriptor)));
return algo;
}
public static bool append_algo_node(CustomOrderDefinition target, CustomOrderDefinition.CustomOneofCase algo_type) {
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out var desc)) {
var node = ProtoHelper.mutable_factory(ProtoHelper.get_field_type(desc.type_case_descriptor));
desc.type_case_descriptor.Accessor.SetValue(target, node);
return true;
}
return false;
}
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinition.CustomOneofCase algo_type) =>
_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData d) ? d.child_algo_data : Array.Empty<ChildAlgoData>();
#endregion
@@ -410,60 +535,143 @@ namespace mbts.spark.utilities {
}
}
public static CustomOrderDefinition.CustomOneofCase get_default_algo(Security.ContractOneofCase security_type) {
switch (security_type) {
case Security.ContractOneofCase.Synthetic: return CustomOrderDefinition.CustomOneofCase.Spreader;
default: return CustomOrderDefinition.CustomOneofCase.Limit;
public static AlgoFormats get_algo_format(Security.ContractOneofCase? security_type) =>
security_type.HasValue ? get_algo_format(security_type.Value) : AlgoFormats.Exchange;
public static AlgoFormats get_algo_format(Security.ContractOneofCase security_type) =>
security_type == Security.ContractOneofCase.Synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange;
public static HashSet<CustomOrderDefinition.CustomOneofCase> get_format_algo_types(AlgoFormats f) {
var rv = new HashSet<CustomOrderDefinition.CustomOneofCase>();
if ((f & AlgoFormats.Exchange) == AlgoFormats.Exchange) {
rv.AddRange(exchange_order_types);
if ((f & AlgoFormats.Spreader) == AlgoFormats.Spreader) {
rv.IntersectWith(spreader_order_types);
}
} else if ((f & AlgoFormats.Spreader) == AlgoFormats.Spreader) {
rv.AddRange(spreader_order_types);
}
if ((f & AlgoFormats.Attachable) == AlgoFormats.Attachable)
rv.AddRange(attachable_order_types);
if ((f & AlgoFormats.DefaultPlaceholder) == AlgoFormats.DefaultPlaceholder)
rv.Add(CustomOrderDefinition.CustomOneofCase.None);
return rv;
}
public static CustomOrderDefinition.CustomOneofCase get_default_algo(AlgoFormats f) {
switch (f) {
case AlgoFormats.Exchange: return CustomOrderDefinition.CustomOneofCase.Limit;
case AlgoFormats.Spreader: return CustomOrderDefinition.CustomOneofCase.Spreader;
default: return CustomOrderDefinition.CustomOneofCase.None;
}
}
public static IReadOnlyDictionary<AlgoKey, AlgoData> get_format_algo_data(AlgoFormats f) {
var algo_types = get_format_algo_types(f);
var algo_dict = algo_types.Select(at => AlgoData.from_custom_def(Defaults.CustomOrderDefinitionImmutable(at))).ToDictionaryCrushed(a => a.key);
return algo_dict;
}
public static HashSet<CustomOrderDefinition.CustomOneofCase> get_algo_types_present(CustomOrderDefinitionImmutable def) {
return new HashSet<CustomOrderDefinition.CustomOneofCase>(impl(def.CloneToMutable()));
// hide mutable-def implementation
IEnumerable<CustomOrderDefinition.CustomOneofCase> impl(CustomOrderDefinition mdef) {
yield return mdef.CustomCase;
foreach (var sd in get_child_algo_data(mdef)) {
var child = sd.get_child(mdef);
if (child != null) {
yield return child.CustomCase;
foreach (var t in impl(child))
yield return t;
}
}
}
}
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type, bool fit_all_to_spreader = false) {
var mdef = defi.CloneToMutable();
switch (security_type) {
case Security.ContractOneofCase.Synthetic:
mdef = spreader_impl(mdef);
break;
default:
mdef = outright_impl(mdef);
break;
}
if (mdef == null)
return null;
return new CustomOrderDefinitionImmutable(mdef);
// local methods
CustomOrderDefinition spreader_impl(CustomOrderDefinition def) {
if (def == null) {
def = new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() };
} else {
switch (def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Limit:
case CustomOrderDefinition.CustomOneofCase.None:
def = new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() };
break;
case CustomOrderDefinition.CustomOneofCase.Stop:
case CustomOrderDefinition.CustomOneofCase.StopLimit:
case CustomOrderDefinition.CustomOneofCase.Market:
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg:
def = fit_all_to_spreader ? new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() } : null;
break;
default:
var desc_data = _algo_type_case_descriptor_data[def.CustomCase];
foreach (var cad in desc_data.child_algo_data) {
var child = spreader_impl(cad.get_child(def));
if (child == null)
return null;
var type_def = (IMessage)desc_data.type_case_descriptor.Accessor.GetValue(def);
cad.assign_child_to_impl(type_def, child);
}
break;
}
}
return def;
}
CustomOrderDefinition outright_impl(CustomOrderDefinition def) {
if (def == null) {
def = new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
} else {
switch (def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Spreader:
case CustomOrderDefinition.CustomOneofCase.None:
def = new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
break;
default:
foreach (var cad in get_child_algo_data(def)) {
var child = outright_impl(cad.get_child(def));
if (child == null)
return null;
cad.assign_child(def, child);
}
break;
}
}
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) =>
@@ -527,6 +735,12 @@ namespace mbts.spark.utilities {
public static bool equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) => custom_order_defi_comparer.Equals(a, b);
public static bool structure_equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) {
return impl(a.CloneToMutable(), b.CloneToMutable());
bool impl(CustomOrderDefinition ma, CustomOrderDefinition mb) =>
ma.CustomCase == mb.CustomCase && get_child_algo_data(ma).All(cad => impl(cad.get_child(ma), cad.get_child(mb)));
}
public static bool iceberg_equals(IcebergOrderDefinitionImmutable a, IcebergOrderDefinitionImmutable b) =>
a.ShowQuantity.Equals(b.ShowQuantity) && a.HasChild ? b.HasChild && equals(a.Child, b.Child) : !b.HasChild;
@@ -788,6 +1002,7 @@ namespace mbts.spark.utilities {
}
};
public static OrderDefinitionImmutable OrderDefinitionImmutable { get; } = new OrderDefinitionImmutable(OrderDefinition);
public static OrderRecordImmutable OrderRecordImmutable { get; } = new OrderRecordImmutable(OrderRecord);
public static CustomOrderDefinition CustomOrderDefinition() => new CustomOrderDefinition { Limit = LimitOrderDefinition() };
@@ -840,6 +1055,9 @@ namespace mbts.spark.utilities {
public static TriggerOrderDefinition TriggerOrderDefinition() => new TriggerOrderDefinition { Child = CustomOrderDefinition(), TriggerCondition = new TriggerCondition(), TriggerAction = new TriggerAction(), };
public static DynamicOrderDefinition DynamicOrderDefinition() => new DynamicOrderDefinition();
public static SpreaderOrderDefinition SpreaderOrderDefinition() => new SpreaderOrderDefinition { ExecutionStrategy = new ExecutionStrategy() };
public static SplitterOrderDefinition SplitterOrderDefinition() => new SplitterOrderDefinition();
}
}
}
@@ -85,7 +85,7 @@
usp:OrderModificationControl.IsDirty="{Binding size_dirty, FallbackValue={x:Null}}"
CommitOnEnter="True"
Focusable="False"
Value="{Binding size_calculator.display_value}"
Value="{Binding size_calculator.value}"
/>
</Grid>
<Grid Grid.Column="2">
@@ -114,26 +114,15 @@
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button
Command="{Binding size_calculator.set_actual_value_command}"
CommandParameter="{Binding bbo.bid_size}"
Foreground="{u:BTSColor MarketData.bid}"
>
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
<Binding Path="bbo.bid_size"/>
<Binding Path="display_quantity_divisor"/>
</MultiBinding>
</Button>
Content="{Binding bbo.bid_size}" Command="{Binding size_calculator.set_value_command}"
CommandParameter="{Binding bbo.bid_size}" Foreground="{u:BTSColor MarketData.bid}"
/>
<Button
Grid.Column="2"
Command="{Binding size_calculator.set_actual_value_command}"
Content="{Binding bbo.ask_size}" Command="{Binding size_calculator.set_value_command}"
CommandParameter="{Binding bbo.ask_size}"
Foreground="{u:BTSColor MarketData.ask}"
>
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
<Binding Path="bbo.ask_size"/>
<Binding Path="display_quantity_divisor"/>
</MultiBinding>
</Button>
/>
</Grid>
<Grid Grid.Row="4" Grid.RowSpan="3">
<Grid.RowDefinitions>
@@ -150,18 +139,9 @@
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Margin="2,4,2,0"
Content="{Binding}"
Focusable="False"
Margin="2,4,2,0" Content="{Binding}" Focusable="False" CommandParameter="{Binding}"
Command="{Binding Path=DataContext.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
>
<Button.CommandParameter>
<MultiBinding Converter="{x:Static cv:DynamicDoubleMultiplierConverter.Instance}">
<Binding/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:OrderModificationControl}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
@@ -82,8 +82,13 @@ namespace mbts.spark {
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic(),
IsLiveSorting = true
};
algo_definitions_view.Filter = d =>
!(d.key.mode == AlgoKey.Mode.Persisted && _algo_metadata.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden);
algo_definitions_view.Filter = d => {
if (d.key.mode == AlgoKey.Mode.Persisted && _algo_metadata.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden)
return false;
var sec_fmt = OrderDataHelper.get_algo_format(security.ContractCase);
return (d.selected_format & sec_fmt) == sec_fmt;
};
_algo_definitions_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionVM.selected_format));
_algo_definitions_view.LiveSortingProperties.Add(nameof(AlgoDefinitionVM.group));
_algo_definitions_view.LiveSortingProperties.Add(nameof(AlgoDefinitionVM.name));
algo_definitions_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(AlgoDefinitionVM.group)));
@@ -162,27 +167,33 @@ namespace mbts.spark {
undo_definition_observer,
redo_definition_observer,
revert_definition_observer),
security_source.SelectManyInSeries(model.subscribe_algo_meta_tuples)
.OnDispatcher()
.Subscribe(table => {
using (_selected_algo_definition.begin_update()) {
foreach (var (algo, meta) in table.removed)
_algo_definitions.Remove(algo.key);
foreach (var (algo, meta) in table.changed) {
var vm = _algo_definitions[algo.key];
vm.load(algo);
vm.favorite_index = _get_favorite_algo_index(vm.key);
}
foreach (var (algo, meta) in table.added) {
var vm = AlgoDefinitionVM.create(_model, algo, AlgoEditingPermissions.AllowEditValues);
vm.favorite_index = _get_favorite_algo_index(vm.key);
_algo_definitions.Add(vm);
}
}
security_md_source.Select(smd => model.factory.default_algos(smd.security)).Table(s => s.key).Subscribe(factory_defs => {
using (_selected_algo_definition.begin_update())
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, factory_defs, fd => {
var vm = AlgoDefinitionVM.create(_model, fd, init_format: OrderDataHelper.get_algo_format(security.ContractCase), editing_permissions: AlgoEditingPermissions.AllowEditValues);
vm.selected_format = OrderDataHelper.get_algo_format(security.ContractCase);
return vm;
});
}),
model.algo_model.definitions.Subscribe(_definitions_buffer),
_definitions_buffer.OnDispatcher().Subscribe(u => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, u, def_data => {
var def_vm = AlgoDefinitionVM.create(model, def_data, OrderDataHelper.get_algo_format(security.ContractCase), AlgoEditingPermissions.AllowEditValues);
def_vm.selected_format = OrderDataHelper.get_algo_format(security.ContractCase);
def_vm.favorite_index = _get_favorite_algo_index(def_vm.key);
return def_vm;
});
}),
model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(_on_template_metadata),
ObservableAutoWrapper.connect_item_hooks(algo_definitions, vm =>
Disposable.Create(vm.connect_data(nullable_security_md_source), vm),
Disposable.Create(
vm.connect_data(nullable_security_md_source),
security_source.Subscribe(s => {
var fmt = OrderDataHelper.get_algo_format(s.ContractCase);
foreach (var d in algo_definitions)
d.selected_format = fmt;
}),
vm),
true)) {
() => Disposable.dispose(ref _algo_param_rows_hook)
};
@@ -314,11 +325,13 @@ namespace mbts.spark {
public bool open_algo_popup_on_select { get => _open_algo_popup_on_select; set => Set(ref _open_algo_popup_on_select, value); }
// commands
public ICommand save_definition_cmd { get; }
public ICommand save_template_cmd { get; }
public ICommand revert_definition_cmd { get; }
public ICommand set_default_algo_params_cmd { get; }
public ICommand revert_algo_params_cmd { get; }
public ICommand undo_definition_cmd { get; }
public ICommand redo_definition_cmd { get; }
public ICommand remove_template_cmd { get; }
public ICommand toggle_is_favorite_cmd { get; }
public IObservable<AccountTuple?> observe_account(bool start_immediately = false) =>
@@ -383,4 +396,4 @@ namespace mbts.spark {
algo_definitions_view.Refresh();
}
}
}
}
+12 -25
View File
@@ -1,16 +1,13 @@
<Window
x:Class="mbts.ui.spark.OrderTicket"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<Window x:Class="mbts.ui.spark.OrderTicket"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
Style="{StaticResource BTSWindowTitleTemplate}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -126,12 +123,7 @@
<TextBlock>
<TextBlock.Inlines>
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}" Mode="OneWay" StringFormat="({0})">
<Binding Path="bid_size" Mode="OneWay"/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10" Text="{Binding bid_size, Mode=OneWay, StringFormat='{}({0})'}"/>
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
@@ -148,12 +140,7 @@
</MultiBinding>
</Run.Text>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}" Mode="OneWay" StringFormat="({0})">
<Binding Path="ask_size" Mode="OneWay"/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
</MultiBinding>
</Run>
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10" Text="{Binding ask_size, Mode=OneWay, StringFormat='{}({0})'}"/>
</TextBlock.Inlines>
</TextBlock>
</DataTemplate>
@@ -4,7 +4,9 @@ using mbts.ui;
using System;
using System.Collections.Specialized;
using System.Reactive.Linq;
using System.Threading.Tasks;
using AlgoDef = bts.api.orderman.CustomOrderDefinition;
using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
using CompositeDef = bts.api.orderman.CompositeOrderDefinition;
using CompositeDefi = bts.api.orderman.CompositeOrderDefinitionImmutable;
@@ -19,7 +21,6 @@ namespace mbts.spark {
: base(id, display_name, editing_permissions) { }
public override string abbreviation => _studio_meta?.abbreviation ?? base.abbreviation;
public override AlgoDisplayGroup group => AlgoDisplayGroup.Studio;
public override OrderMetadata meta {
get {
var m = new OrderMetadata { KeyBinding = string.Empty, NoProps = false };
@@ -65,4 +66,4 @@ namespace mbts.spark {
protected override void validate() => base.validate();
}
}
}
}
@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using AlgoDef = bts.api.orderman.CustomOrderDefinition;
using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
@@ -18,6 +19,7 @@ namespace mbts.spark {
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly VersionContainer<AlgoDefi> _definition;
AlgoFormats _format;
AlgoEditingPermissions _editing_permissions;
SecurityMD? _security_md;
OrdersModel _model;
@@ -82,8 +84,10 @@ namespace mbts.spark {
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
var orig_group = group;
_security_md = security_md;
if (security_md.HasValue)
_format = OrderDataHelper.get_algo_format(security_md.Value.security.ContractCase);
var orig_def = definition;
var fmt_type = OrderDataHelper.get_default_algo(security_md.HasValue ? security_md.Value.security.ContractCase : bts.api.Security.ContractOneofCase.None);
var fmt_type = OrderDataHelper.get_default_algo(_format);
switch (fmt_type) {
case AlgoType.None: _clear_impl(); break;
default:
@@ -2,6 +2,7 @@ using bts.api;
using bts.api.orderman;
using bts.utils;
using Google.Protobuf.Reflection;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -14,44 +15,16 @@ namespace mbts.spark {
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
readonly Dictionary<string, DynamicOrderDescriptor> _dyn_types = new Dictionary<string, DynamicOrderDescriptor>(StringComparer.CurrentCultureIgnoreCase);
readonly RegisteredDynamicOrderTypes _dynamic_types;
[ImportingConstructor]
DynamicOrderHelper(OrderService order_service) {
foreach (var dt in order_service.get_registered_dynamic_order_types(new VoidMessage()).DynamicOrderDesc)
_dyn_types[dt.DynamicOrderType] = dt;
}
DynamicOrderHelper(OrderService order_service) =>
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
public IEnumerable<string> dynamic_types => _dyn_types.Keys;
public static string fmt_param(DynamicOrderParam dyn_param) {
if (dyn_param == null)
return null;
switch (dyn_param.ValueCase) {
case DynamicOrderParam.ValueOneofCase.AsArray:
return string.Join(", ", dyn_param.AsArray.Element.Select(fmt_param));
case DynamicOrderParam.ValueOneofCase.AsBool:
return dyn_param.AsBool.ToString();
case DynamicOrderParam.ValueOneofCase.AsDouble:
return dyn_param.AsDouble.ToString();
case DynamicOrderParam.ValueOneofCase.AsEnumerated:
return dyn_param.AsEnumerated.EnumValue;
case DynamicOrderParam.ValueOneofCase.AsInt32:
return dyn_param.AsInt32.ToString();
case DynamicOrderParam.ValueOneofCase.AsQuantity:
return dyn_param.AsQuantity.ToString();
case DynamicOrderParam.ValueOneofCase.AsString:
return dyn_param.AsString;
default:
// implement additional cases as needed
// (as of 4/17/20, this method is only used to format
// dynamic orders' CustomState params in the orderbook)
return $"({dyn_param.ValueCase})";
}
}
public IEnumerable<string> dynamic_types => _dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType);
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs, Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory) {
foreach (var d in _dyn_types.Values) {
foreach (var d in _dynamic_types.DynamicOrderDesc) {
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
dorder.Dynamic.DynamicOrderType = d.DynamicOrderType;
var ddef = dorder.Dynamic;
@@ -61,20 +34,8 @@ namespace mbts.spark {
}
}
public bool try_parse(string str, out string dynamic_type) {
if (_dyn_types.TryGetValue(str, out var desc)) {
dynamic_type = desc.DynamicOrderType;
return true;
}
dynamic_type = null;
return false;
}
internal DynamicOrderDescriptor get_descriptor(string dynamic_type) =>
_dyn_types.TryGetValue(dynamic_type, out var desc) ? desc : new DynamicOrderDescriptor();
internal CustomOrderDefinitionImmutable create_custom_defi(string dynamic_type) =>
new CustomOrderDefinitionImmutable(new CustomOrderDefinition { Dynamic = create_def(dynamic_type) });
_dynamic_types.DynamicOrderDesc.FirstOrDefault(d => d.DynamicOrderType == dynamic_type) ?? new DynamicOrderDescriptor();
internal DynamicOrderDefinition create_def(string dynamic_type) {
var desc = get_descriptor(dynamic_type);
@@ -33,7 +33,7 @@ namespace mbts.spark
{
return;
}
var ev = fd.EnumType.Values.FirstOrDefault(x => x.Name == value)?.Number;
var ev = fd.EnumType.Values.First(x => x.Name == value)?.Number;
if(ev.HasValue)
fd.Accessor.SetValue(message, ev.Value);
}
@@ -28,15 +28,17 @@ namespace mbts.spark
where TMutable : class, IMessage, new()
{
TMutable _message;
readonly protected Func<IMessage, IMessage> _message_override;
protected GenericOrderParamVM(FieldDescriptor descriptor)
protected GenericOrderParamVM(FieldDescriptor descriptor, Func<IMessage, IMessage> message_override = null)
{
_message = new TMutable();
_message_override = message_override;
this.descriptor = descriptor;
}
public FieldDescriptor descriptor { get; set; }
public Google.Protobuf.IMessage message
public virtual Google.Protobuf.IMessage message
{
get => _message;
set
@@ -53,11 +55,12 @@ namespace mbts.spark
where TMutable : class, IMessage, new()
{
readonly Func<TModel, TView> _convert;
readonly Func<TView, TModel> _convert_back;
readonly Func<TView, TModel> _convert_back;
TView _value;
public GenericOrderParamVM(FieldDescriptor field_descriptor, Func<TModel, TView> convert, Func<TView, TModel> convert_back) :
base(field_descriptor)
public GenericOrderParamVM(FieldDescriptor field_descriptor, Func<TModel, TView> convert,
Func<TView, TModel> convert_back, Func<IMessage, IMessage> message_override = null) :
base(field_descriptor, message_override)
{
_convert = convert;
_convert_back = convert_back;
@@ -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));
}
}
@@ -92,8 +96,8 @@ namespace mbts.spark
class GenericOrderEnumeratedVM<TMutable> : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<string>
where TMutable : class, IMessage, new()
{
public GenericOrderEnumeratedVM(FieldDescriptor descriptor)
: base(descriptor)
public GenericOrderEnumeratedVM(FieldDescriptor descriptor, Func<IMessage, IMessage> message_override = null)
: base(descriptor, message_override)
{
elements = new ObservableCollection<string>(_get_elements(descriptor));
}
@@ -102,11 +106,13 @@ namespace mbts.spark
public string selected_element
{
get => GenericOrderHelper.get_enumerated_value(message, descriptor);
get => GenericOrderHelper.get_enumerated_value(_message_override == null ? this.message : _message_override(this.message),
descriptor);
set
{
var orig = selected_element;
GenericOrderHelper.set_enumerated_value(message, descriptor, value);
GenericOrderHelper.set_enumerated_value(_message_override == null ? this.message : _message_override(this.message),
descriptor, value);
if (selected_element != orig)
RaisePropertyChanged();
}
@@ -148,22 +154,23 @@ namespace mbts.spark
class GenericOrderQuantityVM<TMutable> : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<QuantityMeasureImmutable?>
where TMutable : class, IMessage, new()
{
public GenericOrderQuantityVM(FieldDescriptor descriptor)
: base(descriptor) { }
public GenericOrderQuantityVM(FieldDescriptor descriptor, Func<IMessage, IMessage> message_override = null)
: base(descriptor, message_override) { }
QuantityMeasureImmutable? IGenericOrderParamVM<QuantityMeasureImmutable?>.value { get => quantity; set => quantity= value; }
public QuantityMeasureImmutable? quantity
{
get => GenericOrderHelper.get_quantity_value(message, descriptor);
get => GenericOrderHelper.get_quantity_value(_message_override == null ? this.message : _message_override(this.message),
descriptor);
set
{
var orig = quantity;
GenericOrderHelper.set_quantity_value(message, descriptor, value);
GenericOrderHelper.set_quantity_value(_message_override == null ? this.message : _message_override(this.message),
descriptor, value);
if (quantity != orig)
RaisePropertyChanged();
}
}
public IObservable<QuantityMeasureImmutable?> subscribe_value()
{
return Observable.Create<QuantityMeasureImmutable?>(o =>
@@ -174,10 +181,9 @@ namespace mbts.spark
o.OnNext(quantity);
});
}
protected override void on_param_changed()
{
RaisePropertyChanged(nameof(quantity));
}
}
}
}
@@ -2,6 +2,7 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using mbts.collections;
@@ -14,19 +15,21 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
internal class GenericOrderFactory {
static public IAlgoVM create_vm(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions) {
static partial class OrderTypeVMFactory
{
internal class GenericOrderFactory
{
static public IAlgoVM create_vm(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
{
var prop = typeof(CustomOrderDefinition).GetProperty(algo_type.ToString(),
BindingFlags.Instance | BindingFlags.Public);
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
var rt = prop.PropertyType;
var propi = typeof(CustomOrderDefinitionImmutable).GetProperty(algo_type.ToString(),
BindingFlags.Instance | BindingFlags.Public);
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
var rti = propi.PropertyType;
var vmt = typeof(GenericOrderVM<,>);
@@ -35,33 +38,38 @@ namespace mbts.spark {
object vm = Activator.CreateInstance(makevm, key, algo_type, display_name, editing_permissions);
return (IAlgoVM)vm;
}
static public void setup_generic_custom_order(CustomOrderDefinition custom, CustomOrderDefinition.CustomOneofCase type) {
static public void setup_generic_custom_order(CustomOrderDefinition custom, CustomOrderDefinition.CustomOneofCase type)
{
var prop = typeof(CustomOrderDefinition).GetProperty(type.ToString(),
BindingFlags.Instance | BindingFlags.Public);
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
var rt = prop.PropertyType;
var rtt = Activator.CreateInstance(rt);
var ci = rt.GetProperties().Where(x => x.PropertyType == typeof(CustomOrderDefinition));
foreach (var cid in ci)
foreach(var cid in ci)
cid.SetValue(rtt, new CustomOrderDefinition { Limit = new LimitOrderDefinition() });
prop.SetValue(custom, rtt);
}
}
}
class GenericOrderVM<TMutable, TImmutable> : OrderTypeVMBase<TMutable, TImmutable>
where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage
{
readonly ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string> _parameters =
new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
readonly AlgoType _algo_type;
public GenericOrderVM(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
: base(key, display_name, editing_permissions)
{
_algo_type = algo_type;
_parameters = new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
_resources.AddRange(
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
ObservableAutoWrapper.connect_item_hooks(_parameters, p =>
{
p.PropertyChanged += on_param_prop_changed;
return Disposable.Create(() => {
return Disposable.Create(() =>
{
p.PropertyChanged -= on_param_prop_changed;
remove_property(p.descriptor.Name);
p.Dispose();
@@ -72,113 +80,128 @@ namespace mbts.spark {
}
public string algo_type => _algo_type.ToString();
public override string abbreviation =>
public override string abbreviation =>
model == null ? display_name : model.factory.abbreviate_algo(new OneOf<CustomOrderDefinition.CustomOneofCase, string>(_algo_type));
public override AlgoDisplayGroup group => AlgoDisplayGroup.API;
public ObservableCollection<GenericOrderParamVM<TMutable>> parameters => _parameters;
protected override void on_model_internal() =>
protected override void on_model_internal()
{
_sync_generic_params();
}
void _sync_generic_params() {
if (model != null) {
MessageDescriptor desc = new TMutable().Descriptor;
void _sync_generic_params()
{
if (model != null)
{
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);
}
}
static bool _is_value_param_desc(FieldDescriptor desc) =>
(desc.FieldType != FieldType.Message ||
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 ||
_is_quantity_measure_desc(desc));
static bool _is_quantity_measure_desc(FieldDescriptor desc) =>
desc.FieldType == FieldType.Message &&
desc.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName;
protected override IEnumerable<ChildAlgoData> get_selector_data() =>
OrderDataHelper.get_child_algo_data(_algo_type);
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor) {
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
if (descriptor.FieldType == FieldType.Message &&
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName) {
var qm = new GenericOrderQuantityVM<TMutable>(descriptor);
return attached(qm, GenericOrderHelper.get_quantity_value, GenericOrderHelper.set_quantity_value);
} else if (descriptor.FieldType == FieldType.Enum) {
return EnumParamFactory.create(this, descriptor);
} else if (descriptor.FieldType == FieldType.Bool) {
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault());
} else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float) {
return mk<double, double>(descriptor, v => v, v => v, double.NaN);
} else if (descriptor.FieldType == FieldType.Int32) {
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault());
} else if (descriptor.FieldType == FieldType.UInt32) {
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault());
} else if (descriptor.FieldType == FieldType.String) {
return mk<string, string>(descriptor, v => v, v => v);
} 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) {
//
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
return attached(pvm, get_from_param, assign_to_param, default_value);
static bool _is_quantity_measure_desc(FieldDescriptor desc)
{
return desc.FieldType == Google.Protobuf.Reflection.FieldType.Message &&
desc.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName;
}
protected override IEnumerable<ChildAlgoData> get_selector_data()
{
var v = OrderDataHelper.get_child_algo_data(_algo_type);
return v;
}
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor, Func<IMessage, IMessage> message_override = null)
{
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
if (descriptor.FieldType == FieldType.Message &&
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName)
{
var qm = new GenericOrderQuantityVM<TMutable>(descriptor, message_override);
return attached(qm, GenericOrderHelper.get_quantity_value, GenericOrderHelper.set_quantity_value, message_override);
}
else if(descriptor.FieldType == FieldType.Enum)
{
var pvm = new GenericOrderEnumeratedVM<TMutable>(descriptor, message_override);
attached(pvm, GenericOrderHelper.get_enumerated_value, GenericOrderHelper.set_enumerated_value, message_override);
return pvm;
}
else if (descriptor.FieldType == FieldType.Bool)
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, message_override, double.NaN);
else if (descriptor.FieldType == FieldType.Int32)
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(), message_override);
else if (descriptor.FieldType == FieldType.String)
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,
Func<IMessage, IMessage> message_override = null, TView default_value = default)
{
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> {
//
add_property(
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(),
default_value);
return pvm;
}
static class EnumParamFactory {
[ThreadStatic]
static Dictionary<Type, IFactory> _factories_backing;
static Dictionary<Type, IFactory> _factories =>
_factories_backing ?? (_factories_backing = new Dictionary<Type, IFactory>());
public static GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor) {
if (descriptor.FieldType != FieldType.Enum)
throw ExceptionBuilder.Argument.unexpected_case(descriptor.FieldType, $"{nameof(descriptor)}.{nameof(FieldDescriptor.FieldType)}");
if (!_factories.TryGetValue(descriptor.EnumType.ClrType, out var f)) {
// Impl<T> is a nested type, so parent class's generic type args must be included.
_factories[descriptor.EnumType.ClrType] = f = (IFactory)Activator.CreateInstance(typeof(Impl<>)
.MakeGenericType(typeof(TMutable), typeof(TImmutable), descriptor.EnumType.ClrType));
}
return f.create(owner, descriptor);
}
interface IFactory {
GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor);
}
class Impl<T> : IFactory where T : struct {
static readonly Func<T, T?> _convert = v => v;
static readonly Func<T?, T> _convert_back = v => v ?? default;
public GenericOrderParamVM<TMutable> create(GenericOrderVM<TMutable, TImmutable> owner, FieldDescriptor descriptor) =>
owner.mk(descriptor, _convert, _convert_back, default);
}
}
}
}
}
}
}
@@ -70,7 +70,6 @@ namespace mbts.spark {
string id { get; }
string display_name { get; }
object value { get; set; }
object display_value { get; set; }
bool is_enabled { get; set; }
bool is_dirty { get; }
bool has_value { get; }
@@ -82,7 +81,6 @@ namespace mbts.spark {
interface IAlgoParamVM<T> : IAlgoParamVM {
event PropertyChange<T> value_changed;
new T value { get; set; }
new T display_value { get; set; }
}
interface IAlgoSelector : IAlgoParamVM<IAlgoVM>, IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)> {
@@ -122,6 +120,12 @@ namespace mbts.spark {
public static Color safe_get_badge_color(this IAlgoVM vm) =>
vm == null ? default : vm.meta.Color.rgb_color();
public static IDisposable load_algo_data_recursive(this IAlgoSelector selector, IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
selector.load_algo_data(algo_data, default_algo);
return ObservableAutoWrapper.connect_item_hooks(selector.algos, vm =>
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
}
public static IEnumerable<IAlgoParamVM> enumerate_all_params(this IAlgoVM vm) =>
vm == null ? Enumerable.Empty<IAlgoParamVM>() : vm.algo_params.Concat(vm.child_algo_selectors.SelectMany(s => enumerate_all_params(s.selected_algo)));
@@ -1,82 +1,16 @@
using bts.api.orderman;
using mbts.ui;
using System;
using System.Reactive;
using System.Reactive.Linq;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class IcebergOrderVM : OrderTypeVMBase<IcebergOrderDefinition, IcebergOrderDefinitionImmutable> {
readonly Property<double> _show_quantity;
readonly DisplayQuantityMultiplier<double> _display_qty_translator = DisplayQuantityMultiplier.create_double();
new IDisposable _dispose;
public IcebergOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_show_quantity = add_property(
"show_quantity",
IcebergOrderDefinition.ShowQuantityFieldNumber,
double.NaN,
double.NaN,
to_display_value: v => _qty_conversion.to_display(v),
from_display_value: v => _qty_conversion.from_display(v),
refresh_display_value:
Observable.Create<Unit>(o => {
_request_refresh_display += refresh;
return Disposable.Create(() => _request_refresh_display -= refresh);
void refresh() =>
o.OnNext(Unit.Default);
}));
_show_quantity.Changed += on_show_quantity_changed;
_dispose = Disposable.Create(
_display_qty_translator.connect_owner(this, nameof(display_show_quantity)),
_display_qty_translator.subscribe_actual_value().Subscribe(v => _show_quantity.Value = v),
Disposable.Create(() => _show_quantity.Changed -= on_show_quantity_changed));
void on_show_quantity_changed(double oldval, double newval) =>
_display_qty_translator.actual_value = newval;
_show_quantity = add_property(nameof(show_quantity), IcebergOrderDefinition.ShowQuantityFieldNumber, double.NaN, double.NaN);
}
event Action _request_refresh_display;
public double display_show_quantity {
get => _display_qty_translator.display_value;
set => _display_qty_translator.display_value = value;
}
QuantityConversionData _qty_conversion {
get => _display_qty_translator.conversion_data;
set {
if (_display_qty_translator.conversion_data != value) {
_display_qty_translator.conversion_data = value;
_request_refresh_display?.Invoke();
}
}
}
protected override void on_model_internal() =>
_refresh_display();
protected override void on_security_internal() =>
_refresh_display();
void _refresh_display() {
if (model == null) {
_qty_conversion = default;
} else {
var divisor = model.security_auxiliary_model.get_display_quantity_divisor(security);
if (divisor == 1 || divisor == 0) {
_qty_conversion = default;
} else {
_qty_conversion = new QuantityConversionData(divisor, QuantityConversionData.OpType.DivideActual);
}
}
}
protected override void Dispose(bool disposing) {
Disposable.dispose(ref _dispose);
base.Dispose(disposing);
}
public double show_quantity { get => _show_quantity.Value; set => _show_quantity.set(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); }
}
}
}
@@ -18,7 +18,6 @@ using System.Collections.Specialized;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
@@ -62,9 +61,7 @@ namespace mbts.spark {
public static IAlgoVM composite(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new CompositeAlgoVM(key, display_name, editing_permissions);
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
var vm = def_data.key.is_persisted ?
new CompositeAlgoVM(def_data.key, def_data.name, editing_permissions) :
create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
vm.load_from(def_data.value);
return vm;
}
@@ -82,7 +79,7 @@ namespace mbts.spark {
case AlgoType.Join: vm = join(key, name, editing_permissions); break;
case AlgoType.Top: vm = top(key, name, editing_permissions); break;
case AlgoType.Cover: vm = cover(key, name, editing_permissions); break;
case AlgoType.Oco: return oco(key, name, editing_permissions);
case AlgoType.Oco : return oco(key, name, editing_permissions);
case AlgoType.Pricer: return pricer(key, name, editing_permissions);
case AlgoType.Offset: vm = offset(key, name, editing_permissions); break;
case AlgoType.Trigger: vm = trigger(key, name, editing_permissions); break;
@@ -90,10 +87,13 @@ namespace mbts.spark {
case AlgoType.Iceberg: vm = iceberg(key, name, editing_permissions); break;
case AlgoType.Spreader: vm = spreader(key, name, editing_permissions); break;
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
case AlgoType.Splitter: vm = splitter(key, name, editing_permissions); break;
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
case AlgoType.Composite: vm = composite(key, name, editing_permissions); break;
case AlgoType.None: vm = new DefaultAlgoVM(); break;
default: vm = generic(key, name, algo_type, editing_permissions); break;
//default:
// Log.Ui.debug_fatal($"Failed to create {typeof(IAlgoVM)}: unknown algo type '{algo_type}.'");
// return new DefaultAlgoVM();
}
vm.is_expanded = is_expanded && vm.can_expand;
return vm;
@@ -269,7 +269,12 @@ namespace mbts.spark {
void selector_load_impl(AlgoSelectorData sd) {
var inner_def = sd.get_order(value);
if (inner_def.HasValue && inner_def.Value.CustomCase != AlgoType.None) {
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
return;
}
var ival = inner_def.Value;
var vm = sd.selector.algos.GetOrAdd(
AlgoKey.from_custom_def(ival),
@@ -312,12 +317,18 @@ namespace mbts.spark {
}
AlgoKey key;
if (security.HasValue) {
init = OrderDataHelper.fit_to_type(init.Value, security.Value.ContractCase);
if (!init.HasValue)
return;
key = AlgoKey.from_custom_def(init.Value);
child_list.Add(init.Value);
for (int i = 1; i < algos.Count; i++) {
var child = sd.get_from_impl(algos[i]);
if (!child.HasValue || key != AlgoKey.from_custom_def(child.Value))
return;
child = OrderDataHelper.fit_to_type(child.Value, security.Value.ContractCase);
if (!child.HasValue)
return;
child_list.Add(child.Value);
}
} else {
@@ -361,6 +372,7 @@ namespace mbts.spark {
}),
c.assign_child);
}
_populate_selectors();
}
// local methods
IDisposable reset() {
@@ -403,19 +415,9 @@ namespace mbts.spark {
#endregion
#region Auto-Properties (non-child-algo params)
protected Property<T> add_property<T>(
string prop_name,
int target_field_number,
T init_value = default,
T non_uniform_value = default,
bool validate_on_changed = false,
AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite,
Func<T, T> to_display_value = null,
Func<T, T> from_display_value = null,
IObservable<Unit> refresh_display_value = null) {
//
protected Property<T> add_property<T>(string prop_name, int target_field_number, T init_value = default, T non_uniform_value = default, bool validate_on_changed = false, AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite) {
var prop = backing(prop_name, init_value);
_on_property_added(prop_name, target_field_number, prop, prop.set, non_uniform_value, validate_on_changed, permissions, to_display_value, from_display_value, refresh_display_value);
_on_property_added(prop_name, target_field_number, prop, prop.set, non_uniform_value, validate_on_changed, permissions);
return prop;
}
@@ -590,25 +592,17 @@ namespace mbts.spark {
}
}
void _on_property_added<T>(string prop_name, int target_field_number, IProperty<T> prop, Action<T> assign_to_prop, T non_uniform_value, bool validate_on_changed, AlgoParamPermissions permissions, Func<T, T> to_display_value = null, Func<T, T> from_display_value = null, IObservable<Unit> refresh_display_value = null) {
void _on_property_added<T>(string prop_name, int target_field_number, IProperty<T> prop, Action<T> assign_to_prop, T non_uniform_value, bool validate_on_changed, AlgoParamPermissions permissions) {
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
var param = new AlgoParamVM<T>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), prop.Value, EqualityComparer<T>.Default, null, to_display_value, from_display_value) {
permissions = permissions
};
var param = new AlgoParamVM<T>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), prop.Value) { permissions = permissions };
_algo_params.Add(param);
var prop_hook = _link_props(param, prop, assign_to_prop, non_uniform_value, validate_on_changed);
if (refresh_display_value != null) {
prop_hook = Disposable.Create(
refresh_display_value.Subscribe(_ => param.refresh_display_value()),
prop_hook);
}
_auto_props.Add(new AutoProp(prop_name, mval => {
if (mval != null && param.has_value)
field_desc.Accessor.SetValue(mval, prop.Value);
},
mval => assign_to_prop_and_commit(mval == null ? default : ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(mval))),
mvals => assign_to_prop_and_commit(mvals.Select(v => ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(v))).Distinct().SafeSingleOrDefault(non_uniform_value)),
prop_hook));
_link_props(param, prop, assign_to_prop, non_uniform_value, validate_on_changed)));
void assign_to_prop_and_commit(T value) {
assign_to_prop(value);
param.mark_committed();
@@ -820,6 +814,8 @@ namespace mbts.spark {
return Disposable.Create(() => {
if (security != old_sec) {
on_security_internal();
if (_editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditStructure))
_populate_selectors();
_security_changed?.Invoke(security);
}
if (security_md != old_sec_md)
@@ -827,6 +823,21 @@ namespace mbts.spark {
});
}
IDisposable _selector_data_hooks;
void _populate_selectors() {
if (model != null && security.HasValue) {
var algo_data = model.factory.default_algos(security.Value);
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
foreach (var sd in _child_algo_selectors)
sd.load_algo_data_recursive(algo_data, default_algo);
Disposable.dispose(ref _selector_data_hooks);
_selector_data_hooks = ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
});
}
}
class AutoProp : IDisposable {
public readonly string name;
public readonly Action<TMutable> assign, load;
@@ -913,7 +924,6 @@ namespace mbts.spark {
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
IReadOnlyDictionary<AlgoKey, AlgoData> _pending_data;
AlgoKey? _pending_default;
IDisposable _loaded_data_hook;
public AlgoSelector(string id, string display_name) {
this.id = id;
@@ -975,10 +985,8 @@ namespace mbts.spark {
#region IAlgoParamVM
IAlgoVM IAlgoParamVM<IAlgoVM>.value { get => selected_algo; set => selected_algo = value; }
IAlgoVM IAlgoParamVM<IAlgoVM>.display_value { get => selected_algo; set => selected_algo = value; }
event PropertyChange<IAlgoVM> IAlgoParamVM<IAlgoVM>.value_changed { add => selected_algo_changed += value; remove => selected_algo_changed -= value; }
object IAlgoParamVM.value { get => selected_algo; set => selected_algo = (IAlgoVM)value; }
object IAlgoParamVM.display_value { get => selected_algo; set => selected_algo = (IAlgoVM)value; }
bool IAlgoParamVM.has_value => _algo != null;
// changing child algos is not allowed, so an algo selector is never dirty
bool IAlgoParamVM.is_dirty => false;
@@ -1010,11 +1018,8 @@ namespace mbts.spark {
void _sync_formats() {
if (initialized && _pending_data != null) {
Disposable.dispose(ref _loaded_data_hook);
var data = _pending_data;
algos.SyncWithDictionary(data, d => create(d, is_expanded: true));
_loaded_data_hook = ObservableAutoWrapper.connect_item_hooks(algos, vm =>
ObservableAutoWrapper.connect_item_actions(vm.child_algo_selectors, s => s.load_algo_data(data, null)));
var fmt_algos = _pending_data;
algos.SyncWithDictionary(fmt_algos, d => create(d, is_expanded: true));
if (_algo == null || !algos.ContainsKey(_algo.id)) {
if (_pending_default.HasValue && algos.TryGetValue(_pending_default.Value, out IAlgoVM match))
selected_algo = match;
@@ -138,7 +138,7 @@ namespace mbts.spark {
new DialogSkirtItem("Save Strategy", commit);
void _ensure_init() {
if (_vm == null && _model != null && _template.HasValue && _template.Value.HasSecurity && _template.Value.HasExecutionStrategy) {
if (_vm == null && _model != null && _template.HasValue && _template.Value.HasSecurity) {
Log.isnull(_save_strategy_item);
Log.isnull(_vm_strategy_sub);
var tmp = _template.Value;
Loaded 100 of 823 files, more files were not shown because too many files have changed in this diff. Show more