Compare commits

..
469 Commits
Author SHA1 Message Date
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
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
Kevin Darby 198086e639 got roll going 2019-12-18 13:16:49 -05: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
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
451 changed files with 120110 additions and 4886 deletions

No files matched your search

+1 -1
View File
@@ -34,4 +34,4 @@ trace_data/
/doc/user/public/*
.ionide/*
/.ignore
/build
CMakeDeps.cmake
+145 -12
View File
@@ -44,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",
@@ -98,11 +99,52 @@
"externalConsole": true,
"requireExactSource": false
},
{
"name": "sparkcmd Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"historical",
"--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"
],
@@ -111,6 +153,20 @@
"externalConsole": true,
"requireExactSource": 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",
@@ -126,11 +182,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",
@@ -337,20 +400,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": {
@@ -361,11 +428,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",
},
]
}
+11
View File
@@ -48,6 +48,7 @@ else ()
add_subdirectory(doc/user)
add_subdirectory(src/sdk)
add_subdirectory(src/data_files)
add_subdirectory(src/client)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
@@ -59,7 +60,17 @@ else ()
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
@@ -5,7 +5,7 @@ weight = 10
# C#/.NET
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://www.nuget.org/packages/spark_sdk/) to get started.
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://nuget.org) to get started.
```
dotnet new console -lang c#
+1 -1
View File
@@ -73,7 +73,7 @@
<p>
The Spark client SDK allows you to integrate spark with your existing ecosystem. The Spark Client is built entirely on this simple, powerful API.
</p>
<a href="{{ get_url(path="@/client/_index.md") }}" class="button">Get started</a>
<a href="" class="button">Get started</a>
</div>
</div>
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/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)
+51
View File
@@ -23,7 +23,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\sp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_fix", "src\client\spark_fix\spark_fix.csproj", "{D8031C13-7BEA-430A-BB60-40927D15CFC3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_historical", "src\client\spark_historical\spark_historical.csproj", "{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}"
EndProject
Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
@@ -219,6 +226,50 @@ Global
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|Any CPU.ActiveCfg = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.ActiveCfg = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.Build.0 = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x86.ActiveCfg = Debug|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|Any CPU.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x86.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.Build.0 = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.Build.0 = Release|x64
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.Build.0 = Debug|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+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,20 +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>
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
<ItemGroup>
<AvailableItemName Include="Proto2">
<Targets>CompileProto3</Targets>
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto2</Targets>
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto2Linux</Targets>
</AvailableItemName>
<AvailableItemName Include="Proto3">
<Targets>CompileProto3</Targets>
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto3</Targets>
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto3Linux</Targets>
</AvailableItemName>
<AvailableItemName Include="Proto3Excel">
<Targets>CompileProto3Excel</Targets>
<Targets Condition=" '$(OS)' == 'Windows_NT' ">CompileProto3Excel</Targets>
</AvailableItemName>
</ItemGroup>
@@ -43,6 +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
+4 -23
View File
@@ -3,13 +3,11 @@ using bts.api.core;
using bts.api.orderman;
using bts.spark;
using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows;
@@ -21,26 +19,20 @@ namespace bts.shell {
readonly IBus _bus;
readonly OrdersModel _model;
readonly ErrorReporter _error_reporter;
readonly bool _close_on_send;
IReadOnlyDictionary<uint, SecurityMD> _md_map;
TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> _orders;
IDisposable _dispose;
[ImportingConstructor]
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter)
: this(bus, model, error_reporter, Configuration.get<bts.config.OrderTicketOptions>().close_on_send) { }
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter, bool close_on_send) {
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter) {
_bus = bus;
_model = model;
_error_reporter = error_reporter;
_close_on_send = close_on_send;
_dispose = Disposable.Create(
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketAutoLookupTag).Subscribe(_show_order_tickets),
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
);
}
@@ -62,28 +54,17 @@ namespace bts.shell {
void _show_order_ticket(SecurityMD smd) =>
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd,
Observable.Return(LadderSettings.Default)) {
close_on_send = _close_on_send
}
Observable.Return(LadderSettings.Default))
});
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
Observable.Return(LadderSettings.Default.calculator_buttons)) {
close_on_send = _close_on_send
}
Observable.Return(LadderSettings.Default.calculator_buttons))
});
}
void _show_order_tickets(IImmutableSet<uint> securities) {
foreach (var id in securities.empty_if_null()) {
if (_md_map.TryGetValue(id, out var smd))
_show_order_ticket(smd);
}
}
void _show_modify_order(uint order_key) {
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
@@ -97,4 +78,4 @@ namespace bts.shell {
}
}
}
}
}
-6
View File
@@ -26,7 +26,6 @@ namespace mbts.ui {
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
@@ -46,7 +45,6 @@ namespace mbts.ui {
KeyGeometryKey = nameof(KeyGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
MenuOrientationGeometryKey = nameof(MenuOrientationGeometryKey),
OrdersGeometryKey = nameof(OrdersGeometryKey),
PauseGeometryKey = nameof(PauseGeometryKey),
PaymentGeometryKey = nameof(PaymentGeometryKey),
@@ -64,8 +62,6 @@ namespace mbts.ui {
public static Geometry PlayGeometry => Geometry.Parse(get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
static readonly Pen _copy_boxes_pen = new Pen(Brushes.Black, HighDpiExtensions.RoundLayoutValue(5)).frozen();
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
@@ -160,7 +156,6 @@ namespace mbts.ui {
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
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 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);
@@ -184,7 +179,6 @@ namespace mbts.ui {
public static ImageSource Import => load_resource_and_freeze("Import");
public static ImageSource Key => create_icon(KeyGeometryKey);
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
public static ImageSource MenuOrientation => create_icon(MenuOrientationGeometryKey);
public static ImageSource Pause => create_icon(PauseGeometryKey);
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
-16
View File
@@ -46,18 +46,6 @@
</sys:String>
</x:Array>
<x:Array x:Key="{x:Static mbts:Icons.CopyBoxesGeometryKey}" Type="{x:Type sys:String}">
<sys:String>
M-55,5 L-55,55 L-5,55
</sys:String>
<sys:String>
M-40,-10 L-40,40 L10,40 L10,-10 L-40,-10
</sys:String>
<sys:String>
M-10,10 L40,10 L40,-40 L-10,-40 L-10,10
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
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>
@@ -292,10 +280,6 @@
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.MenuOrientationGeometryKey}">
M14.997001,19.999038L15.000001,23.499012C15.000001,24.327005,14.327008,25,13.500016,25L9.9970522,25 9.9970522,23.000015 13.000022,23.000015 12.997022,20.001038z M9.000001,19.000006L9.000001,30.000006 30.000001,30.000006 30.000001,19.000006z M2,2L2,23 7.000001,23 7.000001,18.500006C7.000001,17.673006,7.6730013,17.000006,8.500001,17.000006L13.000001,17.000006 13.000001,2z M1.5,0L13.500001,0C14.327001,0,15.000001,0.67300034,15.000001,1.5L15.000001,17.000006 26.956518,17.000006 23.162404,13.207182C22.772376,12.816143 22.772376,12.184205 23.162404,11.793225 23.357907,11.597736 23.613928,11.499992 23.869827,11.499992 24.125725,11.499992 24.381502,11.597736 24.576517,11.793225L25.889371,13.105454 25.866285,12.914201C25.554945,10.547287 24.699391,8.7684691 23.308905,7.6089654 21.974919,6.4954615 20.356436,6.1229606 19.066825,6.0297103L19.059648,6.0292783 20.545799,7.5160289C20.936786,7.9070184 20.936786,8.5390015 20.545799,8.929991 20.350807,9.1249857 20.094815,9.2229831 19.838825,9.2229831 19.582834,9.2229831 19.326843,9.1249857 19.131849,8.929991L15.185988,4.9830971 18.981854,1.1871986C19.371841,0.79620934 20.004819,0.79620934 20.395805,1.1871986 20.785791,1.5771885 20.785791,2.2101717 20.395805,2.601161L18.976535,4.020443 18.999199,4.0214248C20.641604,4.1119318 22.785724,4.567862 24.590892,6.0739603 26.493747,7.660778 27.606404,10.054969 27.91277,13.209869L27.913735,13.221022 29.341699,11.793225C29.536714,11.597736 29.792459,11.499992 30.048328,11.499992 30.304196,11.499992 30.560187,11.597736 30.75569,11.793225 31.146694,12.184205 31.146694,12.816143 30.75569,13.207182L26.962552,17.000006 30.500001,17.000006C31.327001,17.000006,32.000001,17.673006,32.000001,18.500006L32.000001,30.500006C32.000001,31.327005,31.327001,32.000006,30.500001,32.000006L8.500001,32.000006C7.6730013,32.000006,7.000001,31.327005,7.000001,30.500006L7.000001,25 1.5,25C0.67300034,25,0,24.327001,0,23.5L0,1.5C0,0.67300034,0.67300034,0,1.5,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
</sys:String>
@@ -48,5 +48,5 @@ namespace mbts.ui.spark {
}
}
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
}
@@ -4,10 +4,10 @@ using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -32,7 +32,6 @@ namespace mbts.spark {
SecurityCriteria
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
SparkMenuDisplayVM _menu_display_vm;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
@@ -49,9 +48,7 @@ namespace mbts.spark {
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override MarketViewWindowFactory.StartupData SaveState =>
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns)) {
menu_display = _menu_display_vm.safe_create()
};
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -104,8 +101,6 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
@@ -165,8 +160,7 @@ namespace mbts.spark {
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
})),
_menu_display_vm.spark_cmd
}))
};
//
_vm.selection_changed += on_selection_changed;
@@ -196,8 +190,5 @@ namespace mbts.spark {
_vm.Dispose();
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
}
}
@@ -1,7 +1,6 @@
using bts.api;
using bts.proto;
using bts.settings;
using bts.view;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -54,8 +53,6 @@ namespace bts.shell {
public readonly FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
public StartupData() { }
@@ -1,36 +0,0 @@
using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
namespace bts.shell.time_and_sales {
[ProtoContract]
class FilterStates {
[ProtoMember(1)]
public TimeFilterTuple time;
[ProtoMember(2)]
public string security;
[ProtoMember(3)]
public DoubleMinMaxState price;
[ProtoMember(4)]
public UIntMinMaxState size;
// [ProtoMember(5)]
// ProtoMember 5 cannot be used further
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public co2.AggressorSide[] aggressor_sides;
}
static class TimeAndSalesFilterStatesExtensions {
public static Func<TimeAndSalesRowVM, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<TimeAndSalesRowVM>();
pb.add_regex(fs.security, r => r.security.ToString());
pb.add_time_filter(fs.time, r => r.time);
pb.add_price(fs.price, r => r.price, r => r.security);
pb.add_numeric(fs.size, r => r.size);
pb.add_discrete_values(fs.aggressor_sides, r => r.aggressor_side);
return pb.to_predicate();
}
}
}
@@ -1,74 +0,0 @@
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System.Collections.Generic;
using System.Linq;
using static mbts.ui.controls.DIYGrid;
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
namespace bts.shell.time_and_sales {
[ProtoContract]
class TimeAndSalesSettings {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criterion;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
[ProtoMember(6)]
public bool use_defaults;
public TimeAndSalesSettings() { }
public TimeAndSalesSettings(SecurityCriteria security_criteria) :
this(security_criteria, new FilterStates(), null, null) { }
public TimeAndSalesSettings(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
this.security_criteria = security_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criterion = sorting_criterion;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
public static TimeAndSalesSettings Default(SecurityCriteria security_criteria) =>
new TimeAndSalesSettings(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
public TimeAndSalesSettings with(SecurityCriteria security_criteria) =>
new TimeAndSalesSettings(security_criteria, filters, grid);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.col_id(), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings time => new GridColumnSettings(Columns.Time.col_id(), DefaultColumnHeaders.Time, width: DefaultColumnWidths.TimeOfDay);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.col_id(), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings size => new GridColumnSettings(Columns.Size.col_id(), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings aggressor => new GridColumnSettings(Columns.Aggressor.col_id(), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.col_id(), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return time;
yield return price;
yield return size;
yield return aggressor;
yield return security_key;
}
}
}
}
}
@@ -1,26 +1,27 @@
using bts.api;
using bts.co2;
using mbts;
using mbts.co2;
using mbts.spark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace bts.shell.time_and_sales {
namespace mbts.spark {
class TimeAndSalesModel : IDisposable {
internal readonly SecurityCriteria security_criteria;
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
IDisposable _dispose;
public TimeAndSalesModel(IBus bus, MDConsumer consumer, IObservable<SecurityCriteria> security_criteria) {
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
this.security_criteria = security_criteria;
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
var copy = curr_channels;
return copy.Values.SelectMany(c => c.read_trades()).ToList();
}));
_dispose = security_criteria.SelectManyInSeries(sc =>sc.subscribe_md_map(bus)).Subscribe(
_dispose = security_criteria.subscribe_md_map(bus).Subscribe(
md_map => {
var copy = new Dictionary<ChannelKey, ChannelConsumer>(curr_channels);
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
@@ -94,4 +95,4 @@ namespace bts.shell.time_and_sales {
}
}
}
}
}
@@ -1,70 +0,0 @@
using bts.api;
using bts.co2;
using bts.utils;
using bts.utils.csv;
using mbts;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
namespace bts.shell.time_and_sales {
class TimeAndSalesRowVM : ViewModelBase, DIYGrid.IRowVM, ISecurityRow, IEquatable<TimeAndSalesRowVM> {
// key comparer guarentees deterministic sorting
// if the rows in the collection have unique row_keys.
public static readonly IComparer<TimeAndSalesRowVM> key_comparer =
new ComparerBuilder<TimeAndSalesRowVM>(new[] {
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
})
.to_comparer()
.to_safe();
readonly TradeMessageImmutable _trade;
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade) {
this.row_key = row_key;
this.security = security;
_trade = trade;
trade_key = trade.Key;
}
public event Action invalidated;
public int row_key { get; }
public uint trade_key { get; }
public uint security_key => security.Id;
public SecurityImmutable security { get; }
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
public double price => _trade.price();
public uint size => _trade.Size;
public AggressorSide aggressor_side => _trade.AggressorSide;
public void raise_invalidated() =>
invalidated?.Invoke();
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesRowVM),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public bool Equals(TimeAndSalesRowVM other) =>
other != null &&
row_key == other.row_key;
public override bool Equals(object obj) =>
Equals(obj as TimeAndSalesRowVM);
public override int GetHashCode() =>
row_key;
}
}
@@ -1,363 +1,87 @@
using bts.api;
using bts.co2;
using bts.collections;
using bts.utils;
using mbts;
using bts.utils.csv;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Windows.Controls;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace bts.shell.time_and_sales {
class TimeAndSalesVM : DisposableViewModelBase, IHandleGridSorting, IHandleRowSelection {
const int _max_row_count = 1000;
static int _id_counter;
static IReadOnlyList<Func<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>> _filter_getters =
ObjectSnapshot.property_delegates_of_type<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>();
namespace mbts.spark {
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
readonly IBus _bus;
readonly TimeAndSalesModel _model;
IWindowSettingsManager<GridSettings> _settings_manager;
readonly ColumnSortManager _sort_manager;
readonly RowFactory _row_factory = new RowFactory();
readonly LoggerContext _log;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly DeferrableActionWrapper
_refresh_filter,
_refresh_sort,
_refresh_selected_rows;
IComparer<TimeAndSalesRowVM> _comparer_backing;
Func<TimeAndSalesRowVM, bool> _filter_backing;
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
ImmutableList<TimeAndSalesRowVM> _all_rows = ImmutableList<TimeAndSalesRowVM>.Empty;
ImmutableList<TradeInfo> _buffered_trades;
/// Although the sorting criteria can be obtained from the <see cref="_sort_manager"/> (<see cref="ColumnSortManager.SortingCriteria"/>),
/// a local copy is kept because the settings collector may call <see cref="create"/> from a thread
/// other than the dispatcher thread associated with this instance as a viewmodel,
/// and the <see cref="ColumnSortManager.SortingCriteria"/> property is not thread-safe.
/// Therefore, <see cref="create"/> returns this local copy, and thus avoids accessing the non-thread-safe property.
ImmutableList<SortingCriteria> _sorting_criteria = ImmutableList<SortingCriteria>.Empty;
TimeAndSalesRowVM _current_row;
bool _is_paused;
int
_load_settings_count,
_filter_change_count,
_sort_change_count;
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager) {
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
_bus = bus;
_model = model;
_settings_manager = settings_manager;
// deferrable filter/sort refresh
_refresh_filter = new DeferrableActionWrapper(_refresh_filter_impl);
_refresh_sort = new DeferrableActionWrapper(_refresh_sort_impl);
_refresh_selected_rows = new DeferrableActionWrapper(() => selected_rows_changed?.Invoke(_selected_rows));
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<TimeAndSalesRowVM>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
grid_settings_manager.load_from(settings_manager.current_settings());
// Columns
add_column(
TimeAndSalesView.Columns.Security,
r => r.security.ToString(),
DefaultColumnHeaders.Security,
DefaultColumnWidths.Security,
security_filter,
DataGridFilterKnownTemplates.Regex);
add_column(
TimeAndSalesView.Columns.Time,
r => r.time.ToString("h:mm:ss tt"),
DefaultColumnHeaders.Time,
DefaultColumnWidths.Time,
time_filter,
DataGridFilterKnownTemplates.TimeFilter);
add_column(
TimeAndSalesView.Columns.Price,
r => r.security.spec_or_default().format(r.price),
DefaultColumnHeaders.Price,
DefaultColumnWidths.Price,
price_filter,
DataGridFilterKnownTemplates.DoubleMinMax,
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Size,
r => r.size.ToString(),
DefaultColumnHeaders.Size,
DefaultColumnWidths.Size,
size_filter,
DataGridFilterKnownTemplates.UIntMinMax,
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Aggressor,
r => r.aggressor_side.ToString(),
DefaultColumnHeaders.Aggressor,
DefaultColumnWidths.Aggressor,
aggressor_filter,
DataGridFilterKnownTemplates.EnumValues);
add_column(
TimeAndSalesView.Columns.SecurityKey,
r => r.security_key.ToString(),
DefaultColumnHeaders.SecurityKey,
DefaultColumnWidths.SecurityKey,
security_key_filter,
DataGridFilterKnownTemplates.Regex);
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager);
// commands
show_ladder_cmd = new DelegateCommand(() => {
if (current_row != null)
request_show_ladder?.Invoke(current_row);
});
// init IDisposable resources
var resources = new List<IDisposable>();
resources.AddRange(_filters.Select(f => {
f.filter_changed += _refresh_filter.execute;
return Disposable.Create(() => f.filter_changed -= _refresh_filter.execute);
}));
_sort_manager.active_sorting_changed += _refresh_sort.execute;
resources.Add(Disposable.Create(() => _sort_manager.active_sorting_changed -= _refresh_sort.execute));
_dispose = Disposable.Create(resources.ToArray());
// local methods
void add_column(
TimeAndSalesView.Columns col_key,
Func<TimeAndSalesRowVM, string> get_cell_text,
string header,
double width,
IDataGridColumnFilter filter,
string filter_template,
System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<TimeAndSalesRowVM, TimeAndSalesView.Columns>(
col_key, get_cell_text, filter, filter_template, _get_aggressor_brush) {
id = col_key.col_id(), Header = header, Width = width, horizontal_alignment = h_alignment
}
);
}
public event Action<ImmutableHashSet<TimeAndSalesRowVM>> selected_rows_changed;
public event Action<TimeAndSalesRowVM> current_row_changed, request_show_ladder;
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public TimeAndSalesRowVM current_row {
get => _current_row;
set {
if (Set(ref _current_row, value))
current_row_changed?.Invoke(value);
}
}
public ObservableCollection<TimeAndSalesRowVM> rows { get; } = new ObservableCollection<TimeAndSalesRowVM>();
public ImmutableHashSet<TimeAndSalesRowVM> selected_rows => _selected_rows;
public int total_row_count => _all_rows.Count;
public SimpleGridSettingsManager<TimeAndSalesRowVM> grid_settings_manager { get; }
public bool is_paused {
get => _is_paused;
set {
if (_is_paused != value) {
_is_paused = value;
if (value) {
_buffered_trades = ImmutableList<TradeInfo>.Empty;
} else {
var resume = _buffered_trades;
_buffered_trades = null;
on_new_trades(resume);
}
}
}
}
// filters
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<TimeAndSalesRowVM>(r => r.time);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security.ToString(), nameof(TimeAndSalesRowVM.security));
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<TimeAndSalesRowVM>(r => r.price);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, UIntMinMaxState> size_filter { get; } = new UIntMinMax<TimeAndSalesRowVM>(r => r.size, nameof(TimeAndSalesRowVM.size));
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<TimeAndSalesRowVM, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side);
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_key_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security_key.ToString());
// commands
public ICommand show_ladder_cmd { get; }
IComparer<TimeAndSalesRowVM> _comparer {
get => _comparer_backing;
set {
if (value == null) {
_log.warn("Comparer should not be set to null, will use the default comparer instead (based on trade time, then row key).");
_comparer_backing = new ComparerBuilder<TimeAndSalesRowVM>(new[] {
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
})
.to_comparer()
.to_safe();
} else {
_comparer_backing = value;
}
var sorted_rows = rows.ToArray();
var snapshot = _selected_rows;
Array.Sort(sorted_rows, _comparer_backing);
using (_refresh_selected_rows.defer_execution()) {
for (int i = 0; i < sorted_rows.Length; i++) {
if (rows[i].row_key != sorted_rows[i].row_key)
rows[i] = sorted_rows[i];
}
}
}
}
Func<TimeAndSalesRowVM, bool> _filter {
get => _filter_backing;
set {
var old_filter = _filter_backing;
var new_filter = value;
_filter_backing = new_filter;
if (new_filter != null) {
for (int i = 0; i < rows.Count; i++) {
if (!new_filter(rows[i]))
rows.RemoveAt(i);
}
rows.RemoveWhere(r => !new_filter(r));
if (old_filter != null) {
foreach (var r in _all_rows) {
if (!old_filter(r) && new_filter(r))
rows.InsertSorted(r, _comparer);
}
}
} else if (old_filter != null) {
foreach (var r in _all_rows) {
if (!old_filter(r))
rows.InsertSorted(r, _comparer);
}
}
}
}
IEnumerable<IDataGridColumnFilter<TimeAndSalesRowVM>> _filters =>
_filter_getters.Select(fn => fn(this));
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting_criteria, GridSettings grid_settings) create() =>
(_get_filter_states(), _sorting_criteria, grid_settings_manager.create());
public void load(FilterStates filters, IEnumerable<SortingCriteria> sorting_criteria, GridSettings grid_settings) {
using (_refresh_filter.defer_execution())
using (_refresh_sort.defer_execution()) {
_log.info($"begin load settings ({++_load_settings_count})...");
var timer = new Stopwatch();
timer.Start();
grid_settings_manager.load_from(grid_settings);
_sort_manager.SortingCriteria = sorting_criteria;
// load filters
show_ladder = DelegateCommandParamObserver.Create(current_row_observable, r => _show_ladder_async(r).ignore(), r => r != null);
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
if (startup_data.filters is FilterStates filters) {
time_filter.load_state(filters.time);
security_filter.load_state(filters.security);
price_filter.load_state(filters.price);
size_filter.load_state(filters.size);
aggressor_filter.load_state(filters.aggressor_sides);
security_key_filter.load_state(filters.security_key);
//
timer.Stop();
_log.info($"load settings ({_load_settings_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
}
// Columns
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, nameof(Row.security), security_filter, DataGridFilterKnownTemplates.Regex);
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, nameof(Row.time), time_filter, DataGridFilterKnownTemplates.TimeFilter);
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
show_ladder,
DataGridFilterHelper.hook_filters(
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
}
public void on_new_trades(IReadOnlyList<TradeInfo> trades) {
/// The total row count is limited by <see cref="_max_row_count"/>;
/// trades are kept in a circular buffer (FIFO).
/// If paused, just need to add trades to <see cref="_buffered_trades"/>.
if (is_paused) {
if (_buffered_trades == null)
_buffered_trades = ImmutableList<TradeInfo>.Empty;
if (trades.Count >= _max_row_count) {
_buffered_trades = _buffered_trades
.Clear()
.AddRange(ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count));
} else {
int overflow_count = _buffered_trades.Count + trades.Count - _max_row_count;
if (overflow_count > 0) {
var rows_to_remove = _buffered_trades.WrapSubRange(0, overflow_count);
_buffered_trades = _buffered_trades.RemoveRange(0, overflow_count);
}
_buffered_trades = _buffered_trades.AddRange(trades);
}
return;
}
/// If not paused, update in the normal fasion:
/// add to <see cref="_all_rows"/> and <see cref="rows"/>
if (trades.Count >= _max_row_count) {
trades = ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count);
_all_rows = _all_rows.Clear();
rows.Clear();
} else {
int overflow_count = _all_rows.Count + trades.Count - _max_row_count;
if (overflow_count > 0) {
var rows_to_remove = _all_rows.WrapSubRange(0, overflow_count);
_all_rows = _all_rows.RemoveRange(0, overflow_count);
rows.RemoveSorted(rows_to_remove, _comparer);
}
}
var all_rows_bd = _all_rows.ToBuilder();
foreach (var t in trades) {
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);
}
_all_rows = all_rows_bd.ToImmutable();
RaisePropertyChanged(nameof(total_row_count));
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public DelegateCommandParamObserver<Row> show_ladder { get; }
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
try {
if (e.Column is ISortableDIYGridColumn sc) {
_log.info($"handling sort event for column {sc.id}.");
_sort_manager.OnColumnClick(sc, KeyboardExtensions.IsShiftDown());
var sorted_rows = rows.ToArray();
var snapshot = _selected_rows;
_refresh_sort_impl();
} else {
_log.warn($"ignoring sort event because column '{e.Column?.id}' does not implement {nameof(ISortableDIYGridColumn)}.");
}
} catch (Exception ex) {
_log.error($"error handling sort event: {ex}.");
}
}
public StartupData create() =>
new StartupData(_model.security_criteria, _get_filter_states(), get_grid_save_state(), rows_view.get_sorting_criteria(columns));
public void row_selection_changed(SelectionChangedEventArgs e) {
var bd = _selected_rows.ToBuilder();
if (e.AddedItems != null)
bd.AddRange(e.AddedItems.OfType<TimeAndSalesRowVM>());
if (e.RemovedItems != null)
bd.removeRange(e.RemovedItems.OfType<TimeAndSalesRowVM>());
_selected_rows = bd.ToImmutable();
_refresh_selected_rows.execute();
}
static IComparer<TimeAndSalesRowVM> _get_column_comparer(string column_name) =>
_get_column_comparer(EnumHelper.parse<TimeAndSalesView.Columns>(column_name));
static IComparer<TimeAndSalesRowVM> _get_column_comparer(TimeAndSalesView.Columns column) {
switch (column) {
case TimeAndSalesView.Columns.Aggressor: return wrap(r => r.aggressor_side);
case TimeAndSalesView.Columns.Time: return wrap(r => r.time);
case TimeAndSalesView.Columns.Security: return wrap(r => r.security);
case TimeAndSalesView.Columns.Price: return wrap(r => r.price);
case TimeAndSalesView.Columns.Size: return wrap(r => r.size);
case TimeAndSalesView.Columns.SecurityKey: return wrap(r => r.security_key);
case TimeAndSalesView.Columns.RowKey: return wrap(r => r.row_key);
default: throw ExceptionBuilder.Argument.unexpected_case(column, nameof(column));
}
IComparer<TimeAndSalesRowVM> wrap<T>(Func<TimeAndSalesRowVM, T> selector) =>
Comparer<T>.Default.wrap(selector);
}
Brush _get_aggressor_brush(TimeAndSalesRowVM r) {
private Brush _get_aggressor_brush(Row r) {
BTSColor c;
Brush b;
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
@@ -367,6 +91,14 @@ namespace bts.shell.time_and_sales {
return b;
}
#region Filters
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<Row>(r => r.time, nameof(Row.time));
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> size_filter { get; } = new UIntMinMax<Row>(r => r.size, nameof(Row.size));
public IDataGridColumnPersistentFilter<Row, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<Row, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side, nameof(Row.aggressor_side));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
FilterStates _get_filter_states() =>
new FilterStates {
time = time_filter.get_state(),
@@ -376,35 +108,86 @@ namespace bts.shell.time_and_sales {
security_key = security_key_filter.get_state(),
aggressor_sides = aggressor_filter.get_state()
};
#endregion
void _refresh_sort_impl() {
_log.info($"begin change sort ({++_sort_change_count})...");
var timer = new Stopwatch();
timer.Start();
_comparer = _sort_manager.BuildComparer(_get_column_comparer, TimeAndSalesRowVM.key_comparer);
_sorting_criteria = ImmutableList.CreateRange(_sort_manager.SortingCriteria);
timer.Stop();
_log.info($"sort change ({_sort_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
protected override IEnumerable<SortingCriteria> get_default_sorting() {
yield return new SortingCriteria(nameof(Row.time), false);
}
void _refresh_filter_impl() {
_log.info($"begin change filter ({++_filter_change_count})...");
var timer = new Stopwatch();
timer.Start();
_filter = _get_filter_states().build_predicate();
timer.Stop();
_log.info($"filter change ({_filter_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
protected override Row row_factory(TradeInfo row_model) =>
new Row(this, row_model.security, row_model.trade);
async Task _show_ladder_async(Row row) {
var smd_lookup = await SparkUtils.get_security_md_async(_bus, row.security.Id);
if (smd_lookup.success) _bus.post(SparkTags.ShowLadderTag, smd_lookup.result);
}
class RowFactory {
// the row key must be unique to each row in the collection
int _row_id_counter;
public class Row : SecurityRow, DIYGrid.IRowVM {
readonly uint _trade_key;
public TimeAndSalesRowVM create(TradeInfo trade_info) =>
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade);
readonly string
_security_text,
_time_text,
_price_text,
_size_text,
_aggressor_text,
_trade_key_text,
_security_key_text;
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade) =>
new TimeAndSalesRowVM(_row_id_counter++, security, trade);
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
_trade_key = trade.Key;
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
price = trade.price();
size = trade.Size;
aggressor_side = trade.AggressorSide;
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
_security_text = security.ToString();
_time_text = time.ToString("M/d h:mm:ss tt");
_price_text = security.ContractSpec.format(price);
_size_text = size.ToString();
_aggressor_text = aggressor_side.ToString();
_trade_key_text = _trade_key.ToString();
_security_key_text = security_key.ToString();
}
public event Action invalidated;
public uint trade_key => _trade_key;
public DateTime time { get; }
public double price { get; }
public uint size { get; }
public AggressorSide aggressor_side { get; }
public ICommand show_ladder { get; }
public string this[TimeAndSalesView.Columns col] {
get {
switch (col) {
case TimeAndSalesView.Columns.Security: return _security_text;
case TimeAndSalesView.Columns.Time: return _time_text;
case TimeAndSalesView.Columns.Price: return _price_text;
case TimeAndSalesView.Columns.Size: return _size_text;
case TimeAndSalesView.Columns.Aggressor: return _aggressor_text;
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
default: return null;
}
}
}
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
CsvHelper.create_ordering_maintainer(
typeof(TimeAndSalesVM.Row),
new string[] {
nameof(security),
nameof(time),
nameof(price),
nameof(size),
nameof(aggressor_side),
nameof(security_key),
});
public void raise_invalidated() => invalidated?.Invoke();
}
}
}
}
@@ -1,10 +1,10 @@
<UserControl
x:Class="bts.shell.time_and_sales.TimeAndSalesView"
x:Class="mbts.ui.spark.TimeAndSalesView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:tas="clr-namespace:bts.shell.time_and_sales"
xmlns:usp="clr-namespace:mbts.ui.spark"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
@@ -26,14 +26,13 @@
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
Rows="{Binding rows}"
Rows="{Binding rows_view}"
CurrentRow="{Binding current_row}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding show_ladder_cmd}"/>
<KeyBinding Key="Return" Command="{Binding show_ladder_cmd}"/>
<KeyBinding Key="A" Modifiers="Ctrl" Command="{Binding select_all_cmd}"/>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
</c:DIYGrid.InputBindings>
<c:DIYGrid.CommandBindings>
<CommandBinding
@@ -44,18 +43,13 @@
</c:DIYGrid.CommandBindings>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<tas:TimeAndSalesRowView/>
<usp:TimeAndSalesRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Trades:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding total_row_count, Mode=OneWay}" />
</TextBlock>
<TextBlock Text=" "/>
</StatusBarItem>
</StatusBar>
</Grid>
@@ -1,21 +1,17 @@
using mbts.ui.controls;
using mbts.spark;
using mbts.ui.controls;
using System.Windows.Controls;
namespace bts.shell.time_and_sales {
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for TimeAndSalesView.xaml
/// </summary>
partial class TimeAndSalesView : UserControl {
public enum Columns { Time, Security, Price, Size, SecurityKey, Aggressor, RowKey }
public enum Columns { Time, Security, Price, Size, OrderType, SecurityKey, Aggressor }
public TimeAndSalesView() =>
InitializeComponent();
}
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesRowVM> { }
static class TimeAndSalesViewColumnsExtensions {
public static string col_id(this TimeAndSalesView.Columns column) =>
column.ToString();
}
}
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
}
@@ -1,218 +1,127 @@
using bts.api;
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts;
using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
namespace bts.shell.time_and_sales {
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly bool _can_pause_updates;
TimeAndSalesVM _vm;
SingleValuePump<TimeAndSalesVM> _vm;
TimeAndSalesModel _model;
SecurityCriteria _security_criteria_backing;
TimeAndSalesSettings _state;
StartupData _state;
TimeAndSalesView _view;
SparkCommand[] _commands;
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)
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager)
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
Log.assert(state != null);
_consumer = consumer;
_factory = factory;
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
if (_state.security_criteria == null)
_state = _state.with(new SecurityCriteria());
_security_criteria_backing = _state.security_criteria;
_state = state;
_settings_manager = settings_manager;
_can_pause_updates = can_pause_updates;
thread_id = mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().time_and_sales ? _window_type : null;
Icon.Freeze();
}
event Action<SecurityCriteria> _security_criteria_changed;
public override string window_type => _window_type;
public override IEnumerable<SparkCommand> Commands => _commands;
public override UIElement Content => _view;
public override double MinWidth => TimeAndSalesSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
public override double MinWidth => StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public override TimeAndSalesSettings SaveState {
get {
_ensure_state(true);
return _state;
}
}
SecurityCriteria _security_criteria {
get => _security_criteria_backing;
set {
_security_criteria_backing = value;
_state = null;
_security_criteria_changed?.Invoke(value);
}
}
public override StartupData SaveState => _state = _vm.value.create();
public override void Dispose() =>
_dispose_resources();
public void thread_init() {
_dispose_resources();
_ensure_state();
if (!_state.use_defaults)
_settings_manager.instance_settings = _state.grid;
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_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);
});
var current_row_src = Observable.Create<TimeAndSalesRowVM>(o => {
if (_vm.current_row != null)
o.OnNext(_vm.current_row);
_vm.current_row_changed += o.OnNext;
return Disposable.Create(() => _vm.current_row_changed -= o.OnNext);
});
var current_sec_key_src = current_row_src.Select(r => {
var result = ImmutableHashSet<uint>.Empty;
if (r != null)
result = result.Add(r.security_key);
return result;
});
_vm = new SingleValuePump<TimeAndSalesVM>();
var connectable_builder = new ConnectableSeriesBuilder<TimeAndSalesVM>(_vm.observable);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
SparkCommand toggle_is_paused_cmd = null;
toggle_is_paused_cmd = new SparkCommandCommand(get_is_paused_cmd_title(), Icons.Pause, new DelegateCommand(() => {
_vm.is_paused = !_vm.is_paused;
toggle_is_paused_cmd.title = get_is_paused_cmd_title();
})) {
show_in_toolbar = _can_pause_updates,
show_in_context_menu = _can_pause_updates
};
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
_commands = new SparkCommand[] {
// csv export
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
// ladder
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
// ticket
SparkCommands.ShowOrderTicketAutoLookup(SparkCommands.KeyGestures.ShowOrderTicket)
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
// fills
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills)
.complete(selected_rows_src, always_true, get_fills_startup_data),
// mview
SparkCommands.ShowMarketView
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria),
// pause
toggle_is_paused_cmd,
// change securities
SparkCommands.change_securities_command(() => (update_securities_cmd, _security_criteria)),
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.value.row_vms.ToCsv(TimeAndSalesVM.Row.csv_ordered_properties), "TaS")),
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
s.with(_vm.value.columns.Select(c => c.create()))
)
);
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
})),
_menu_display_vm.spark_cmd
}))
};
_vm.load(_state.filters, _state.sorting_criterion, _state.grid);
_vm.request_show_ladder += show_ladder;
var security_criteria_src = _bus.subscribe(update_securities_cmd.tag);
var subs = new List<IDisposable> {
security_criteria_src.Subscribe(sc => _security_criteria = sc),
Disposable.Create(() => _vm.request_show_ladder -= show_ladder)
};
_model = new TimeAndSalesModel(_bus, _consumer, Observable.Create<SecurityCriteria>(o => {
o.OnNext(_security_criteria);
return security_criteria_src.Subscribe(o);
}));
subs.Add(_model.trades.OnDispatcher().Subscribe(_vm.on_new_trades));
_subscriptions = Disposable.Create(subs.ToArray());
if (_view == null)
_view = new TimeAndSalesView();
_view.DataContext = _vm;
// local methods
_subscriptions = Disposable.Create(
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
connectable_builder.connect_all());
_initialize();
bool always_true<T>(T _) => true;
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsSettings(new FillsFilterStates {
security = rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
});
string get_is_paused_cmd_title() =>
$"{(_vm.is_paused ? "Resume" : "Pause")} Updates";
void show_ladder(TimeAndSalesRowVM row) {
if (row != null)
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet.Create(new[] { row.security_key }));
void update_criteria(SecurityCriteria c) {
_state = _vm.value.create().with(c);
_initialize();
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
void _dispose_resources() {
Disposable.dispose(ref _subscriptions);
Disposable.dispose(ref _vm);
Disposable.dispose(ref _model);
if (_commands != null) {
_commands.OfType<IDisposable>().DisposeAll();
_commands = null;
}
Disposable.dispose(ref _model);
Disposable.dispose(ref _vm);
}
void _ensure_state(bool force_recreate = false) {
if (_state == null || force_recreate) {
if (_vm == null) {
_state = TimeAndSalesSettings.Default(_security_criteria);
} else {
var (filters, sorting, grid) = _vm.create();
_state = new TimeAndSalesSettings(_security_criteria, filters, grid, sorting.ToList()) {
use_defaults = _settings_manager.instance_settings == null
};
}
// should be safe to assume _menu_display_vm and _vm will either both be null, or both be non null,
// but redundant null checks don't hurt, and just may prevent a crash.
if (_menu_display_vm != null)
_state.menu_display = _menu_display_vm.safe_create();
}
void _initialize() {
Disposable.dispose(ref _model);
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
_vm.value = new TimeAndSalesVM(_bus, _settings_manager, _model, _state);
if (_view == null)
_view = new TimeAndSalesView();
_view.DataContext = _vm.value;
}
}
}
}
@@ -1,32 +1,33 @@
using bts.api;
using bts.spark;
using bts.shell.time_and_sales;
using mbts;
using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
using mbts.config;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.TimeAndSalesView;
namespace bts.shell {
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesSettings> {
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesWindowFactory.StartupData> {
readonly IBus _bus;
readonly MDConsumer _consumer;
readonly UserSettingsHub _settings_hub;
readonly TimeAndSalesConfig _time_and_sales_conf;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
_bus = bus;
_consumer = consumer;
_settings_hub = settings_hub;
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
@@ -35,21 +36,91 @@ namespace bts.shell {
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
public override Task<IShellWindow> load(StartupData 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));
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
public async void show(SecurityCriteria criteria) {
var w = await load(new TimeAndSalesSettings(criteria));
var w = await load(new StartupData(criteria));
_bus.post(SparkTags.ShowWindow, w);
}
readonly struct TimeAndSalesConfig {
public readonly bool can_pause_updates;
[ProtoContract]
public class StartupData {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criterion;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
public TimeAndSalesConfig(bts.config.TimeAndSalesOptions options) =>
can_pause_updates = options.can_pause_updates;
public StartupData() { }
public StartupData(SecurityCriteria security_criteria) :
this(security_criteria, new FilterStates(), null, null) { }
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
this.security_criteria = security_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criterion = sorting_criterion;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
public StartupData with(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, filters, grid);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings time => new GridColumnSettings(col_id(Columns.Time), DefaultColumnHeaders.Time, width: DefaultColumnWidths.Time);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings size => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings aggressor => new GridColumnSettings(col_id(Columns.Aggressor), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible:false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return time;
yield return price;
yield return size;
yield return aggressor;
yield return security_key;
}
}
}
}
[ProtoContract]
public class FilterStates {
[ProtoMember(1)]
public TimeFilterTuple time;
[ProtoMember(2)]
public string security;
[ProtoMember(3)]
public DoubleMinMaxState price;
[ProtoMember(4)]
public UIntMinMaxState size;
// [ProtoMember(5)]
// ProtoMember 5 cannot be used further
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public co2.AggressorSide[] aggressor_sides;
}
}
}
}
@@ -20,5 +20,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
}
@@ -3,7 +3,6 @@ using bts.api;
using bts.shell;
using bts.shell.spreader;
using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
@@ -28,7 +27,6 @@ namespace mbts.spark {
StartupData _state;
OrdersModel _orders_model;
SyntheticManagementView _view;
SparkMenuDisplayVM _menu_display_vm;
IDisposable _subscriptions;
IReadOnlyList<SparkCommand> _commands;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -45,7 +43,7 @@ namespace mbts.spark {
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create().with(menu_display: _menu_display_vm.safe_create());
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -55,8 +53,6 @@ namespace mbts.spark {
public void thread_init() {
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
@@ -101,8 +97,7 @@ namespace mbts.spark {
new SparkCommandCommand(
"Import Synthetics...", Icons.Import, FileDialogCommand.open_file(import, extensions: SyntheticSecurityParser.file_extension)) {
show_in_toolbar = false
},
_menu_display_vm.spark_cmd
}
};
_subscriptions = Disposable.Create(
@@ -134,9 +129,6 @@ namespace mbts.spark {
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
struct RowModificationParams {
public readonly SecurityImmutable security;
public readonly bool has_working_orders;
@@ -48,28 +48,14 @@ namespace bts.shell {
public readonly FilterStates filters;
[ProtoMember(3)]
public readonly GridSettings grid;
[ProtoMember(4)]
public readonly SparkMenuDisplaySettings menu_display;
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default)
: this(sorting_criteria, filters, grid, SparkMenuDisplaySettings.Default) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid, SparkMenuDisplaySettings menu_display) {
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
this.sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
this.menu_display = menu_display;
}
public StartupData with(List<SortingCriteria> sorting_criteria = null, FilterStates filters = null, GridSettings grid = null, SparkMenuDisplaySettings? menu_display = null) =>
new StartupData(
sorting_criteria ?? this.sorting_criteria,
filters ?? this.filters,
grid ?? this.grid,
menu_display ?? this.menu_display);
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
static class DefaultColumns {
@@ -25,7 +25,6 @@
<sys:Double x:Key="VerticalPush2">8</sys:Double>
<sys:Double x:Key="VerticalPush3">12</sys:Double>
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP1_RP1" Left="{StaticResource HorizontalPush1}" Right="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
@@ -131,6 +130,22 @@
</ResourceDictionary>
</UserControl.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.EditPane}}">
<VisualState Name="{x:Static u:VisualStates.Active}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static u:VisualStates.Inactive}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<AdornerDecorator>
<Grid>
@@ -138,19 +153,13 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox
x:Name="_tree_view"
Grid.RowSpan="2"
ItemsSource="{Binding definitions_view}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinWidth="180"
Background="Transparent"
BorderThickness="0,0,1,0"
SelectedItem="{Binding selected_definition}"
>
<ListBox.ItemTemplate>
@@ -164,176 +173,275 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid x:Name="edit_pane" Grid.Column="1">
<u:StateManager.VisualState>
<Binding Path="selected_definition">
<Binding.Converter>
<cv:ValueMap Default="{x:Static u:VisualStates.Active}">
<cv:KeyValue Key="{x:Null}" Value="{x:Static u:VisualStates.Inactive}"/>
</cv:ValueMap>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState Name="{x:Static u:VisualStates.Active}"/>
<VisualState Name="{x:Static u:VisualStates.Inactive}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="algo_editor_scroll_viewer" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(FrameworkElement.ToolTip).(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ToolTip>
<ToolTip Visibility="Collapsed">
<StackPanel Orientation="Vertical">
<TextBlock FontStyle="Italic">Select an existing algo to edit from the list to the left.</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock FontStyle="Italic">Or click the</TextBlock>
<Image Source="{x:Static u:Icons.AddBoxes}" Height="20" Margin="2,0"/>
<TextBlock FontStyle="Italic"> button to create a new one.</TextBlock>
</StackPanel>
</StackPanel>
</ToolTip>
</Grid.ToolTip>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Fill="{x:Static u:BTSColors.dialog_skirt_brush}" UseLayoutRounding="True"/>
<Button
x:Name="custom_badge_color_picker_button"
Background="{Binding selected_definition.badge_fill}"
Margin="{StaticResource Margin.LP1}"
ToolTip="Change the icon color used for this algo."
IsEnabled="True"
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
/>
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox
x:Name="default_badge_color_checkbox"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="6,4,0,4"
IsChecked="{Binding selected_definition.custom_badge_color.enabled, Converter={StaticResource NotConverter}}"
>
<CheckBox.ToolTip>
<TextBlock>
<Run>Use the default icon color for this algo.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: the default color is that of the algo-root.</Run>
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
<Grid x:Name="edit_pane">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid u:StateManager.VisualStateCollection="{Binding content_visual_states}" Margin="{StaticResource Margin.TP1}" >
<VisualStateManager.VisualStateGroups>
<!--Name-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameDefinition, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definitions."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameDefinitionWhileTemplatesPresent, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definition while templates present."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static CannotRenameTemplate, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename templates."/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!--Abbreviation-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<VisualState Name="{x:Static Populated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Unpopulated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static NotSupportedForTemplates, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(ToolTipService.ShowOnDisabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
</ColorAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_not_supported_for_templates_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!--Badge Selector-->
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="default_badge" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="0.5"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="default_badge_radio_button" Storyboard.TargetProperty="(ToggleButton.IsChecked)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Name:</TextBlock>
<TextBox
x:Name="name_text_box"
Grid.Column="1"
ToolTipService.ShowOnDisabled="True"
Margin="{StaticResource Margin.LP1}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
/>
<Grid x:Name="abbreviation_root" Grid.Column="2">
<Grid.ToolTip>
<Grid>
<TextBlock x:Name="abbreviation_active_tooltip" Visibility="Collapsed">
<TextBlock.Inlines>
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
</TextBlock.Inlines>
</TextBlock>
</CheckBox.ToolTip>
<Grid x:Name="default_badge_content_root">
<TextBlock x:Name="abbreviation_not_supported_for_templates_tooltip" Visibility="Collapsed">
Not supported for templates.
</TextBlock>
</Grid>
</Grid.ToolTip>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
</Grid>
</Grid>
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Icon:</TextBlock>
<RadioButton
x:Name="default_badge_radio_button"
Grid.Column="1"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="{StaticResource Margin.LP1}"
GroupName="Badge"
>
<RadioButton.ToolTip>
<ToolTip DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Vertical">
<TextBlock>Use the default icon color for this algo.</TextBlock>
<StackPanel TextElement.FontStyle="Italic" Orientation="Horizontal">
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
<usp:OrderTypeItem
Margin="2,0"
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
DisplayName="{Binding selected_definition.algo_root.display_name}"
/>
<TextBlock>).</TextBlock>
</StackPanel>
</StackPanel>
</ToolTip>
</RadioButton.ToolTip>
<Grid x:Name="default_badge_content_root">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Default</TextBlock>
<Rectangle
x:Name="default_badge"
Grid.Column="1"
Margin="{StaticResource Margin.LP1}"
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
</Grid>
</RadioButton>
<RadioButton
Grid.Column="2"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="{StaticResource Margin.LP2}"
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
GroupName="Badge"
>
<RadioButton.ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Use Default Icon</TextBlock>
<TextBlock Grid.ColumnSpan="3">Specify a custom icon color for this algo.</TextBlock>
<TextBlock Grid.Row="1" FontStyle="Italic">Hint: Click</TextBlock>
<Rectangle
x:Name="default_badge"
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource Margin.LP1}"
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Margin="3,0"
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
</Grid>
</CheckBox>
<cp:ColorPickerControl
Grid.Row="1"
ToolTipService.ShowOnDisabled="True"
Margin="{StaticResource Margin.TP1}"
Background="Transparent"
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
PreviewMouseDown="_on_badge_color_picker_activated"
PreviewGotKeyboardFocus="_on_badge_color_picker_activated"
/>
</Grid>
</Border>
</Popup>
<TextBox
x:Name="name_text_box"
Grid.Column="1"
u:TextBoxBehavior.TextPrompt="Name"
Margin="{StaticResource Margin.LP1}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
MinWidth="50"
IsEnabled="True"
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
ToolTip="This algo's full name."
/>
<TextBox
x:Name="abbreviation_text_box"
Grid.Column="2"
u:TextBoxBehavior.TextPrompt="Abbrev."
VerticalAlignment="Center"
Margin="{StaticResource Margin.LP1_RP1}"
MinWidth="50"
IsEnabled="True"
Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"
>
<TextBox.ToolTip>
<TextBlock>
<TextBlock.Inlines>
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
<LineBreak/>
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
</TextBlock.Inlines>
</TextBlock>
</TextBox.ToolTip>
</TextBox>
<ScrollViewer x:Name="algo_editor_scroll_viewer" Grid.Row="1" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto">
</RadioButton.ToolTip>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Custom</TextBlock>
<Button
x:Name="custom_badge_color_picker_button"
Grid.Column="1"
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Margin="{StaticResource Margin.LP1}"
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
/>
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
<cp:ColorPickerControl
Background="Transparent"
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
/>
</Border>
</Popup>
</Grid>
</RadioButton>
</Grid>
<usp:AlgoEditorView
Grid.Row="2"
DataContext="{Binding selected_definition}"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
/>
</ScrollViewer>
</Grid>
<c:DialogSkirt
Grid.Row="2"
Grid.Column="1"
BorderThickness="0"
>
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
Save Algo
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<StackPanel.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
</StackPanel.Margin>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding revert_cmd_tooltip}">
Revert Algo
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -19,17 +19,6 @@ namespace mbts.ui.spark {
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
}
void _on_badge_color_picker_activated(object sender, MouseButtonEventArgs e) =>
_on_badge_color_picker_activated();
void _on_badge_color_picker_activated(object sender, KeyboardFocusChangedEventArgs e) =>
_on_badge_color_picker_activated();
void _on_badge_color_picker_activated() {
if (DataContext is AlgoStudioVM vm && vm.selected_definition != null)
vm.selected_definition.custom_badge_color.enabled = true;
}
public static class VisualStateNames {
public static class EditPane {
public const string GroupName = nameof(EditPane);
@@ -55,7 +44,8 @@ namespace mbts.ui.spark {
public static readonly string
Populated = $"{GroupName}{nameof(Populated)}",
Unpopulated = $"{GroupName}{nameof(Unpopulated)}";
Unpopulated = $"{GroupName}{nameof(Unpopulated)}",
NotSupportedForTemplates = $"{GroupName}{nameof(NotSupportedForTemplates)}";
}
public static class BadgeSelector {
@@ -9,7 +9,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -69,12 +68,23 @@ namespace mbts.spark {
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
// add definition
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.CopyBoxes, _vm.clone_selected_definition_cmd));
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
show_in_toolbar = _ui_configuration.can_save_specific
});
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
// clone definition
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
_commands.Add(clone_selected_definition_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
// save
if (_ui_configuration.can_save_specific) {
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_definition is AlgoDefinitionVM d)
_vm.save_definition(d).ignore();
}, content_is_dirty_src));
}
// save all
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
// undo/redo
if (_ui_configuration.can_undo_redo) {
// undo
@@ -107,11 +117,30 @@ namespace mbts.spark {
_resources.Add(can_redo_source.Subscribe(redo_cmd));
}
// remove
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
vm => vm != null,
out var remove_cmd_observer)));
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
if (value is AlgoDefinitionVM d) {
string prompt; MessageBoxImage icon;
if (d.key.mode == AlgoKey.Mode.Persisted) {
prompt = $"Are you sure you want to delete '{d.name}?'";
icon = MessageBoxImage.Question;
} else {
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
prompt = null;
icon = default;
}
if (prompt != null) {
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
switch (resp) {
case BTSMessageBoxButtons.Yes: break;
case BTSMessageBoxButtons.No:
default: return;
}
}
_vm.remove_definition(d).ignore();
}
});
_commands.Add(remove_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
}
void _dispose_core() {
@@ -120,4 +149,4 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
}
}
}
}
@@ -6,6 +6,7 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
@@ -15,6 +16,7 @@ namespace mbts.spark {
readonly AdminModel _admin_model;
readonly ErrorReporter _error_reporter;
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
IDisposable _dispose;
[ImportingConstructor]
@@ -32,13 +34,17 @@ namespace mbts.spark {
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
public IAlgoDefinitionMetadataService metadata_service { get; }
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
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 = _admin_model.user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving definition");
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
return null;
@@ -47,22 +53,23 @@ namespace mbts.spark {
public async Task<bool> remove_definition(AlgoKey definition_key) {
var req = new AlgoDefinitionRemoveRequest();
_set_key(ref req, definition_key);
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(
() => _service.remove_algo_definition_async(req),
r => r.Success,
r => r.ErrorMessage,
"remove definition");
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(() => _service.remove_algo_definition_async(req), r => r.Success, r => r.ErrorMessage, "remove template");
return success;
}
public Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) => Task.FromResult((AlgoTemplateData?)null);
public Task<bool> remove_template(AlgoKey key) =>
Task.FromResult(false);
public void Dispose() =>
Disposable.dispose(ref _dispose);
static AlgoDefinition _api_definition(AlgoData definition_data) =>
static AlgoDefinition _template(AlgoData definition_data) =>
new AlgoDefinition { Name = definition_data.name, Definition = definition_data.value.CloneToMutable() };
static AlgoDefinitionImmutable _api_definitioni(AlgoData definition_data) =>
new AlgoDefinitionImmutable(_api_definition(definition_data));
static AlgoDefinitionImmutable _templatei(AlgoData definition_data) =>
new AlgoDefinitionImmutable(_template(definition_data));
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
AlgoData.from_record(recordi);
@@ -92,5 +99,11 @@ namespace mbts.spark {
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
}
}
void _report_error(string present_progressive_desc, string msg) =>
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
}
}
@@ -7,9 +7,13 @@ using System.Threading.Tasks;
namespace mbts.spark {
interface IAlgoStudioModel {
IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key);
IAlgoDefinitionMetadataService metadata_service { get; }
Task<AlgoData?> save_definition(AlgoData definition_data);
Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data);
Task<bool> remove_definition(AlgoKey definition_key);
Task<bool> remove_template(AlgoKey template_key);
bts.config.AlgoStudioOptions ui_configuration { get; }
}
@@ -68,13 +68,7 @@ namespace mbts.spark {
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
key = def_data.key;
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
// badge color picker palettes
custom_badge_color_palettes.AddRange(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
var root_algo_color_item = new NamedColorVM(new NamedColor { name = "(Root Algo Type)" }, false);
var algo_type_color_palette = new ColorPaletteVM(ColorPalette.factory_order_colors_palette(model.factory));
algo_type_color_palette.named_colors.Insert(0, root_algo_color_item);
custom_badge_color_palettes.Insert(0, algo_type_color_palette);
//
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
_version = new VersionContainer<Snapshot>(_snapshot);
var conf = _model.algo_model.ui_configuration;
// init backing props
@@ -97,7 +91,6 @@ namespace mbts.spark {
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
_can_redo.Changed += on_can_redo_changed;
_can_undo.Changed += on_can_undo_changed;
custom_badge_color.enabled_changed += on_use_custom_badge_color_changed;
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
// load initial state
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
@@ -110,13 +103,16 @@ namespace mbts.spark {
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
_algo_root.observe_current(true).SubscribeEach(vm => {
if (vm != null) {
root_algo_color_item.assign_color(vm.meta.Color.argb_color());
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
}
return null;
}),
_algo_root.observe_current(true).SubscribeEach(vm =>
vm == null ? null :
Disposable.Create(
vm.subscribe_is_dirty().Subscribe(v => {
if (!v) {
_version.increment_version();
_on_committed_changed();
}
}),
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
Disposable.Create(() => {
_can_redo.Changed -= on_can_redo_changed;
@@ -157,10 +153,6 @@ namespace mbts.spark {
// local methods
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
void on_use_custom_badge_color_changed() {
if (!custom_badge_color.enabled && algo_root != null)
custom_badge_color.value = algo_root.meta.Color.argb_color();
}
void on_local_changed() {
_version.increment_version();
if (algo_root != null)
@@ -198,7 +190,6 @@ namespace mbts.spark {
public AlgoDisplayGroup group => key.display_group();
public bool can_redo => _can_redo.Value;
public bool can_undo => _can_undo.Value;
//public bool use_custom_badge_color
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;
@@ -13,6 +13,7 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -81,19 +82,18 @@ namespace mbts.spark {
_persisted_meta = meta;
definitions_view.Refresh();
}),
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) {
val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
}
}),
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
} }),
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
// local methods
string get_revert_tooltip() {
var def = selected_definition;
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
(def.is_dirty ? $"Revert changes to '{def.name}'" : "No changes to revert") :
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
null;
}
async Task save_async(IAlgoStudioContentVM content) {
@@ -105,7 +105,7 @@ namespace mbts.spark {
if (def != null) {
switch (def.key.mode) {
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}'" : "No changes to save";
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
}
}
return null;
@@ -24,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;
@@ -311,6 +312,7 @@ namespace mbts.spark {
public FillStatus fill_status => _fill.FillStatus;
public string fill_key => _fill.FillKey;
public string order_key => _order.Key.ToString();
public string symbol => fill_tuple.security?.Symbol;
public bool is_selected {
get => _is_selected;
set => Set(ref _is_selected, value);
@@ -335,6 +337,7 @@ namespace mbts.spark {
CsvHelper.create_ordering_maintainer(typeof(Row),
new string[] {
nameof(security),
nameof(symbol),
nameof(order_type),
nameof(root_algo_display_name),
nameof(side),
@@ -31,5 +31,5 @@ namespace mbts.ui.spark {
InitializeComponent();
}
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
}
@@ -3,7 +3,6 @@ using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.ui;
@@ -26,7 +25,6 @@ namespace mbts.spark {
FillsVM _vm;
FillsView _view;
FillsSettings _state;
SparkMenuDisplayVM _menu_display_vm;
IReadOnlyList<SparkCommand> _commands;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
@@ -41,10 +39,7 @@ namespace mbts.spark {
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state()) {
menu_display = _menu_display_vm.safe_create()
};
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
public override double MinWidth =>
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
@@ -56,8 +51,6 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
_view = new FillsView { DataContext = _vm };
@@ -79,8 +72,7 @@ namespace mbts.spark {
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
})),
_menu_display_vm.spark_cmd
}))
};
}
@@ -88,8 +80,5 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
_menu_display_vm.safe_bind_to_view(parent_view);
}
}
@@ -21,8 +21,6 @@ namespace bts.shell.fills {
public FillsFilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
[ProtoMember(5)]
public SparkMenuDisplaySettings menu_display;
public FillsSettings() { }
public FillsSettings(uint[] orders) : this(orders, null, null) { }
@@ -1,18 +1,15 @@
using bts.api.orderman;
using bts.collections;
using bts.persist.algo;
using bts.proto;
using bts.utils;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using bts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using bts.proto;
using mbts.ui.spark;
using System.Collections.Generic;
using System.ComponentModel;
using mbts.ui.settings;
namespace mbts.spark {
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
@@ -185,97 +182,33 @@ namespace mbts.spark {
_type_hash;
}
[Flags]
enum LadderFavoriteAlgoFlags {
Sticky = 1,
AutoRevert = 2,
}
/// <summary>
/// This struct contains properties for a single algo favorite item.
/// The various <see cref="AlgoPersistedKey"/> arrays in <see cref="LadderFavoriteAlgosSettings"/>,
/// can be used to create a collection of <see cref="LadderFavoriteAlgoItem"/>s.
/// In fact, the method <see cref="LadderFavoriteAlgosSettings.to_algo_items"/> does just that.
/// </summary>
readonly struct LadderFavoriteAlgoItem : IEquatable<LadderFavoriteAlgoItem> {
public readonly AlgoKey key;
public readonly int sort_index;
public readonly LadderFavoriteAlgoFlags flags;
public LadderFavoriteAlgoItem(AlgoKey key, int sort_index, LadderFavoriteAlgoFlags flags) {
this.key = key;
this.sort_index = sort_index;
this.flags = flags;
}
public bool Equals(LadderFavoriteAlgoItem other) =>
key == other.key &&
flags == other.flags;
public override bool Equals(object obj) =>
obj is LadderFavoriteAlgoItem other && Equals(other);
public override int GetHashCode() =>
key.GetHashCode();
}
[ProtoContract]
struct LadderFavoriteAlgosSettings : IEquatable<LadderFavoriteAlgosSettings> {
[ProtoMember(1)]
readonly AlgoPersistedKey[] _favorite_algos;
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
[ProtoMember(2)]
public readonly int column_count;
[ProtoMember(3)]
readonly AlgoPersistedKey[] _stickied_algos;
[ProtoMember(4)]
readonly AlgoPersistedKey[] _auto_revert_algos;
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos, AlgoKey[] auto_revert_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new AlgoPersistedKey(k.persisted)),
ArrayHelper.safe_convert(stickied_algos, k => new AlgoPersistedKey(k.persisted)),
ArrayHelper.safe_convert(auto_revert_algos, k => new AlgoPersistedKey(k.persisted)),
column_count) { }
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
column_count,
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
LadderFavoriteAlgosSettings(AlgoPersistedKey[] favorite_algos, AlgoPersistedKey[] stickied_algos, AlgoPersistedKey[] auto_revert_algos, int column_count) {
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
_favorite_algos = favorite_algos;
_stickied_algos = stickied_algos;
_auto_revert_algos = auto_revert_algos;
this.column_count = column_count;
_stickied_algos = stickied_algos;
}
public IReadOnlyList<AlgoKey> favorite_algos => _favorite_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
public IReadOnlyList<AlgoKey> stickied_algos => _stickied_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
public IReadOnlyList<AlgoKey> auto_revert_algos => _auto_revert_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
/// <summary>
/// A more sane representation of the data contained in this proto,
/// so callers don't need to join the various key lists together to determine which algos are sticky, auto revert, etc.
/// </summary>
/// <returns>An immutable dictionary of favorite algo data.</returns>
public ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> build_map() {
var map = ImmutableDictionary.CreateBuilder<AlgoKey, LadderFavoriteAlgoItem>();
if (_favorite_algos != null) {
var stickied_algo_set = new HashSet<AlgoKey>(stickied_algos);
var auto_revert_algo_set = new HashSet<AlgoKey>(auto_revert_algos);
for (int i = 0; i < _favorite_algos.Length; i++) {
var pk = _favorite_algos[i];
var k = pk.to_algo_key();
var flags = (LadderFavoriteAlgoFlags)0;
if (stickied_algo_set.Contains(k))
flags |= LadderFavoriteAlgoFlags.Sticky;
if (auto_revert_algo_set.Contains(k))
flags |= LadderFavoriteAlgoFlags.AutoRevert;
map[k] = new LadderFavoriteAlgoItem(k, i, flags);
}
}
return map.ToImmutable();
}
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
public bool Equals(LadderFavoriteAlgosSettings other) =>
column_count == other.column_count &&
favorite_algos.SequenceEqual(other.favorite_algos) &&
stickied_algos.SequenceEqual(other.stickied_algos) &&
auto_revert_algos.SequenceEqual(other.auto_revert_algos);
stickied_algos.SequenceEqual(other.stickied_algos);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
@@ -284,8 +217,7 @@ namespace mbts.spark {
EqualityComparer.aggregate_hash(
column_count,
EqualityComparer.aggregate_hash_limited(favorite_algos),
EqualityComparer.aggregate_hash_limited(stickied_algos),
EqualityComparer.aggregate_hash_limited(auto_revert_algos));
EqualityComparer.aggregate_hash_limited(stickied_algos));
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
@@ -349,7 +281,7 @@ namespace mbts.spark {
order_entry_panel_initial_width = _default_order_entry_panel_width;
zoom_factor = _default_zoom_factor;
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null, null);
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
show_warn_on_cross = _default_show_warn_on_cross;
enforce_max_clip = _default_enforce_max_clip;
status_message_visible = true;
@@ -70,11 +70,7 @@ namespace bts.shell {
if (version < _version_where_stickied_algos_introduced) {
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(
favorite_algos_array,
state.settings.favorite_algos.column_count,
favorite_algos_array,
state.settings.favorite_algos.auto_revert_algos.ToArray());
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
}
return load(state);
}
@@ -268,15 +268,14 @@ namespace mbts.spark {
}
}
// on Stop/Limit order updates
// When not triggered, allow for modification to StopPrice
// else treat modification as normal (Price)
public async void try_set_price(OrderRecordImmutable order, double new_price) {
// don't change stop or stop_limits => server crash
if (order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit) return;
if (order.working_quantity() <= 0) return;
_phantom_price = new_price;
RaisePropertyChanged(nameof(price));
++_n_active_price_change_ops;
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity, null)) {
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity)) {
// wait 3 secs, then give up if price change still hasn't taken effect
await Task.Delay(3000);
}
@@ -15,7 +15,6 @@ using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
@@ -747,17 +746,15 @@ namespace mbts.spark {
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
int _column_count = 2;
ImmutableHashSet<AlgoPersistedKey>
_sticky_algo_keys,
_auto_revert_algo_keys;
IDisposable _algo_data_hook;
HashSet<AlgoPersistedKey> _sticky_algo_keys;
IDisposable _sticky_algos_hook;
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
_algo_definitions = algo_definitions;
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
_dispose = Disposable.Create(() => Disposable.dispose(ref _algo_data_hook));
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -774,47 +771,32 @@ namespace mbts.spark {
fai.selector.try_select(k);
return fai;
}));
_algo_data_hook?.Dispose();
_sticky_algo_keys = ImmutableHashSet.CreateRange(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
_auto_revert_algo_keys = ImmutableHashSet.CreateRange(value.auto_revert_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
var sticky_algos = _sticky_algo_keys;
var auto_revert_algos = _auto_revert_algo_keys;
_algo_data_hook = ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null && algo.key.is_persisted) {
var pk = new AlgoPersistedKey(algo.key.persisted);
// sticky
x.is_sticky = _sticky_algo_keys.Contains(pk);
sticky_algos = sticky_algos.Remove(pk);
// auto revert
x.auto_revert = _auto_revert_algo_keys.Contains(pk);
auto_revert_algos = auto_revert_algos.Remove(pk);
if (sticky_algos.Count == 0 && auto_revert_algos.Count == 0)
Disposable.dispose(ref _algo_data_hook);
}
}));
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
Disposable.replace(ref _sticky_algos_hook,
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null &&
algo.key.is_persisted &&
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
_sticky_algo_keys.Count == 0)
//
Disposable.dispose(ref _sticky_algos_hook);
})));
}
public LadderFavoriteAlgosSettings create() {
return new LadderFavoriteAlgosSettings(_raw_items
public LadderFavoriteAlgosSettings create() =>
new LadderFavoriteAlgosSettings(_raw_items
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray(),
column_count,
// sticky
build_filtered_algo_array(fai => fai.is_sticky),
// auto revert
build_filtered_algo_array(fai => fai.auto_revert));
AlgoKey[] build_filtered_algo_array(Func<FavoriteAlgoItem, bool> filter) =>
_raw_items
.Where(filter)
.Where(fai => fai.is_sticky)
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray();
}
.ToArray());
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
@@ -827,9 +809,7 @@ namespace mbts.spark {
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
bool
_auto_revert,
_is_sticky;
bool _is_sticky;
protected FavoriteAlgoItem() { }
@@ -840,7 +820,6 @@ namespace mbts.spark {
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
public bool auto_revert { get => _auto_revert; set => Set(ref _auto_revert, value); }
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
@@ -11,7 +11,6 @@ using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@@ -35,6 +34,7 @@ namespace mbts.spark {
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom, _show_status_message;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double
_position,
_day_position,
@@ -49,7 +49,6 @@ namespace mbts.spark {
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
@@ -169,9 +168,17 @@ namespace mbts.spark {
foreach (var os in _cache.Values) {
var wo = os.order;
var slot_price_target = wo.ui_price();
var slot_price_target = wo.price();
var order_type = wo.order_type();
var filled = wo.filled();
if (order_type == CustomOrderDefinition.CustomOneofCase.Stop) {
// if the stop order has been submitted but hasn't been triggered
// we draw the badge at the stop/trigger price, else the price price
if (filled == 0) { slot_price_target = wo.stop_price(); }
} else if (order_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
// consistent behavior as Stop order above.
if (filled == 0) { slot_price_target = wo.stop_limit_price(); }
}
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
@@ -232,8 +239,8 @@ namespace mbts.spark {
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
return Disposable.Create(
settings.OnDispatcher().Subscribe(s => {
_favorite_algo_map = s.favorite_algos.build_map();
order_properties_vm.on_favorite_algos(_favorite_algo_map, s.favorite_algos.column_count);
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
order_properties_vm.on_favorite_algos(s.favorite_algos);
}),
order_properties_vm,
position_src.OnDispatcher().Subscribe(pos => {
@@ -402,9 +409,9 @@ namespace mbts.spark {
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.ui_price();
double p = order.price();
foreach (var o in lineage(order.ParentKey)) {
if (_equality.Equals(p, o.ui_price()))
if (_equality.Equals(p, o.price()))
order = o;
}
return order;
@@ -503,16 +510,10 @@ namespace mbts.spark {
if (auto_clear)
size_calculator.value = 0;
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
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(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;
}
}
if (d.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
fav_algo.algo_root.revert_all_params();
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
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;
}
}
}
@@ -28,8 +28,7 @@ using System.Windows.Media.Animation;
namespace mbts.spark {
partial class LadderGridHost : Control {
const int _min_row_count = 501, _max_row_count = 2001;
const int _min_row_count = 501, _max_row_count = 2001, _column_min_width = 8;
int _row_count = _min_row_count;
readonly VisualHost _visualHost = new VisualHost();
@@ -109,8 +108,6 @@ namespace mbts.spark {
public event Action visible_columns_changed;
public static double ColumnMinWidth => 6;
public IEnumerable<ColumnType> visible_column_types =>
columns.Where(c => c.settings.show)
.Select(c => c.column_type);
@@ -575,7 +572,7 @@ namespace mbts.spark {
case ColumnType.Price: new_col_width = (int)price_column_width; break;
case ColumnType.Ask: new_col_width = (int)ask_column_width; break;
case ColumnType.Volume: new_col_width = (int)VolumeColumnWidth; break;
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, ColumnMinWidth); break;
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, _column_min_width); break;
}
int true_adj = new_col_width - (int)column.settings.width;
column.settings.width = (uint)new_col_width;
@@ -828,7 +828,6 @@
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
@@ -838,12 +837,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Column="2" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}" ToolTip="Stay Enabled" Margin="4,0">
Stay Enabled
</CheckBox>
<CheckBox Grid.Column="3" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding auto_revert}" ToolTip="Auto Revert" Margin="0,0,4,0">
Auto Revert
</CheckBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
@@ -48,7 +48,7 @@
<Setter Property="BorderThickness" Value="0,0,1,0"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="23" />
<Setter Property="MinWidth" Value="{x:Static spark:LadderGridHost.ColumnMinWidth}"/>
<Setter Property="MinWidth" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
@@ -22,7 +22,7 @@ namespace mbts.spark {
DataContext = vm;
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public MiniOrderBookRowView() : base() {
FocusManager.SetIsFocusScope(this, true);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
@@ -650,13 +650,12 @@ namespace mbts.spark {
ListSortDirection? _cached_sort_direction;
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
: base(Columns.LastMessage, filter, DataGridFilterKnownTemplates.Regex) {
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = DefaultColumnWidths.LastMessage;
TextBinding = new Binding(nameof(OrderBookRowVM.last_msg));
ToolTipBinding = new Binding(nameof(OrderBookRowVM.last_msg));
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
@@ -119,7 +119,7 @@ namespace mbts.spark {
public DateTime last_change => order.last_change();
public string last_change_text => _fmt_date(last_change);
public OrderStatus status => order.status();
public double price => order.ui_price();
public double price => order.price();
public double last_price => order.last_price();
public double avg_price => order.avg_price();
public string price_text => _fmt_price(price);
@@ -300,14 +300,13 @@ namespace mbts.spark {
if ((Columns.OrderType & dirty) != 0)
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
// quantity
if ((Columns.Size & dirty) != 0) {
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;
}
// 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));
@@ -446,7 +445,7 @@ namespace mbts.spark {
dirty |= Columns.LastPrice;
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
dirty |= Columns.AvgFilled;
if (oldval.order.order.ui_price() != newval.order.order.ui_price())
if (oldval.order.order.price() != newval.order.order.price())
dirty |= Columns.Price;
if (oldval.order.order.quantity() != newval.order.order.quantity())
dirty |= Columns.Size;
@@ -9,6 +9,7 @@ using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.extensions;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -31,7 +32,6 @@ namespace mbts.spark {
OrderBookVM _vm;
OrderBookView _view;
OrderBookWindowFactory _factory;
SparkMenuDisplayVM _menu_display_vm;
SparkCommand[] _commands;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
@@ -45,14 +45,7 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
public override OrderBookStartupData SaveState {
get {
_state = _vm?.create();
if (_state != null)
_state.menu_display = _menu_display_vm.safe_create();
return _state;
}
}
public override OrderBookStartupData SaveState => _state = _vm?.create();
public override string window_type => _window_type;
public override UIElement Content => _view;
@@ -71,9 +64,6 @@ namespace mbts.spark {
}
public void thread_init() {
_menu_display_vm = new SparkMenuDisplayVM();
if (_state != null)
_menu_display_vm.load_from(_state.menu_display);
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
@@ -209,8 +199,7 @@ namespace mbts.spark {
show_grid_settings_command,
// hidden cmds (i.e., keyboard shortcuts at window level)
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
_menu_display_vm.spark_cmd
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders))
};
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
@@ -238,13 +227,11 @@ namespace mbts.spark {
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) {
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
_vm.is_attaching_orders = false;
}));
_menu_display_vm.safe_bind_to_view(parent_view);
}
static class CommandGroups {
public const string
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.order.quantity());
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
pb.add_price(fs.price, o => o.order.order.price(), o => o.order.security);
pb.add_numeric(fs.qty_filled, o => o.order.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
@@ -65,9 +65,6 @@ namespace bts.shell.orderbook {
// use defaults
[ProtoMember(25)]
public bool use_defaults;
// menu display
[ProtoMember(27)]
public SparkMenuDisplaySettings menu_display { get; set; }
/// <summary>
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
@@ -99,7 +99,7 @@ namespace mbts.spark {
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public OrderBookRowView() : base() { }
}
}
@@ -1,388 +1,375 @@
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
using bts.view;
using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace bts.shell {
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
const string _window_type = "PNL", _window_title = "PNL";
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
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;
_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);
}
public override Task<IShellWindow> load(PNLSaveState state) =>
Task.FromResult(_create(state.settings));
void _show(PNLSettings settings) =>
_bus.post(SparkTags.ShowWindow, _create(settings));
IShellWindow _create(PNLSettings settings) {
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
Action<SparkWindowView> on_parent_view_created_impl = null;
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
var init_settings = settings ?? PNLSettings.Default;
var menu_display_vm = new SparkMenuDisplayVM();
menu_display_vm.load_from(init_settings.menu_display);
on_parent_view_created_impl = menu_display_vm.safe_bind_to_view;
var vm = new PNLVM(_bus, _orders_model, init_settings);
var content = new PNLView {
DataContext = vm
};
vm.sorting_changed += sh => {
try {
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
} catch { }
};
vm.SubscribeDisposed(
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
.OnDispatcher()
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
Func<PNLSaveState> get_save_state = () => {
var cols = cached_columns.ToArray(c => c.create());
var filters = vm.get_filters_states();
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
var calc_type = vm.calculation_type;
var state = settings_manager.instance_settings;
if (state == null) {
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
var defaults = settings_manager.default_settings;
if (calc_type == defaults.calculation_type &&
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
return new PNLSaveState { use_defaults = true, settings = null };
}
state = defaults;
}
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
state = state.with(
sorting_criterion,
vm.anchors,
filters,
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
calc_type,
vm.header_row_display,
menu_display: menu_display_vm.create());
return new PNLSaveState { use_defaults = false, settings = state };
};
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))),
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
settings_manager,
us => us.pnl,
UserSettings.FileExtensions.PNL,
s => new PNLSettingsVM(s));
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.RequestClose += on_request_close;
return dialog;
void on_request_close(object sender, EventArgs e) =>
WindowUtilities.force_close(dialog);
})),
menu_display_vm.spark_cmd
};
IDisposable dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
vm.load_from(s);
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
var fallback_family = new FontFamily(s.grid.font_family);
// group
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
var group_appearance = s.group_cell_appearance.to_appearance();
// header
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
var header_appearance = s.header_cell_appearance.to_appearance();
//
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
if (col_setting_map.TryGetValue(c.id, out var cs)) {
c.load_from(cs);
}
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
c.load_group_params(
group_appearance.font_size,
gcs.font_weight_override ?? group_appearance.font_weight,
gcs.font_color_override ?? group_appearance.foreground,
group_appearance.font_family);
}
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
c.load_header_params(
header_appearance.font_size,
hcs.font_weight_override ?? header_appearance.font_weight,
hcs.font_color_override ?? header_appearance.foreground,
header_appearance.font_family);
}
}
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
throw new InvalidOperationException();
}, c => c.id.ToLower(), cs => cs.id.ToLower());
}),
content._grid.Columns.Subscribe((s, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: add_new(); break;
case NotifyCollectionChangedAction.Remove: remove_old(); break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
remove_old();
add_new();
break;
case NotifyCollectionChangedAction.Reset:
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
break;
}
void add_new() =>
cached_columns = e.NewStartingIndex >= 0 ?
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
void remove_old() =>
cached_columns = e.OldStartingIndex >= 0 ?
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
menu_display_vm,
vm);
return (content, commands, get_save_state, dispose);
},
// the on_parent_view_created_impl delegate is initialized in the main create delegate,
// so it can't be passed directly as an argument because it would just be null,
// hence the proxy/wrapper delegate below.
on_parent_view_created: pview => on_parent_view_created_impl?.Invoke(pview),
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
min_height_offset: 60);
}
readonly struct PNLConfig {
public readonly bool
enable_animations,
show_pause_resume;
public PNLConfig(bts.config.PNLOptions options) {
enable_animations = options.enable_animations;
show_pause_resume = options.show_pause_resume;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && (
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
//
new_settings = settings;
return true;
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
if (_apply(actual_triggers, default_triggers, out var result)) {
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
return true;
}
return false;
}
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
var snapshot = actual_trigger_map;
foreach (var kv in snapshot) {
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
// assume 1 animation per column
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
actual_col_anims.Difference(default_col_anims,
out var exclusive_actual,
out var exclusive_defaults,
out var diff);
var new_col_anims = exclusive_defaults.ToList();
if (new_col_anims.Count > 0) {
// add the original col animations to the new ones (combine them)
new_col_anims.AddRange(actual_col_anims);
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
}
}
}
foreach (var kv in default_trigger_map) {
if (!actual_trigger_map.ContainsKey(kv.Key))
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
}
if (actual_trigger_map != snapshot) {
result = actual_trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
var defaults = PNLSettings.Default;
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (_apply(columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
var col_list = columns.ToList();
int orig_count = col_list.Count;
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
if (col_list.Count < orig_count) {
result = col_list;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
if (_apply(triggers, out var new_triggers)) {
settings = set_triggers(settings, new_triggers);
return true;
}
return false;
}
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
var snapshot = trigger_map;
foreach (var t in trigger_map.Values) {
var anims = t.animations.ToList();
int orig_count = anims.Count;
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
if (anims.Count < orig_count)
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
}
if (trigger_map != snapshot) {
result = trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && !settings.grid.enable_background_color) {
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
return true;
}
new_settings = null;
return false;
}
}
}
}
using bts.api;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
using bts.spark;
using bts.utils;
using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace bts.shell {
class PNLWindowFactory : ShellWindowFactory<PNLSaveState> {
const string _window_type = "PNL", _window_title = "PNL";
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
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;
_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);
}
public override Task<IShellWindow> load(PNLSaveState state) =>
Task.FromResult(_create(state.settings));
void _show(PNLSettings settings) =>
_bus.post(SparkTags.ShowWindow, _create(settings));
IShellWindow _create(PNLSettings settings) {
var settings_manager = _settings_hub.create_pnl_settings_manager(settings);
// Need to keep a local copy of the columns, since they are defined in xaml (not the viewmodel), and get_save_state may be called on a non-dispatcher thread.
// If that happens, we would be unable to access _grid.Columns, and thus unable to get the current column settings.
// Use an ImmutableList to avoid any threading issues when responding to CollectionChanged events on the dispatcher thread.
ImmutableList<DIYGrid.ColumnBase> cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty;
return SparkWindow<PNLSaveState>.create(_window_type, _window_title, _bus, this, Icons.Accounting, _thread_id_provider.next_id(), () => {
var vm = new PNLVM(_bus, _orders_model, settings ?? PNLSettings.Default);
var content = new PNLView {
DataContext = vm
};
vm.sorting_changed += sh => {
try {
DIYGridSortHelper.set_sort_directions(sh, content._grid.Columns);
} catch { }
};
vm.SubscribeDisposed(
DIYGridSortHelper.observe_active_comparers<PNLView.Columns, PNLRowVM>(content._grid, PNLVM.get_column_comparer)
.OnDispatcher()
.Subscribe(sh => vm.sorting_hierarchy = sh.Select(s => (s.key, s.direction)).ToList()));
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
Func<PNLSaveState> get_save_state = () => {
var cols = cached_columns.ToArray(c => c.create());
var filters = vm.get_filters_states();
var sorting_criterion = vm.sorting_hierarchy.Select(tup => new SortingCriteria(tup.key.ToString(), tup.direction == ListSortDirection.Ascending)).ToArray();
var calc_type = vm.calculation_type;
var state = settings_manager.instance_settings;
if (state == null) {
// don't use defaults if the user manipulated the window state outside of the settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
var defaults = settings_manager.default_settings;
if (calc_type == defaults.calculation_type &&
defaults.sorting_criterion.SequenceEqual(sorting_criterion) &&
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
// the user's current state still matches the defaults (i.e., no outside manipulation was performed); return null so this window will use the latest default settings when restored
return new PNLSaveState { use_defaults = true, settings = null };
}
state = defaults;
}
// assign latest values, as they may have be modified outside of the settings dialog (in which case the settings manager would be out of date)
state = state.with(
sorting_criterion,
vm.anchors,
filters,
state.grid.with(columns: cols, background: vm.grid_background_color.rgb_color()),
calc_type,
vm.header_row_display);
return new PNLSaveState { use_defaults = false, settings = state };
};
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))),
new SparkCommandCommand("Play", Icons.Play, new DelegateCommand(() => vm.is_paused = false)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Pause", Icons.Pause, new DelegateCommand(() => vm.is_paused = true)) {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
settings_manager,
us => us.pnl,
UserSettings.FileExtensions.PNL,
s => new PNLSettingsVM(s));
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.RequestClose += on_request_close;
return dialog;
void on_request_close(object sender, EventArgs e) =>
WindowUtilities.force_close(dialog);
}))
};
IDisposable dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(s => {
vm.load_from(s);
var col_setting_map = s.grid.columns.ToDictionary(c => c.id);
var fallback_family = new FontFamily(s.grid.font_family);
// group
var group_col_setting_map = s.group_columns.ToDictionary(c => c.id);
var group_appearance = s.group_cell_appearance.to_appearance();
// header
var header_col_setting_map = s.header_columns.ToDictionary(c => c.id);
var header_appearance = s.header_cell_appearance.to_appearance();
//
foreach (var c in content._grid.Columns.OfType<PNLColumn>()) {
if (col_setting_map.TryGetValue(c.id, out var cs)) {
c.load_from(cs);
}
if (group_col_setting_map.TryGetValue(c.id, out var gcs)) {
c.load_group_params(
group_appearance.font_size,
gcs.font_weight_override ?? group_appearance.font_weight,
gcs.font_color_override ?? group_appearance.foreground,
group_appearance.font_family);
}
if (header_col_setting_map.TryGetValue(c.id, out var hcs)) {
c.load_header_params(
header_appearance.font_size,
hcs.font_weight_override ?? header_appearance.font_weight,
hcs.font_color_override ?? header_appearance.foreground,
header_appearance.font_family);
}
}
CollectionHelper.sync_with_never_duplicate(content._grid.Columns, s.grid.columns, cs => {
throw new InvalidOperationException();
}, c => c.id.ToLower(), cs => cs.id.ToLower());
}),
content._grid.Columns.Subscribe((s, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add: add_new(); break;
case NotifyCollectionChangedAction.Remove: remove_old(); break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
remove_old();
add_new();
break;
case NotifyCollectionChangedAction.Reset:
cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(content._grid.Columns);
break;
}
void add_new() =>
cached_columns = e.NewStartingIndex >= 0 ?
cached_columns.InsertRange(e.NewStartingIndex, e.NewItems.OfType<DIYGrid.ColumnBase>()) :
cached_columns.AddRange(e.NewItems.OfType<DIYGrid.ColumnBase>());
void remove_old() =>
cached_columns = e.OldStartingIndex >= 0 ?
cached_columns.RemoveRange(e.OldStartingIndex, e.OldItems.Count) :
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
vm);
return (content, commands, get_save_state, dispose);
},
min_width_offset: SystemParameters.HorizontalScrollBarButtonWidth + 8,
min_height_offset: 60);
}
readonly struct PNLConfig {
public readonly bool
enable_animations,
show_pause_resume;
public PNLConfig(bts.config.PNLOptions options) {
enable_animations = options.enable_animations;
show_pause_resume = options.show_pause_resume;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && (
_apply(ref settings, settings.animations, PNLSettings.default_animations, (s, anims) => s.with(animations: anims)) |
_apply(ref settings, settings.group_header_animations, PNLSettings.default_group_header_animations, (s, anims) => s.with(group_header_animations: anims)) |
_apply(ref settings, settings.header_row_animations, PNLSettings.default_header_row_animations, (s, anims) => s.with(header_row_animations: anims)))) {
//
new_settings = settings;
return true;
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
if (_apply(actual_triggers, default_triggers, out var result)) {
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
return true;
}
return false;
}
static bool _apply(IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var default_trigger_map = default_triggers.ToDictionary(t => t.expression);
var actual_trigger_map = actual_triggers.ToImmutableDictionary(t => t.expression);
var snapshot = actual_trigger_map;
foreach (var kv in snapshot) {
if (default_trigger_map.TryGetValue(kv.Key, out var default_anims)) {
// assume 1 animation per column
var actual_col_anims = kv.Value.animations.ToDictionaryCrushed(a => a.column);
var default_col_anims = default_anims.animations.ToDictionaryCrushed(a => a.column);
actual_col_anims.Difference(default_col_anims,
out var exclusive_actual,
out var exclusive_defaults,
out var diff);
var new_col_anims = exclusive_defaults.ToList();
if (new_col_anims.Count > 0) {
// add the original col animations to the new ones (combine them)
new_col_anims.AddRange(actual_col_anims);
actual_trigger_map = actual_trigger_map.SetItem(kv.Key, kv.Value.with(animations: new_col_anims.Select(col_anim => col_anim.Value).ToArray()));
}
}
}
foreach (var kv in default_trigger_map) {
if (!actual_trigger_map.ContainsKey(kv.Key))
actual_trigger_map = actual_trigger_map.Add(kv.Key, kv.Value);
}
if (actual_trigger_map != snapshot) {
result = actual_trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
var defaults = PNLSettings.Default;
if (_apply(ref settings, settings.grid.columns, defaults.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, defaults.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, defaults.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
static bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> actual_columns, IEnumerable<GridColumnSettings> default_columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (GridSettingsHelper.add_missing_columns(actual_columns, default_columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.grid.columns, (s, cols) => s.with(grid: s.grid.with(cols))) |
_apply(ref settings, settings.group_columns, (s, cols) => s.with(group_columns: cols)) |
_apply(ref settings, settings.header_columns, (s, cols) => s.with(header_columns: cols))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<GridColumnSettings> columns, Func<PNLSettings, IEnumerable<GridColumnSettings>, PNLSettings> set_columns) {
if (_apply(columns, out var new_cols)) {
settings = set_columns(settings, new_cols);
return true;
}
return false;
}
bool _apply(IEnumerable<GridColumnSettings> columns, out IEnumerable<GridColumnSettings> result) {
var col_list = columns.ToList();
int orig_count = col_list.Count;
col_list.RemoveWhere(c => !Enum.TryParse<PNLView.Columns>(c.id, true, out var _));
if (col_list.Count < orig_count) {
result = col_list;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class RemoveColumnAnimationsMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null) {
if (_apply(ref settings, settings.animations, (s, t) => s.with(animations: t)) |
_apply(ref settings, settings.group_header_animations, (s, t) => s.with(group_header_animations: t)) |
_apply(ref settings, settings.header_row_animations, (s, t) => s.with(header_row_animations: t))) {
new_settings = settings;
return true;
}
}
new_settings = null;
return false;
}
bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> triggers, Func<PNLSettings, IEnumerable<PNLAnimationTriggerData>, PNLSettings> set_triggers) {
if (_apply(triggers, out var new_triggers)) {
settings = set_triggers(settings, new_triggers);
return true;
}
return false;
}
bool _apply(IEnumerable<PNLAnimationTriggerData> triggers, out IEnumerable<PNLAnimationTriggerData> result) {
var trigger_map = triggers.ToImmutableDictionary(t => t.expression);
var snapshot = trigger_map;
foreach (var t in trigger_map.Values) {
var anims = t.animations.ToList();
int orig_count = anims.Count;
anims.RemoveWhere(a => !Enum.IsDefined(typeof(PNLView.Columns), a.column));
if (anims.Count < orig_count)
trigger_map = trigger_map.SetItem(t.expression, t.with(animations: anims.ToArray()));
}
if (trigger_map != snapshot) {
result = trigger_map.Values;
return true;
}
result = null;
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class EnableGridBackgroundColorMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && !settings.grid.enable_background_color) {
new_settings = settings.with(grid: settings.grid.with(enable_background_color: true));
return true;
}
new_settings = null;
return false;
}
}
}
}
@@ -224,8 +224,6 @@ namespace bts.shell.pnl {
}
[ProtoMember(6)]
public PNLHeaderRowDisplay header_row_display { get; set; }
[ProtoMember(15)]
public SparkMenuDisplaySettings menu_display { get; set; }
public IReadOnlyList<Columns> anchors => _anchors ?? (_anchors = Array.Empty<Columns>());
public IReadOnlyList<PNLAnimationTriggerData> animations => _animations ?? (_animations = default_animations.ToArray());
@@ -277,8 +275,7 @@ namespace bts.shell.pnl {
IEnumerable<PNLAnimationTriggerData> animations = null,
IEnumerable<PNLAnimationTriggerData> group_header_animations = null,
IEnumerable<PNLAnimationTriggerData> header_row_animations = null,
IEnumerable<GridColumnSettings> header_columns = null,
SparkMenuDisplaySettings? menu_display = null) =>
IEnumerable<GridColumnSettings> header_columns = null) =>
new PNLSettings {
calculation_type = calculation_type ?? this.calculation_type,
header_row_display = header_row_display ?? this.header_row_display,
@@ -298,8 +295,7 @@ namespace bts.shell.pnl {
_group_header_animations = group_header_animations?.ToArray() ?? _group_header_animations,
_header_row_animations = header_row_animations?.ToArray() ?? _header_row_animations,
// grid
grid = grid ?? this.grid,
menu_display = menu_display ?? this.menu_display
grid = grid ?? this.grid
};
public bool Equals(PNLSettings other) =>
@@ -490,4 +486,4 @@ namespace bts.shell.pnl {
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> group_triggers(this PNLSettings settings) => settings.group_header_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
public static IReadOnlyList<IPNLAnimationTrigger<PNLNormalizedData>> header_triggers(this PNLSettings settings) => settings.header_row_animations.Select(a => a.to_trigger<PNLNormalizedData>()).ToList();
}
}
}
@@ -29,10 +29,6 @@
<Color x:Key="GroupRowForegroundColor">#FF000000</Color>
<!--Converters-->
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<cv:ValueConverterPipe x:Key="GroupRowHeightConverter">
<cv:RangeBlockConverter Minimum="{x:Static pnl:PNLView.MinGroupRowHeight}"/>
<cv:GridLengthConverter UnitType="Pixel"/>
</cv:ValueConverterPipe>
<cv:MultiValueConverterPipe x:Key="GroupRowBackgroundConverter">
<cv:PartialConverter/>
<cv:ColorToBrushConverter/>
@@ -262,7 +258,7 @@
</Setter>
</Style>
<Style x:Key="TextCellBase" TargetType="c:TextCell">
<Style x:Key="RowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
@@ -273,14 +269,10 @@
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
</Style>
<Style x:Key="RowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -292,7 +284,20 @@
</Setter>
</Style>
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Style x:Key="GroupRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -304,8 +309,21 @@
</Setter>
</Style>
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell" BasedOn="{StaticResource TextCellBase}">
<Style x:Key="HeaderRowTextCell" TargetType="c:TextCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -317,22 +335,7 @@
</Setter>
</Style>
<Style x:Key="HyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Style x:Key="HyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
@@ -346,23 +349,41 @@
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Cursor">
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<Binding Path="IsMouseOverClickArea" RelativeSource="{RelativeSource Self}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="{x:Static Cursors.Arrow}" ValueForTrue="{x:Static Cursors.Hand}"/>
</Binding.Converter>
</Binding>
<x:Array Type="{x:Type c:CachedColumnParam}">
<c:CachedColumnParam SourcePropertyName="font_family" TargetProperty="c:HyperlinkCell.FontFamily"/>
<c:CachedColumnParam SourcePropertyName="font_size" TargetProperty="c:HyperlinkCell.FontSize"/>
<c:CachedColumnParam SourcePropertyName="font_brush" TargetProperty="c:HyperlinkCell.Foreground"/>
<c:CachedColumnParam SourcePropertyName="horizontal_alignment" TargetProperty="c:HyperlinkCell.HorizontalContentAlignment"/>
</x:Array>
</Setter.Value>
</Setter>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Text" Value="{Binding text}"/>
</Style>
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="RowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{Binding border}"/>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="{x:Static c:DIYGrid.AnimatableCellPadding}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -382,9 +403,24 @@
</Setter>
</Style>
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="HeaderRowHyperlinkCellBase" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="c:DIYGrid.ObservedCachedColumnParams">
<Setter.Value>
<x:Array Type="{x:Type c:CachedColumnParam}">
@@ -418,7 +454,23 @@
</Setter>
</Style>
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell" BasedOn="{StaticResource HyperlinkCellBase}">
<Style x:Key="GroupRowHyperlinkCell" TargetType="c:HyperlinkCell">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding background}"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding foreground}"/>
</Setter.Value>
</Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="FontWeight" Value="{Binding font_weight}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Text" Value="{Binding text}"/>
<Setter Property="Command" Value="{x:Static pnl:PNLView.CellClickCommand}"/>
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static pnl:PNLView.CellParamsConverter}">
@@ -454,7 +506,7 @@
<Grid
c:DIYGrid.SyncWithColumnGeometrySource="{Binding ElementName=_grid}"
c:DIYGrid.SyncWithColumnGeometry="True"
Height="{Binding RowHeight, ElementName=_grid, Converter={x:Static pnl:PNLView.HeaderRowHeightConverter}}"
Height="{Binding RowHeight, ElementName=_grid}"
>
<Grid.Visibility>
<Binding Path="header_row_display">
@@ -557,6 +609,7 @@
c:DIYGrid.CachedColumnParamsContainer="{Binding RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"
VirtualizingPanel.ScrollUnit="Pixel"
ScrollViewer.CanContentScroll="True"
RowHeight="24"
Rows="{Binding rows_view}"
SelectionHighlight="{Binding selection_highlight}"
SupportGroupItems="True"
@@ -1000,7 +1053,7 @@
<!--Row Template-->
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}" Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}">
<Grid c:DIYGrid.SyncWithColumnGeometry="True" usp:GridAnimation.RowAnimation="{Binding animations}">
<Grid.Background>
<SolidColorBrush Color="{Binding DataContext.grid_background_color, RelativeSource={RelativeSource AncestorType=pnl:PNLView}}"/>
</Grid.Background>
@@ -1094,7 +1147,7 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GroupRowHeightConverter}}"/>
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GridLengthPixelConverter}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Gray" StrokeThickness="1"/>
@@ -1164,4 +1217,4 @@
<Rectangle Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="Gray"/>
</Grid>
</AdornerDecorator>
</UserControl>
</UserControl>
@@ -6,7 +6,6 @@ using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -32,10 +31,6 @@ namespace bts.shell.pnl {
}
partial class PNLView : UserControl {
public static readonly double
MinGroupRowHeight = 24.0.RoundLayoutValue(),
MinHeaderRowHeight = 24.0.RoundLayoutValue();
public enum Columns {
Account = 1,
// Security
@@ -90,7 +85,6 @@ namespace bts.shell.pnl {
public static readonly IValueConverter
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
HeaderRowHeightConverter = new RangeBlockConverter<double> { Minimum = MinHeaderRowHeight },
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
public static readonly IMultiValueConverter
@@ -169,22 +163,17 @@ namespace bts.shell.pnl {
void _grid_loaded(object sender, RoutedEventArgs e) {
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
rp.SetBinding(MarginProperty,
BindingHelper.create_multi(
ValueConverter.create<PNLHeaderRowDisplay, double, Thickness>((hrd, row_height) => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += row_height;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += row_height;
return new Thickness(0, top, 0, 0);
}),
new Binding(nameof(PNLVM.header_row_display)),
new Binding {
Path = new PropertyPath(DIYGrid.RowHeightProperty),
Converter = HeaderRowHeightConverter,
Source = _grid
}));
rp.SetBinding(MarginProperty, new Binding(nameof(PNLVM.header_row_display)) {
Converter = ValueConverter.create<PNLHeaderRowDisplay, Thickness>(hrd => {
double top = 0;
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
top += 24;
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
top += 24;
return new Thickness(0, top, 0, 0);
}),
Source = DataContext
});
}
}
#endregion
+1 -3
View File
@@ -54,8 +54,7 @@ namespace mbts.spark {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
model.send_order(security, properties_vm.create(side), price, size_calculator.value.Value).ignore();
if (close_on_send)
RequestClose?.Invoke(this, new EventArgs());
RequestClose?.Invoke(this, new EventArgs());
}
});
set_price_command = new DelegateCommand<double>(set_price);
@@ -85,7 +84,6 @@ namespace mbts.spark {
public CalculatorLogic size_calculator { get; }
public OrderPropertiesEditorVM properties_vm { get; }
public BBOVM bbo { get; }
public bool close_on_send { get; set; }
public ICommand send_order { get; }
public ICommand set_price_command { get; }
+15 -44
View File
@@ -653,54 +653,25 @@ namespace mbts.spark.utilities {
return def;
}
}
static CustomOrderDefinition.CustomOneofCase[] algo_config(
Func<bts.config.AlgoPermissions, List<string>> selector)
{
var conf = mbts.config.Configuration.get<bts.config.AlgoPermissions>();
return selector(conf).Select(x =>
{
return (CustomOrderDefinition.CustomOneofCase)System.Enum.Parse(typeof(CustomOrderDefinition.CustomOneofCase), x, true);
}).ToArray();
}
public static readonly CustomOrderDefinition.CustomOneofCase[]
// exchange_order_types are the types which can be submitted on an exchange security
exchange_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset,
CustomOrderDefinition.CustomOneofCase.Splitter,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
spreader_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Spreader,
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Splitter,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
attachable_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset,
CustomOrderDefinition.CustomOneofCase.Scratch,
CustomOrderDefinition.CustomOneofCase.Prorata,
},
outright_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
};
exchange_order_types = algo_config(x => x.exchange_order_types),
spreader_order_types = algo_config(x => x.spreader_order_types),
attachable_order_types = algo_config(x => x.attachable_order_types),
outright_order_types = algo_config(x => x.outright_order_types);
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
public static void copy_values(CustomOrderDefinitionImmutable source, ref CustomOrderDefinition target) =>
+128 -151
View File
@@ -1,113 +1,90 @@
using bts;
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class OrderParamsAggregator {
readonly TimeSpan _throttle;
readonly CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> _algo_def_mon =
new CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable>(
ask => ask,
OrderDataHelper.EqualsHelpers.custom_order_defi_structure_comparer,
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgo> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgo>();
readonly DynamicOrderHelper _dyn_helper;
readonly Func<uint, OrderSecurityAlgo?> _lookup_order;
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle, Func<uint, OrderSecurityAlgo?> lookup_order) {
_dyn_helper = dyn_helper;
_throttle = throttle;
_lookup_order = lookup_order;
_group_mon.add_member_mon(_algo_def_mon, _build_algo_tree);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
public SecurityImmutable? security => _security_mon.common_value;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
/// <summary>
/// Notifies observers when the set of <see cref="CustomOrderDefinitionImmutable"/>s changes.
/// The algos in the set are the distinct group of algos for the selected <see cref="OrderSecurityAlgo"/>s.
/// </summary>
// Note: this might be better optimized by adding a 'begin_reset' method to CommonValueMonitor that records the current state,
// and accumulates subsequent changes into a batch change. If the 'begin_reset' method returned an IDisposable,
// the batch change object could be examined when Dispose is called,
// to avoid firing the common_values_changed event if the accumulated changes cancel each other out.
// However, such a change would be non-trivial, so this method will suffice for now.
// It should perform fine in practice, even with a large number of selected orders,
// because the input is throttled.
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() =>
Observable.Create<ImmutableHashSet<CustomOrderDefinitionImmutable>>(o => {
var last_algos = ImmutableHashSet.Create(OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
return _algo_def_mon
.SubscribeThrottled(_throttle, (ask, vals) => vals, Array.Empty<CustomOrderDefinitionImmutable>())
.Subscribe(algos => {
// only notify the observer if the algo set has actually changed.
if (algos.Count != last_algos.Count || !algos.All(last_algos.Contains))
o.OnNext(last_algos = ImmutableHashSet.CreateRange(OrderDataHelper.EqualsHelpers.custom_order_defi_comparer, algos));
});
});
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable defer_updates() =>
_group_mon.defer_updates();
public void Add(OrderSecurityAlgo order_tuple) =>
_group_mon.add(order_tuple);
public void Remove(OrderSecurityAlgo order_tuple) =>
_group_mon.remove(order_tuple);
public void Clear() =>
_group_mon.clear();
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgo osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
CustomOrderDefinitionImmutable _build_algo_tree(OrderSecurityAlgo root) =>
root.order.Def.AttachTargetOrderKey == 0 ?
root.order.Def.CustomDef :
new CustomOrderDefinitionImmutable(_add_attached_targets(root.order.Def.CustomDef.CloneToMutable(), root.order.Key, root.order.Def.AttachTargetOrderKey));
CustomOrderDefinition _add_attached_targets(CustomOrderDefinition mroot, uint root_id, uint attached_target) {
var lin = _lineage(root_id, attached_target);
if (lin != null && lin.Count > 0) {
var mparent = mroot;
var pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
using bts;
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class OrderParamsAggregator {
readonly TimeSpan _throttle;
readonly CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> _algo_def_mon =
new CommonValueGroupMonitor<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable>(
ask => ask,
OrderDataHelper.EqualsHelpers.custom_order_defi_structure_comparer,
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer);
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgo> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgo>();
readonly DynamicOrderHelper _dyn_helper;
readonly Func<uint, OrderSecurityAlgo?> _lookup_order;
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle, Func<uint, OrderSecurityAlgo?> lookup_order) {
_dyn_helper = dyn_helper;
_throttle = throttle;
_lookup_order = lookup_order;
_group_mon.add_member_mon(_algo_def_mon, _build_algo_tree);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
public SecurityImmutable? security => _security_mon.common_value;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals, Enumerable.Empty<CustomOrderDefinitionImmutable>());
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable defer_updates() =>
_group_mon.defer_updates();
public void Add(OrderSecurityAlgo order_tuple) =>
_group_mon.add(order_tuple);
public void Remove(OrderSecurityAlgo order_tuple) =>
_group_mon.remove(order_tuple);
public void Clear() =>
_group_mon.clear();
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgo osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
CustomOrderDefinitionImmutable _build_algo_tree(OrderSecurityAlgo root) =>
root.order.Def.AttachTargetOrderKey == 0 ?
root.order.Def.CustomDef :
new CustomOrderDefinitionImmutable(_add_attached_targets(root.order.Def.CustomDef.CloneToMutable(), root.order.Key, root.order.Def.AttachTargetOrderKey));
CustomOrderDefinition _add_attached_targets(CustomOrderDefinition mroot, uint root_id, uint attached_target) {
var lin = _lineage(root_id, attached_target);
if (lin != null && lin.Count > 0) {
var mparent = mroot;
var pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
if (pcad.HasValue) {
do {
var mchild = lin.Pop().order.Def.CustomDef.CloneToMutable();
@@ -115,44 +92,44 @@ namespace mbts.spark {
mparent = mchild;
pcad = OrderDataHelper.get_primary_child_algo_data(mparent);
} while (lin.Count > 0 && pcad.HasValue);
}
}
return mroot;
}
Stack<OrderSecurityAlgo> _lineage(uint root, uint target) {
// check args/preconditions
if (root == 0) throw new ArgumentException("Cannot be zero.", nameof(root));
if (target == 0) throw new ArgumentException("Cannot be zero.", nameof(target));
if (target == root) return null;
var inner = _lookup_order(target);
if (!inner.HasValue)
return null;
// if 'target' has an attach target iteself (nested), push the nested lineage onto the stack first,
// then push the original target onto the stack, and try to hop up each algo-generation
// via parent-key lookups until we reach the root.
// **If the lineage is disjointed, and we never reach the root, return null**
var ival = inner.Value;
var lin_stack = ival.order.attached_target_order_key() != 0 ?
_lineage(ival.order.Key, ival.order.attached_target_order_key()) ?? new Stack<OrderSecurityAlgo>() :
new Stack<OrderSecurityAlgo>();
lin_stack.Push(ival);
target = ival.order.ParentKey;
while (target != root) {
if (target == 0) {
// **disjointed lineage**
return null;
}
inner = _lookup_order(target);
if (!inner.HasValue) {
// **disjointed lineage**
return null;
}
ival = inner.Value;
lin_stack.Push(ival);
target = ival.order.ParentKey;
}
return lin_stack;
}
}
}
}
}
return mroot;
}
Stack<OrderSecurityAlgo> _lineage(uint root, uint target) {
// check args/preconditions
if (root == 0) throw new ArgumentException("Cannot be zero.", nameof(root));
if (target == 0) throw new ArgumentException("Cannot be zero.", nameof(target));
if (target == root) return null;
var inner = _lookup_order(target);
if (!inner.HasValue)
return null;
// if 'target' has an attach target iteself (nested), push the nested lineage onto the stack first,
// then push the original target onto the stack, and try to hop up each algo-generation
// via parent-key lookups until we reach the root.
// **If the lineage is disjointed, and we never reach the root, return null**
var ival = inner.Value;
var lin_stack = ival.order.attached_target_order_key() != 0 ?
_lineage(ival.order.Key, ival.order.attached_target_order_key()) ?? new Stack<OrderSecurityAlgo>() :
new Stack<OrderSecurityAlgo>();
lin_stack.Push(ival);
target = ival.order.ParentKey;
while (target != root) {
if (target == 0) {
// **disjointed lineage**
return null;
}
inner = _lookup_order(target);
if (!inner.HasValue) {
// **disjointed lineage**
return null;
}
ival = inner.Value;
lin_stack.Push(ival);
target = ival.order.ParentKey;
}
return lin_stack;
}
}
}
@@ -3,6 +3,7 @@ using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
@@ -13,7 +14,6 @@ using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
@@ -62,7 +62,7 @@ namespace mbts.spark {
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly AlgoDefinitionVM the_anchor_algo_def_vm;
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
IDisposable _algo_param_rows_hook;
int _favorite_algos_column_count;
bool _open_algo_popup_on_select, _use_algo_param_grid;
@@ -239,15 +239,12 @@ namespace mbts.spark {
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
Disposable.dispose(ref _algo_param_rows_hook);
if (old_value != null) {
if (old_value != null)
// Taha feels that we should always revert to a Limit order either when the user explicity unchecks a sticky button
// or when a non-sticky button is pressed. Rather than completely remove the stack at this time (b/c it may be come
// back in the future), let's just always push a specfic def_vm.key onto it [Limit] ;)
//_selected_algo_stack.Push(old_value.key);
_selected_algo_stack.Push(the_anchor_algo_def_vm.key);
if (_favorite_algo_map.TryGetValue(old_value.key, out var tup) && tup.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
old_value.algo_root.revert_all_params();
}
if (new_value != null) {
if (use_algo_param_grid)
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
@@ -343,9 +340,6 @@ namespace mbts.spark {
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
public IObservable<AlgoDefinitionVM> observe_algo_definition_vm(bool start_immediately = false) =>
_selected_algo_definition.observe_selected_item(start_immediately);
public OrderDefinition create(Side side) {
if (HasErrors)
throw new InvalidOperationException("Cannot create order when errors are present.");
@@ -371,12 +365,12 @@ namespace mbts.spark {
return order;
}
public void on_favorite_algos(ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> favorite_algo_map, int favorite_algos_column_count) {
_favorite_algo_map = favorite_algo_map;
public void on_favorite_algos(LadderFavoriteAlgosSettings value) {
_favorite_algos_index = value.favorite_algos.Select((v, i) => (v, i)).ToDictionaryCrushed(t => t.v, t => t.i);
foreach (var d in algo_definitions) {
d.favorite_index = _get_favorite_algo_index(d.key);
}
this.favorite_algos_column_count = favorite_algos_column_count;
favorite_algos_column_count = value.column_count;
RaisePropertyChanged(nameof(selected_favorite_algo_definition));
}
@@ -389,7 +383,7 @@ namespace mbts.spark {
}
int? _get_favorite_algo_index(AlgoKey key) =>
key.mode == AlgoKey.Mode.Persisted && _favorite_algo_map.TryGetValue(key, out var tup) ? tup.sort_index : (int?)null;
key.mode == AlgoKey.Mode.Persisted && _favorite_algos_index.TryGetValue(key, out int i) ? i : (int?)null;
void _on_template_metadata(IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> meta) {
_algo_metadata = meta;
@@ -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));
}
}
}
}
@@ -96,9 +96,41 @@ namespace mbts.spark {
{
Google.Protobuf.Reflection.MessageDescriptor desc = new TMutable().Descriptor;
_parameters.SyncWithDictionary(
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), _mk_param_vm);
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), x => _mk_param_vm(x));
add_message_params(desc);
}
}
void add_message_params(Google.Protobuf.Reflection.MessageDescriptor desc)
{
var msgs = desc.Fields.InDeclarationOrder().Where(x => !_is_value_param_desc(x) &&
x.MessageType.ClrType != typeof(CustomOrderDefinition) );
foreach(var message_field in msgs)
{
foreach (var fd in message_field.MessageType.Fields.InDeclarationOrder())
{
if(_is_value_param_desc(fd)) //one level deep
{
var mk = _mk_param_vm(fd,
outside_object =>
{
var msgfield = message_field.Accessor.GetValue(outside_object);
if(msgfield == null)
{
message_field.Accessor.SetValue(outside_object, Activator.CreateInstance(message_field.MessageType.ClrType));
msgfield = message_field.Accessor.GetValue(outside_object);
}
return (IMessage)msgfield;
});
if(mk == null)
continue;
if(!_parameters.Add(mk))
_parameters.Add(message_field.Name + "_" + mk.descriptor.Name, mk);
}
}
}
}
static bool _is_value_param_desc(FieldDescriptor desc)
{
return (desc.FieldType != Google.Protobuf.Reflection.FieldType.Message ||
@@ -117,50 +149,53 @@ namespace mbts.spark {
return v;
}
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor)
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor, Func<IMessage, IMessage> message_override = null)
{
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
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);
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);
attached(pvm, GenericOrderHelper.get_enumerated_value, GenericOrderHelper.set_enumerated_value);
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());
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float)
return mk<double, double>(descriptor, v => v, v => v, double.NaN);
return mk<double, double>(descriptor, v => v, v => v, message_override, double.NaN);
else if (descriptor.FieldType == FieldType.Int32)
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault());
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
else if (descriptor.FieldType == FieldType.UInt32)
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault());
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault(), message_override);
else if (descriptor.FieldType == FieldType.String)
return mk<string, string>(descriptor, v => v, v => v);
return mk<string, string>(descriptor, v => v, v => v, message_override);
else
return null;
}
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default)
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back,
Func<IMessage, IMessage> message_override = null, TView default_value = default)
{
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back, message_override);
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param,
message_override, default_value);
TView get_from_param(IMessage param, FieldDescriptor fd) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
void assign_to_param(IMessage param, FieldDescriptor fd, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
}
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param,
Func<IMessage, IMessage> message_override = null, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
{
add_property<T>(
pvm.descriptor.Name,
mdef => mdef != null ? selector(mdef, pvm.descriptor) : default_value,
(mdef, v) => assign_to_param(mdef, pvm.descriptor, v),
mdef => mdef != null ? selector( message_override == null ? mdef : message_override(mdef), pvm.descriptor) : default_value,
(mdef, v) => assign_to_param(message_override == null ? mdef : message_override(mdef), pvm.descriptor, v),
() => pvm.value,
v => pvm.value = v,
pvm.subscribe_value(),
@@ -129,13 +129,6 @@ namespace mbts.spark {
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)));
public static void revert_all_params(this IAlgoVM vm) {
foreach (var p in vm.enumerate_all_params()) {
if (p.is_dirty)
p.revert();
}
}
public static IObservable<bool> subscribe_is_dirty(this IAlgoParamVM param_vm) =>
Observable.Create<bool>(o => {
o.OnNext(param_vm.is_dirty);
@@ -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); }
}
}
}
+2 -29
View File
@@ -35,24 +35,11 @@ namespace mbts.spark {
public static double price(this OrderRecordImmutable order) =>
order.HasState ? order.State.Price : double.NaN;
public static double ui_price(this OrderRecordImmutable order) {
if (order.HasState) {
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop && !order.stop_or_stop_limit_has_been_triggered()) {
return order.stop_price();
}
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit && !order.stop_or_stop_limit_has_been_triggered()) {
return order.stop_limit_price();
}
return order.State.Price;
}
return double.NaN;
}
public static double stop_price(this OrderRecordImmutable order) =>
order.HasState && order.State.HasStop && order.State.Stop.HasNativeState ? order.State.Stop.NativeState.StopPx : double.NaN;
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasStop ? order.Def.CustomDef.Stop.StopPrice : double.NaN;
public static double stop_limit_price(this OrderRecordImmutable order) =>
order.HasState && order.State.HasStopLimit && order.State.StopLimit.HasNativeState ? order.State.StopLimit.NativeState.StopPx : double.NaN;
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasStopLimit ? order.Def.CustomDef.StopLimit.StopPrice : double.NaN;
public static double avg_price(this OrderRecordImmutable order) =>
order.HasState ? order.State.AvgPrice : double.NaN;
@@ -261,20 +248,6 @@ namespace mbts.spark {
}
}
public static bool stop_or_stop_limit_has_been_triggered(this OrderRecordImmutable order) {
if (order.HasState && (order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit)) {
var native_state = order.State.native_order_state();
if (native_state.HasValue) {
if (order.State.HasStop || order.State.HasStopLimit) {
return native_state.Value.NativeType == NativeOrderType.Limit;
}
} else {
return false;
}
}
return false;
}
public static string type_id(this CustomOrderDefinition.CustomOneofCase order_type) =>
order_type.ToString().ToUpper();
+2 -25
View File
@@ -154,9 +154,7 @@ namespace mbts.spark {
.GroupTable(OrderSecurityHelper.key_fn, tup => tup.order.Def.SecurityKey)
.Cache();
// working account orders
var working_account_order_groups = working_orders
.Filter(o => o.order.is_exchange_order())
.GroupTable(t => t.order.Key, t => new PNLKey(t.order.account_id(), t.order.security_key()));
var working_account_order_groups = working_orders.GroupTable(t => t.order.Key, t => new PNLKey(t.order.account_id(), t.order.security_key()));
// security account pnl
security_account_pnl = on_pnl
#if DEBUG
@@ -351,21 +349,6 @@ namespace mbts.spark {
return success;
}
private CustomOrderDefinitionImmutable? _create_custom_def_for_stop_and_stop_limit(OrderRecordImmutable order, ref double stop_price) {
CustomOrderDefinition modified_custom_def = null;
var ord_type = order.order_type();
if (ord_type == CustomOrderDefinition.CustomOneofCase.Stop) {
modified_custom_def = order.Def.CustomDef.CloneToMutable();
modified_custom_def.Stop.StopPrice = stop_price;
} else if (ord_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
modified_custom_def = order.Def.CustomDef.CloneToMutable();
double temp_price = order.price();
modified_custom_def.StopLimit.StopPrice = stop_price;
stop_price = temp_price;
}
return modified_custom_def;
}
public async Task<bool> update_order(OrderRecordImmutable order, double? new_price = null, uint? new_size = null, CustomOrderDefinitionImmutable? new_algo = null) {
if (order.working_quantity() <= 0) {
// order already filled to completion
@@ -373,16 +356,10 @@ namespace mbts.spark {
}
var size = new_size ?? order.quantity();
var price = new_price ?? order.price();
if (!(order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit)
&& size == order.quantity() && price == order.price() && !new_algo.HasValue) {
if (size == order.quantity() && price == order.price() && !new_algo.HasValue) {
// no parameters would be changed by this update
return false;
}
if ((order.order_type() == CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == CustomOrderDefinition.CustomOneofCase.StopLimit) &&
!order.stop_or_stop_limit_has_been_triggered() && !new_algo.HasValue) {
// note the ref price parameter, if this is a stop limit then we swap the fields
new_algo = _create_custom_def_for_stop_and_stop_limit(order, ref price);
}
_activity.update_order(order, price, size, new_algo.HasValue);
var result = await _service.update_order_async(new Update() {
OrderKey = order.Key,
+1 -2
View File
@@ -9,7 +9,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
<PlatformTarget>AnyCPU</PlatformTarget>
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
<ApplicationIcon>logo.ico</ApplicationIcon>
@@ -36,7 +35,7 @@
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
<PackageReference Include="IronPython" Version="2.7.7" />
<PackageReference Include="NAudio" Version="1.8.4" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
+3 -10
View File
@@ -525,11 +525,8 @@
<Setter.Value>
<DataTemplate>
<control:SparkMenuItem
Command="{Binding command}"
Style="{StaticResource SparkMenuButton}"
Icon="{Binding icon}"
Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
Collapsed="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type control:SparkMenu}}, Path=ShowText, Converter={StaticResource NotConverter}}"
ContentFlowDirection="{Binding Orientation, RelativeSource={RelativeSource AncestorType=control:SparkMenu}, Converter={x:Static control:SparkMenu.ItemContentFlowDirectionConverter}}"
>
<control:SparkMenuItem.Content>
<MultiBinding Converter="{x:Static ut:CommandHelper.DescriptionConverter}">
@@ -549,11 +546,7 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
>
<StackPanel
IsItemsHost="True"
Orientation="{Binding Orientation, RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
/>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</ScrollViewer>
</ControlTemplate>
@@ -566,4 +559,4 @@
<Setter Property="Command" Value="{Binding command}"/>
<Setter Property="Icon" Value="{Binding icon, Converter={StaticResource IconConverter}}"/>
</Style>
</ResourceDictionary>
</ResourceDictionary>
-37
View File
@@ -1,15 +1,10 @@
using bts.api;
using mbts;
using mbts.ui;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using static mbts.spark.SparkTags;
@@ -65,7 +60,6 @@ namespace bts.spark {
}
}
}
public ObservableCollection<SparkCommand> child_items { get; } = new ObservableCollection<SparkCommand>();
public abstract void execute(IBus bus);
public virtual bool canExecute() => true;
@@ -334,8 +328,6 @@ namespace bts.spark {
public class SparkCommandVM : mbts.ui.shell.ViewModelBase, ICommand {
readonly SparkCommand _cmd;
readonly IBus _bus;
readonly ISelectable _selectable_cmd;
object _first_child_group;
public SparkCommandVM(SparkCommand cmd, IBus bus) {
_cmd = cmd;
@@ -344,20 +336,6 @@ namespace bts.spark {
cmd.group_changed += () => RaisePropertyChanged(nameof(group));
cmd.show_in_toolbar_changed += () => RaisePropertyChanged(nameof(show_in_toolbar));
cmd.show_in_context_menu_changed += () => RaisePropertyChanged(nameof(show_in_context_menu));
if (cmd.child_items != null) {
ObservableAutoWrapper.connect(cmd.child_items, child_items, item => new SparkCommandVM(item, bus));
child_items_view = new ListCollectionView(child_items);
child_items_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(group)));
if (child_items_view.Groups is INotifyCollectionChanged child_groups_ncc) {
child_groups_ncc.Subscribe((s, e) => refresh_first_child_group());
refresh_first_child_group();
void refresh_first_child_group() =>
first_child_group = (child_items_view.Groups.FirstOrDefault() as CollectionViewGroup)?.Name;
}
}
_selectable_cmd = cmd as ISelectable;
if (_selectable_cmd != null)
_selectable_cmd.is_selected_changed += _ => RaisePropertyChanged(nameof(is_selected));
}
public string title => _cmd.title;
@@ -366,22 +344,7 @@ namespace bts.spark {
public ImageSource icon => _cmd.icon;
public bool show_in_toolbar => _cmd.show_in_toolbar;
public bool show_in_context_menu => _cmd.show_in_context_menu;
public bool is_selectable => _selectable_cmd != null;
public bool is_selected {
get => _selectable_cmd?.is_selected ?? false;
set {
if (_selectable_cmd != null)
_selectable_cmd.is_selected = value;
}
}
public ICommand command => this;
public ObservableCollection<SparkCommandVM> child_items { get; } = new ObservableCollection<SparkCommandVM>();
public ListCollectionView child_items_view { get; }
public object first_child_group {
get => _first_child_group;
set => Set(ref _first_child_group, value);
}
event EventHandler ICommand.CanExecuteChanged { add { _cmd.CanExecuteChanged += value; } remove { _cmd.CanExecuteChanged -= value; } }
bool ICommand.CanExecute(object parameter) => _cmd.canExecute();
@@ -57,9 +57,6 @@ namespace bts.spark {
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowPickOne, ShowLadder());
// Order Ticket
public static PartialCommand<IImmutableSet<uint>> ShowOrderTicketAutoLookup(KeyGesture key_gesture = null) =>
SparkCommand.create("Order Ticket", Icons.AddOrders, SparkTags.ShowOrderTicketAutoLookupTag, key_gesture);
public static PartialCommand<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicket(KeyGesture key_gesture = null) =>
SparkCommand.create("Order Ticket", Icons.AddOrders, SparkTags.ShowOrderTicketTag, key_gesture);
-1
View File
@@ -25,7 +25,6 @@ namespace mbts.spark {
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
public static ITag<IImmutableSet<uint>> ShowOrderTicketAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>(nameof(ShowOrderTicketAutoLookupTag));
public static ITag<SecurityMD> ShowOrderTicketFromSecurityTag = new SparkTag<SecurityMD>(nameof(ShowOrderTicketFromSecurityTag));
public static ITag<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicketTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size)>(nameof(ShowOrderTicketTag));
#endregion
@@ -37,8 +37,6 @@ namespace mbts.ui
}
public class DelegateCommand : DelegateCommand<object> {
public static DelegateCommand empty => new DelegateCommand(() => { });
public DelegateCommand(Action execute) : this(_ => execute()) { }
public DelegateCommand(Action execute, Func<bool> can_execute) : this(_ => execute(), _ => can_execute()) { }
public DelegateCommand(Action<object> execute) : base(execute) { }
@@ -65,10 +65,8 @@ namespace mbts.ui {
var cmd = new AsyncCommand(() => execute(param), () => can_execute, allow_concurrent_executions);
observer = new AnonymousObserver<T>(p => {
param = p;
if (can_execute != get_can_execute(param)) {
can_execute = !can_execute;
if (can_execute != get_can_execute(param))
cmd.RaiseCanExecuteChanged();
}
});
return cmd;
}
@@ -1,22 +0,0 @@
using System;
using System.Diagnostics;
namespace bts.utils.diagnostics {
class OpTimer : IDisposable {
readonly Stopwatch _stopwatch = new Stopwatch();
public TimeSpan elapsed => _stopwatch.Elapsed;
public static OpTimer Hot() {
var ot = new OpTimer();
ot.Start();
return ot;
}
public void Start() =>
_stopwatch.Start();
public void Dispose() =>
_stopwatch.Stop();
}
}
@@ -1,41 +0,0 @@
using bts.api.core;
using bts.api.orderman;
using mbts;
using mbts.spark;
using System;
using System.Linq;
using System.Reactive.Linq;
namespace bts.utils.diagnostics {
static class TestDataFactory {
#if DEBUG
public static IObservable<TableUpdate<OrderSecurity, uint>> OrdersWithRandomPIQ(IObservable<TableUpdate<OrderSecurity, uint>> orders, TimeSpan throttle) =>
Observable.Create<TableUpdate<OrderSecurity, uint>>(o => {
TableUpdate<OrderSecurity, uint> last_orders = null;
var output = TableUpdate<OrderSecurity, uint>.mk_empty(p => p.order.Key);
var piq_offsets = Enumerable.Range(1, 10).Select(i => (uint)i).ToArray();
var rand = new Random();
return Disposable.Create(
orders.Subscribe(new_orders => {
last_orders = new_orders;
notify();
}),
Observable.Interval(throttle).Subscribe(_ => notify()));
void notify() {
var bd = last_orders.to_builder();
foreach (var os in last_orders.state.Values) {
if (os.order.is_active()) {
var rpiq = os.piq ?? 0;
rpiq += piq_offsets[rand.Next(piq_offsets.Length)];
bd.add_or_update(os.order.Key, new OrderSecurity(os.security, os.order, rpiq, os.algo, os.meta));
}
}
last_orders = bd.to_table();
if (last_orders.has_changes())
o.OnNext(last_orders);
}
}).Cache();
#endif
}
}
@@ -22,7 +22,7 @@ namespace mbts.spark {
_notification = NotificationSettings.Default,
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings,
_time_and_sales_grid = bts.shell.time_and_sales.TimeAndSalesSettings.DefaultGridSettings,
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
_order_book_grid = bts.shell.orderbook.OrderBookStartupData.DefaultGrid
};
@@ -72,7 +72,7 @@ namespace mbts.spark {
public uint next_ladder_profile_id => _last_ladder_profile_id + 1;
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.time_and_sales.TimeAndSalesSettings.DefaultColumns.all));
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
@@ -123,15 +123,15 @@ namespace bts.utils {
_deferred_adds = new RefCountMap<T>(value_comparer);
_deferred_removes = new RefCountMap<T>(value_comparer);
_value_groups = new Dictionary<TKey, RefCountMap<T>>(key_comparer);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IReadOnlyCollection<T>>(_value_groups, rcm => rcm.Keys);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.Keys);
}
public event Action<KeyValuePair<TKey, IReadOnlyCollection<T>>?> common_value_group_changed;
public event Action<(IReadOnlyCollection<T> removed, IReadOnlyCollection<T> added)> common_values_changed;
public event Action<KeyValuePair<TKey, IEnumerable<T>>?> common_value_group_changed;
public event Action<(IEnumerable<T> removed, IEnumerable<T> added)> common_values_changed;
public IReadOnlyDictionary<TKey, IReadOnlyCollection<T>> distinct_value_groups { get; }
public KeyValuePair<TKey, IReadOnlyCollection<T>>? common_value_group =>
distinct_value_groups.TryGetSingle(out KeyValuePair<TKey, IReadOnlyCollection<T>> kv) ? kv : (KeyValuePair<TKey, IReadOnlyCollection<T>>?)null;
public IReadOnlyDictionary<TKey, IEnumerable<T>> distinct_value_groups { get; }
public KeyValuePair<TKey, IEnumerable<T>>? common_value_group =>
distinct_value_groups.TryGetSingle(out KeyValuePair<TKey, IEnumerable<T>> kv) ? kv : (KeyValuePair<TKey, IEnumerable<T>>?)null;
public void add(T value) {
if (_is_deferred) {
@@ -141,7 +141,7 @@ namespace bts.utils {
var k = _key(value);
if (_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
if (rcm.increment_adds_key(value) && _value_groups.Count == 1) {
common_values_changed?.Invoke((Array.Empty<T>(), new[] { value }));
common_values_changed?.Invoke((Enumerable.Empty<T>(), new[] { value }));
}
} else {
_value_groups.Add(k, rcm = new RefCountMap<T>(_value_comparer));
@@ -172,7 +172,7 @@ namespace bts.utils {
break;
default:
if (_value_groups.Count == 1)
common_values_changed?.Invoke((new[] { value }, Array.Empty<T>()));
common_values_changed?.Invoke((new[] { value }, Enumerable.Empty<T>()));
break;
}
}
@@ -190,7 +190,7 @@ namespace bts.utils {
switch (_value_groups.Count) {
case 1:
_value_groups.Clear();
common_values_changed?.Invoke((Array.Empty<T>(), Array.Empty<T>()));
common_values_changed?.Invoke((Enumerable.Empty<T>(), Enumerable.Empty<T>()));
common_value_group_changed?.Invoke(null);
break;
default:
@@ -230,8 +230,8 @@ namespace bts.utils {
common_value_group_changed?.Invoke(common_value_group);
} else if (last_fired_common_key is TKey k) {
common_values_changed?.Invoke((
CollectionHelper.wrap_mutable_as_read_only(_deferred_removes, kv => kv.Key),
CollectionHelper.wrap_mutable_as_read_only(_deferred_adds, kv => kv.Key)));
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
}
_deferred_adds = new RefCountMap<T>(_value_comparer);
_deferred_removes = new RefCountMap<T>(_value_comparer);
@@ -435,13 +435,7 @@ namespace bts.utils {
o.OnNext(monitor.try_get_common_value(out T result) ? selector(result) : default_value);
});
public static IObservable<TResult> SubscribeThrottled<T, TKey, TResult>(
this CommonValueGroupMonitor<T, TKey> monitor,
TimeSpan throttle,
Func<TKey, IReadOnlyCollection<T>, TResult> selector,
TResult default_value)
where TKey : struct =>
//
public static IObservable<TResult> SubscribeThrottled<T, TKey, TResult>(this CommonValueGroupMonitor<T, TKey> monitor, TimeSpan throttle, Func<TKey, IEnumerable<T>, TResult> selector, TResult default_value) where TKey : struct =>
Observable.Create<TResult>(o => {
bool busy = false, dirty = false;
notify();
@@ -451,8 +445,8 @@ namespace bts.utils {
monitor.common_values_changed -= on_values_changed;
monitor.common_value_group_changed -= on_value_group_changed;
});
void on_values_changed((IReadOnlyCollection<T>, IReadOnlyCollection<T>) _) => on_changed();
void on_value_group_changed(KeyValuePair<TKey, IReadOnlyCollection<T>>? _) => on_changed();
void on_values_changed((IEnumerable<T>, IEnumerable<T>) _) => on_changed();
void on_value_group_changed(KeyValuePair<TKey, IEnumerable<T>>? _) => on_changed();
async void on_changed() {
if (busy) {
dirty = true;
@@ -467,7 +461,7 @@ namespace bts.utils {
busy = false;
}
void notify() =>
o.OnNext(monitor.common_value_group is KeyValuePair<TKey, IReadOnlyCollection<T>> kv ? selector(kv.Key, kv.Value) : default_value);
o.OnNext(monitor.common_value_group is KeyValuePair<TKey, IEnumerable<T>> kv ? selector(kv.Key, kv.Value) : default_value);
});
public static IObservable<T?> SubscribeThrottledNullable<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) where T : struct =>
@@ -258,7 +258,7 @@ namespace bts.utils {
_property_monitors[pm_pos++] = (pad.prop_info.Name, initialize, refresh);
}
}
ArrayHelper.trim(ref _property_monitors, pm_pos);
ArrayHelper.trim(ref _property_monitors, _property_monitors.Length - pm_pos);
}
public IDisposable detect_changes(T target) {
+2 -2
View File
@@ -61,8 +61,8 @@
<SolidColorBrush x:Key="CheckBox.Pressed.BorderBrush" Color="{StaticResource CheckBox.Pressed.BorderColor}"/>
<!--ComboBox-->
<Color x:Key="ComboBoxBackgroundLightColor">#FFF0F0F0</Color>
<Color x:Key="ComboBoxBackgroundMediumColor">#FFE5E5E5</Color>
<Color x:Key="ComboBoxBackgroundLightColor">#FFE5E5E5</Color>
<Color x:Key="ComboBoxBackgroundMediumColor">#FFF0F0F0</Color>
<Color x:Key="ComboBoxBorderLightColor">#FFACACAC</Color>
<Color x:Key="ComboBoxBorderMediumColor">#FF7EB4EA</Color>
@@ -7,7 +7,7 @@ using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.controls.adorners {
public class DragRectAdorner : Adorner, IDisposable, DIYGrid.ISelectionHighlightListener {
public class DragRectAdorner : Adorner, IDisposable {
Brush _fill = DIYGrid.DefaultSelectionHighlight;
Pen _stroke = DIYGrid.DefaultSelectionStroke;
readonly ScrollViewerTuple _scroll_viewer_tup;
@@ -110,12 +110,6 @@ namespace mbts.ui.controls.adorners {
}
}
public void on_selection_fill(SolidColorBrush selection_fill) {
Fill = selection_fill;
Stroke = new Pen(selection_fill.Color.to_solid_brush(), 1);
InvalidateVisual();
}
class ScrollViewerTuple {
public readonly ScrollViewer scroll_viewer;
ScrollContentPresenter _presenter;
@@ -126,4 +120,4 @@ namespace mbts.ui.controls.adorners {
public ScrollContentPresenter presenter => _presenter ?? (_presenter = scroll_viewer.descendants_of_type<ScrollContentPresenter>().FirstOrDefault());
}
}
}
}
@@ -1,12 +1,13 @@
using mbts.ui.extensions;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using bts.utils;
using mbts.ui.extensions;
namespace mbts.ui.controls.adorners {
public class RowSelectionAdorner : Adorner, DIYGrid.ISelectionHighlightListener, IDisposable {
@@ -54,24 +55,16 @@ namespace mbts.ui.controls.adorners {
_diy_scp = _diy_rp.descendants_of_type<ScrollContentPresenter>().empty_if_null().FirstOrDefault();
// We actually don't need mouse up/down or key up/down when we can just respond to selection changes
_disposables.Add(_diy_rp.SubscribeSelectionChanged((s, e) => InvalidateVisual()));
_disposables.Add(((System.Windows.Controls.Primitives.Selector)_diy_rp).SubscribeSelectionChanged((s, e) => InvalidateVisual()));
// so that we update the highlight as the grid scrolls
_disposables.Add(_diy_scroll.SubscribeScrollChanged((s, e) => InvalidateVisual()));
// listen to ItemContainerGenerator changes
_diy_rp.ItemContainerGenerator.ItemsChanged += on_items_changed;
_diy_rp.ItemContainerGenerator.StatusChanged += on_status_changed;
_disposables.Add(Disposable.Create(() => {
_diy_rp.ItemContainerGenerator.ItemsChanged -= on_items_changed;
_diy_rp.ItemContainerGenerator.StatusChanged -= on_status_changed;
}));
void on_items_changed(object sender, ItemsChangedEventArgs e) => InvalidateVisual();
void on_status_changed(object sender, EventArgs e) => InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
if (AdornedElement is ListBox lb && lb.SelectedItems != null && lb.SelectedItems.Count > 0) {
foreach (var lbi in lb.SelectedItems) {
foreach(var lbi in lb.SelectedItems) {
if (_diy_rp.ItemContainerGenerator.ContainerFromItem(lbi) is FrameworkElement row && row.IsVisible) {
var top_left = lb.GetChildTopLeft(row);
var top_is_within_bounds = top_left.Y >= 0;
@@ -79,7 +72,7 @@ namespace mbts.ui.controls.adorners {
// Note that the top of the row can still technically be visible but we wont show
// the adorner rectangle b/c the bottom of the row will be obscured by the scrollbar
var bottom_is_within_bounds = lb.GetChildBounds(row).BottomRight.Y <= _diy_scroll.ActualHeight;
if (top_is_within_bounds && bottom_is_within_bounds) {
if (top_is_within_bounds && bottom_is_within_bounds) {
// we use the scroll content presenter in the X point calculation instead of the row
// because of the OB which has a right side panel that takes up some space over
// row width
@@ -1,83 +0,0 @@
using bts.utils;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace mbts.ui.controls.adorners {
class SimpleOverlayAdorner : Adorner {
static readonly IComparer<(int, UIElement, Visual)> _id_comparer =
Comparer<int>.Default.wrap<int, (int, UIElement, Visual)>(tup => tup.Item1);
readonly List<(int id, UIElement target, Visual content)> _cache =
new List<(int id, UIElement target, Visual content)>();
int _child_id_counter;
public SimpleOverlayAdorner(UIElement adorned_element)
: base(adorned_element) { }
protected override int VisualChildrenCount => _cache.Count;
public IDisposable OverlayVisual(UIElement target, Visual visual) {
int id = _child_id_counter++;
_cache.Add((id, target, visual));
AddVisualChild(visual);
if (_cache.Count == 1)
this.attach();
// don't the caller prevent children from being garbage collected (returned idisposable's lifetime may exceed that of this adorner).
var weak_cache_ref = new WeakReference<List<(int, UIElement, Visual)>>(_cache);
return Disposable.Create(() => {
if (weak_cache_ref.TryGetTarget(out var loc_cache)) {
int pos = loc_cache.BinarySearch((id, null, null), _id_comparer);
if (pos >= 0) {
RemoveVisualChild(loc_cache[pos].Item3);
loc_cache.RemoveAt(pos);
}
if (loc_cache.Count == 0)
this.detach();
}
});
}
protected override Size ArrangeOverride(Size final_size) {
double xmin = final_size.Width, xmax = 0, ymin = final_size.Height, ymax = 0;
var final_rect = new Rect(new Point(0, 0), final_size);
foreach (var (id, target, content) in _cache) {
if (content is UIElement el) {
var tl = target.TranslatePoint(new Point(0, 0), AdornedElement);
var br = new Point(tl.X + target.RenderSize.Width, tl.Y + target.RenderSize.Height);
var rect = new Rect(tl, br);
rect.Intersect(final_rect);
if (!rect.Size.IsEmpty) {
el.Arrange(rect);
if (rect.Right > xmax)
xmax = rect.Right;
if (rect.Bottom > ymax)
ymax = rect.Bottom;
if (rect.Left < xmin)
xmin = rect.Left;
if (rect.Top < ymin)
ymin = rect.Top;
}
}
}
if (xmin < xmax && ymin < ymax)
return new Size(xmax - xmin, ymax - ymin);
return new Size(0, 0);
}
protected override Visual GetVisualChild(int index) =>
index >= 0 && index < _cache.Count ? _cache[index].content : null;
protected override Size MeasureOverride(Size constraint) {
foreach (var (id, target, content) in _cache) {
if (content is UIElement el)
el.Measure(target.DesiredSize);
}
return new Size(
Math.Min(constraint.Width, AdornedElement.DesiredSize.Width),
Math.Min(constraint.Height, AdornedElement.DesiredSize.Height));
}
}
}
@@ -1,7 +1,7 @@
using mbts.ui.converters;
using mbts.ui.dpi;
using mbts.ui.shell;
using mbts.ui.utility.text;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -83,39 +83,79 @@ namespace mbts.ui.controls {
}
}
public static Rect? get_clip(Size content_size, double row_height, double left, double unclipped_left, double width) =>
content_size == null ? get_clip(0, 0, row_height, left, unclipped_left, width) :
get_clip(content_size.Width, content_size.Height, row_height, left, unclipped_left, width);
public static Rect? get_clip(double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
if (content_width > width || unclipped_left < left)
return new Rect(left, 0, width - left + unclipped_left, row_height);
return null;
}
public static IDisposable draw_with_clip(DrawingContext dc, double raw_content_width, double raw_content_height, Thickness padding, double row_height, ColumnGeometry position) =>
draw_with_clip(dc,
raw_content_width + padding.Left + padding.Right,
raw_content_height + padding.Top + padding.Bottom,
row_height, position.left, position.unclipped_left, position.width);
public static IDisposable draw_with_clip(DrawingContext dc, double content_width, double content_height, double row_height, double left, double unclipped_left, double width) {
var clip = get_clip(content_width, content_height, row_height, left, unclipped_left, width);
if (clip.HasValue) {
dc.PushClip(new RectangleGeometry(clip.Value));
return Disposable.Create(dc.Pop);
} else return Disposable.Create(() => { });
}
public static void draw_text(
DrawingContext dc,
GlyphContext glyph_context,
double font_size,
string text,
ColumnGeometry position,
double row_height,
Brush foreground,
Brush background,
HorizontalAlignment x_align,
VerticalAlignment y_align,
Thickness padding,
bool round_to_layout = true) {
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding) {
//
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding, round_to_layout);
var gwidth = gr == null ? 0 : gr.ComputeAlignmentBox().Width;
draw_text(dc, gr, gwidth, font_size, position, padding, row_height, foreground, background);
if (!string.IsNullOrEmpty(text)) {
var gr = glyph_context.get_glyph_run(text, font_size, position.unclipped_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, gr, gr.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
}
public static void draw_text(DrawingContext dc, GlyphRun glyph_run, double glyph_width, double font_size, ColumnGeometry position, Thickness padding, double row_height, Brush foreground, Brush background) {
if (glyph_run == null && background == null)
return;
var rect = position.to_rect(row_height);
if (background != null) {
var guidelines = rect.AntiAliasGuidelines(0);
dc.PushGuidelineSet(guidelines);
dc.DrawRectangle(background, null, rect);
dc.Pop();
}
if (glyph_run != null) {
dc.PushClip(new RectangleGeometry(rect));
using (draw_with_clip(dc, glyph_width, font_size, padding, row_height, position)) {
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
dc.DrawGlyphRun(foreground, glyph_run);
dc.Pop();
}
}
public static void draw_text(
DrawingContext dc, GlyphContext glyph_context, double font_size, string text, ColumnGeometry position, double row_height,
Brush foreground, Brush background, HorizontalAlignment x_align, VerticalAlignment y_align, Thickness padding, IEnumerable<CellAnimation> trigger_actions) {
//
using (position.clip_draw(dc, row_height)) {
double adjusted_left = position.unclipped_left;
double available_width = position.width;
if (background != null)
dc.DrawRectangle(background, null, new Rect(position.left, 0, position.width, row_height));
// store any trigger-glyphs in a list, to be drawn after iterating through all triggers (must draw background first, so glyphs will not be covered up).
List<(Brush brush, GlyphRun glyph)> glyphs = new List<(Brush brush, GlyphRun glyph)>(1);
if (trigger_actions != null) {
foreach (CellAnimation ta in trigger_actions) {
switch (ta.animation_type_case) {
case CellAnimation.AnimationTypeOneofCase.BackgroundAnimation:
dc.DrawRectangle(ta.background_animation.brush, ta.background_animation.pen, new Rect(position.left, 0, position.width, row_height));
break;
case CellAnimation.AnimationTypeOneofCase.SymbolAnimation:
var (symbol_text, is_sideways) = _get_symbol_animation_args(ta.symbol_animation);
GlyphRun symbol_glyph = glyph_context.get_glyph_run(symbol_text, font_size, position.width, row_height, HorizontalAlignment.Left, adjusted_left, y_align, 0, is_sideways: is_sideways);
glyphs.Add((ta.symbol_animation.brush, symbol_glyph));
adjusted_left += symbol_glyph.AdvanceWidths.Sum();
break;
}
}
}
foreach (var (brush, glyph) in glyphs) {
dc.DrawGlyphRun(brush, glyph);
}
GlyphRun main_content = glyph_context.get_glyph_run(text, font_size, adjusted_left, 0, position.right, row_height, x_align, y_align, padding);
draw_text(dc, main_content, main_content.AdvanceWidths.Sum(), font_size, position, padding, row_height, foreground, background);
}
}
@@ -125,6 +165,14 @@ namespace mbts.ui.controls {
public static void change_position(FrameworkElement cell_view, double row_height, ColumnGeometry? position) {
if (position.HasValue) {
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Visible);
var clip = get_clip(cell_view.DesiredSize, row_height, 0, position.Value.unclipped_left - position.Value.left, position.Value.width);
var el_clip = cell_view.Clip as RectangleGeometry;
if (clip.HasValue) {
if (el_clip == null || !el_clip.Rect.Equals(clip.Value))
cell_view.Clip = new RectangleGeometry(clip.Value);
} else if (cell_view.Clip != null) {
cell_view.Clip = null;
}
} else {
WPFHelper.set_if_changed(cell_view, VisibilityProperty, Visibility.Collapsed);
}
@@ -140,4 +188,4 @@ namespace mbts.ui.controls {
}
}
}
}
}
@@ -1,25 +1,16 @@
using mbts.spark;
using mbts.ui.dpi;
using mbts.ui.utilities;
using mbts.ui.utilities;
using mbts.ui.utility.text;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.ui.controls {
enum HyperlinkCellClickMode {
Text,
Anywhere
}
class HyperlinkCell : FrameworkElement, ICommandSource {
// these fields each represent properties that are accessed frequently,
// they are stored locally to reduce repetative DependencyProperty lookups
@@ -27,35 +18,10 @@ namespace mbts.ui.controls {
Brush _background, _foreground_backing, _border_brush_backing;
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
GlyphRun _glyph_run => _gcr.glyph_run;
bool
_is_mouse_over_text,
_is_mouse_over_click_area_backing,
_can_execute,
_snaps_to_device_pixels;
bool _is_mouse_over_text, _can_execute;
Pen _underline_pen_backing, _border_stroke_pen_backing;
GlyphContentRect _gcr = new GlyphContentRect {
// add 2 pixels to account for underline (1 for space, 1 for the line itself)
content_height_offset = HighDpiExtensions.pixel_unit * 2
};
IDisposable _mouse_over_click_area_op;
GlyphContentRect _gcr = new GlyphContentRect();
bool _is_mouse_over_click_area {
get => _is_mouse_over_click_area_backing;
set {
if (_is_mouse_over_click_area_backing != value) {
Disposable.dispose(ref _mouse_over_click_area_op);
_is_mouse_over_click_area_backing = value;
if (value) {
_mouse_over_click_area_op = WorkLoop.Start(
$"Evaluate {nameof(HyperlinkCell)} {nameof(IsMouseOverClickArea)}",
TimeSpan.FromMilliseconds(100),
DispatcherPriority.Input,
_on_mouse_changed,
WorkLoop.default_excessive_cycle_ratio);
}
}
}
}
Brush _foreground {
get => _foreground_backing;
set {
@@ -71,23 +37,13 @@ namespace mbts.ui.controls {
}
}
Thickness _border_thickness {
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
get => _gcr.border_thickness;
set {
_gcr.border_thickness = new Thickness(value.Left);
_border_stroke_pen_backing = null;
}
}
Pen _underline_pen {
get {
if (_underline_pen_backing == null) {
var fg = _foreground;
if (fg != null) {
_underline_pen_backing = new Pen(fg, _snaps_to_device_pixels ? HighDpiExtensions.pixel_unit : 1);
}
}
return _underline_pen_backing;
}
}
Pen _underline_pen => _underline_pen_backing ?? (_foreground == null ? null : (_underline_pen_backing = new Pen(_foreground, 1)));
Pen _border_stroke_pen =>
// border_thickness.Left is used as the uniform border thickness. This can be changed if need be.
_border_stroke_pen_backing == null && _border_brush != null && _border_thickness.Left > 0 ?
@@ -95,8 +51,6 @@ namespace mbts.ui.controls {
_border_stroke_pen_backing;
static HyperlinkCell() {
IsMouseOverClickAreaProperty = IsMouseOverClickAreaPropertyKey.DependencyProperty;
DefaultStyleKeyProperty.OverrideMetadata(typeof(HyperlinkCell), new FrameworkPropertyMetadata(typeof(HyperlinkCell)));
WPFHelper.override_metadata<HorizontalAlignment, HyperlinkCell>(
HorizontalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
@@ -110,11 +64,6 @@ namespace mbts.ui.controls {
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.width = v,
double.NaN);
WPFHelper.override_metadata<bool, HyperlinkCell>(
SnapsToDevicePixelsProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._snaps_to_device_pixels = v,
true);
}
public HyperlinkCell() {
@@ -124,7 +73,7 @@ namespace mbts.ui.controls {
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.padding = _underline_padding(Padding);
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
@@ -137,39 +86,8 @@ namespace mbts.ui.controls {
if (Text is string str && !string.IsNullOrEmpty(str))
_gcr.text_lines = new[] { str };
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
_snaps_to_device_pixels = SnapsToDevicePixels;
}
#region IsMouseOverClickArea
static readonly DependencyPropertyKey IsMouseOverClickAreaPropertyKey =
DependencyProperty.RegisterReadOnly(
"IsMouseOverClickArea",
typeof(bool),
typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender,
OnIsMouseOverClickAreaChanged,
CoerceIsMouseOverClickArea));
public static readonly DependencyProperty IsMouseOverClickAreaProperty;
static void OnIsMouseOverClickAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is HyperlinkCell hlc)
hlc._is_mouse_over_click_area = e.NewValue is bool b && b;
}
static object CoerceIsMouseOverClickArea(DependencyObject d, object baseValue) =>
d is HyperlinkCell hlc ?
hlc._is_mouse_over_text || (hlc.IsMouseOver && hlc.HyperlinkClickMode == HyperlinkCellClickMode.Anywhere) :
baseValue;
public bool IsMouseOverClickArea {
get => (bool)GetValue(IsMouseOverClickAreaProperty);
private set => SetValue(IsMouseOverClickAreaPropertyKey, value);
}
#endregion
#region BorderThickness
public static readonly DependencyProperty BorderThicknessProperty =
Control.BorderThicknessProperty.AddOwner(typeof(HyperlinkCell),
@@ -204,7 +122,6 @@ namespace mbts.ui.controls {
Control.HorizontalContentAlignmentProperty.AddOwner(typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
HorizontalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((HyperlinkCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue));
@@ -218,8 +135,7 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(HyperlinkCell),
new FrameworkPropertyMetadata(
VerticalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((HyperlinkCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
public VerticalAlignment VerticalContentAlignment {
@@ -299,8 +215,7 @@ namespace mbts.ui.controls {
nameof(Padding),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(p, v) => p._gcr.padding = v,
DIYGrid.DefaultCellPadding);
(p, v) => p._gcr.padding = _underline_padding(v), DIYGrid.DefaultCellPadding);
public Thickness Padding { get => (Thickness)GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); }
#endregion
@@ -316,18 +231,6 @@ namespace mbts.ui.controls {
public string Text { get => GetValue(TextProperty) as string; set => SetValue(TextProperty, value); }
#endregion
#region HyperlinkClickMode
public static readonly DependencyProperty HyperlinkClickModeProperty =
WPFHelper.create_dp<HyperlinkCellClickMode, HyperlinkCell>(
nameof(HyperlinkClickMode),
(o, v) => o.CoerceValue(IsMouseOverClickAreaProperty));
public HyperlinkCellClickMode HyperlinkClickMode {
get => (HyperlinkCellClickMode)GetValue(HyperlinkClickModeProperty);
set => SetValue(HyperlinkClickModeProperty, value);
}
#endregion
#region ICommandSource
#region Command
@@ -394,8 +297,8 @@ namespace mbts.ui.controls {
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
_on_mouse_changed(e);
if (_is_mouse_over_click_area) {
_check_mouse_over_text(e);
if (_is_mouse_over_text) {
// Hyperlink should take focus when left mouse button is clicked on it
// This is consistent with all ButtonBase controls and current Win32 behavior
Focus();
@@ -405,8 +308,6 @@ namespace mbts.ui.controls {
if (e.ButtonState == MouseButtonState.Pressed) {
// Capture the mouse, and make sure we got it.
CaptureMouse();
} else {
ReleaseMouseCapture();
}
e.Handled = true;
}
@@ -414,25 +315,23 @@ namespace mbts.ui.controls {
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) {
base.OnMouseLeftButtonUp(e);
_on_mouse_changed(e);
if (IsMouseCaptured) {
_check_mouse_over_text(e);
if (IsMouseCaptured)
ReleaseMouseCapture();
if (_can_execute)
CommandHelper.ExecuteCommandSource(this);
_on_mouse_changed(e);
e.Handled = true;
}
if (_can_execute && _is_mouse_over_text)
CommandHelper.ExecuteCommandSource(this);
e.Handled = true;
}
#endregion
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) => _on_mouse_changed();
protected override void OnPreviewMouseMove(MouseEventArgs e) {
_on_mouse_changed(e);
e.Handled = IsMouseCaptured;
protected override void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e) {
if (_is_mouse_over_text != IsMouseDirectlyOver)
_toggle_mouse_over_text();
}
protected override void OnMouseEnter(MouseEventArgs e) => _on_mouse_changed(e);
protected override void OnMouseLeave(MouseEventArgs e) => _on_mouse_changed(e);
protected override void OnPreviewMouseMove(MouseEventArgs e) => _check_mouse_over_text(e);
protected override void OnMouseEnter(MouseEventArgs e) => _check_mouse_over_text(e);
protected override void OnMouseLeave(MouseEventArgs e) => _check_mouse_over_text(e);
protected override Size ArrangeOverride(Size finalSize) => finalSize;
@@ -448,32 +347,45 @@ namespace mbts.ui.controls {
var rs = RenderSize;
var rect = new Rect(0, 0, rs.Width, rs.Height);
var bg = Background;
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
var gr = _glyph_run;
if (gr != null) {
if (bg != null) {
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
}
if (_glyph_run != null) {
drawingContext.PushClip(new RectangleGeometry(rect));
drawingContext.DrawGlyphRun(_foreground, gr);
if (IsMouseOverClickArea) {
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
if (_is_mouse_over_text) {
// draw underline
var ul_pen = _underline_pen;
if (ul_pen != null) {
var line_start = new Point(gr.BaselineOrigin.X, gr.BaselineOrigin.Y + 2 * ul_pen.Thickness);
drawingContext.DrawHorizontalLine(ul_pen, line_start, gr.AdvanceWidths.Sum(), _snaps_to_device_pixels);
}
double x = _gcr.glyph_run.BaselineOrigin.X;
double y = _gcr.glyph_run.BaselineOrigin.Y + 2;
drawingContext.DrawLine(_underline_pen, new Point(x, y), new Point(x + _gcr.glyph_run.AdvanceWidths.Sum(), y));
}
drawingContext.Pop();
}
}
void _on_mouse_changed() =>
_on_mouse_changed(Mouse.GetPosition(this));
static Thickness _underline_padding(Thickness padding) =>
new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom + 2);
void _on_mouse_changed(MouseEventArgs e) =>
_on_mouse_changed(e.GetPosition(this));
void _check_mouse_over_text(MouseEventArgs e) {
var pt = e.GetPosition(this);
var orig = _gcr.content_area;
var exp = orig.Expanded(1);
if (_is_mouse_over_text != exp.Contains(pt)) {
bts.Log.assert(_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)));
_toggle_mouse_over_text();
return;
}
if (_is_mouse_over_text != _gcr.content_area.Expanded(1).Contains(e.GetPosition(this)))
_toggle_mouse_over_text();
}
void _on_mouse_changed(Point pt) {
_is_mouse_over_text = _gcr.content_area.Expanded(1).Contains(pt);
CoerceValue(IsMouseOverClickAreaProperty);
void _toggle_mouse_over_text() {
_is_mouse_over_text = !_is_mouse_over_text;
if (_is_mouse_over_text)
SetCurrentValue(CursorProperty, Cursors.Hand);
else
ClearValue(CursorProperty);
InvalidateVisual();
}
}
}
@@ -1,5 +1,5 @@
using mbts.ui.dpi;
using mbts.ui.utility.text;
using mbts.ui.utility.text;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -15,7 +15,6 @@ namespace mbts.ui.controls {
Brush _background, _foreground, _border_brush_backing;
double _glyph_width => _gcr.glyph_run.AdvanceWidths.Sum();
GlyphRun _glyph_run => _gcr.glyph_run;
bool _snaps_to_device_pixels;
Pen _border_stroke_pen_backing;
GlyphContentRect _gcr = new GlyphContentRect();
@@ -27,7 +26,7 @@ namespace mbts.ui.controls {
}
}
Thickness _border_thickness {
get => _snaps_to_device_pixels ? _gcr.border_thickness.RoundToLayout() : _gcr.border_thickness;
get => _gcr.border_thickness;
set {
_gcr.border_thickness = new Thickness(value.Left);
_border_stroke_pen_backing = null;
@@ -53,11 +52,6 @@ namespace mbts.ui.controls {
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._gcr.width = v,
double.NaN);
WPFHelper.override_metadata<bool, TextCell>(
SnapsToDevicePixelsProperty,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._snaps_to_device_pixels = v,
true);
}
public TextCell() {
@@ -66,7 +60,7 @@ namespace mbts.ui.controls {
_gcr.max_width = MaxWidth;
_gcr.min_height = MinHeight;
_gcr.height = Height;
_gcr.max_height = MaxHeight;
_gcr.max_height = Height;
_gcr.padding = Padding;
_gcr.border_thickness = BorderThickness;
_gcr.horizontal_alignment = HorizontalAlignment;
@@ -80,7 +74,6 @@ namespace mbts.ui.controls {
if (Text is string str && !string.IsNullOrEmpty(str))
_gcr.text_lines = new[] { str };
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
_snaps_to_device_pixels = SnapsToDevicePixels;
}
#region BorderThickness
@@ -117,9 +110,15 @@ namespace mbts.ui.controls {
Control.HorizontalContentAlignmentProperty.AddOwner(typeof(TextCell),
new FrameworkPropertyMetadata(
HorizontalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue));
(d, e) => {
if (e.OldValue is HorizontalAlignment oldha) {
if (oldha == HorizontalAlignment.Right) {
}
}
((TextCell)d)._gcr.horizontal_content_alignment = (HorizontalAlignment)e.NewValue;
}));
public HorizontalAlignment HorizontalContentAlignment {
get => (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
@@ -131,8 +130,7 @@ namespace mbts.ui.controls {
public static readonly DependencyProperty VerticalContentAlignmentProperty = Control.VerticalContentAlignmentProperty.AddOwner(typeof(TextCell),
new FrameworkPropertyMetadata(
VerticalAlignment.Center,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender,
FrameworkPropertyMetadataOptions.AffectsArrange,
(d, e) => ((TextCell)d)._gcr.vertical_content_alignment = (VerticalAlignment)e.NewValue));
public VerticalAlignment VerticalContentAlignment {
@@ -239,17 +237,35 @@ namespace mbts.ui.controls {
return _gcr.layout_size;
}
double bl_x = 0;
protected override void OnRender(DrawingContext drawingContext) {
var rs = RenderSize;
var rect = new Rect(0, 0, rs.Width, rs.Height);
var bg = Background;
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect, _snaps_to_device_pixels);
var gr = _glyph_run;
if (gr != null) {
if (bg != null) {
drawingContext.DrawRectangle(bg, _border_stroke_pen, rect);
}
if (_glyph_run != null) {
var new_x = _glyph_run.BaselineOrigin.X;
if (Math.Abs(bl_x - new_x) > 5) {
if (new_x < bl_x) {
}
}
if (Text == "-3,375.000") {
}
bl_x = new_x;
drawingContext.PushClip(new RectangleGeometry(rect));
drawingContext.DrawGlyphRun(_foreground, gr);
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
drawingContext.Pop();
}
if (bg != null) {
var bgo = DIYGrid.GetBackgroundOverlay(this);
if (bgo != null)
drawingContext.DrawRectangle(bgo, null, rect);
}
}
}
}
}
@@ -36,7 +36,6 @@ namespace mbts.ui.controls {
Filled = 56,
Symbol = 78,
Time = 112,
TimeOfDay = 64,
TimeInForce = 47,
User = 53,
Aggressor = 80,
@@ -224,7 +223,7 @@ namespace mbts.ui.controls {
}
// To include extra data with your column type you are encouraged to derive from DIYGridColumn
public abstract class ColumnBase : GridViewColumn, IValueFactory<GridColumnSettings>, IDIYGridColumn, ISortableDIYGridColumn, INotifyPropertyChanged {
public abstract class ColumnBase : GridViewColumn, IValueFactory<GridColumnSettings>, INotifyPropertyChanged {
//readonly ExternalProperty<FontWeight?> _font_weigh
GridColumnSettings _settings;
FontWeight _fallback_font_weight;
@@ -363,7 +362,7 @@ namespace mbts.ui.controls {
id.ToString();
}
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey>, IDIYGridColumn<TColKey>, ISortableDIYGridColumn<TColKey>, IGridColumn {
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey>, IGridColumn {
TColKey _key;
bool _key_initialized;
// need a default constructor to support declaration in xaml
@@ -381,7 +380,6 @@ namespace mbts.ui.controls {
on_key_initialized(value);
}
}
TColKey IDIYGridColumn<TColKey>.id => key;
protected virtual void on_key_initialized(TColKey key) { }
}
@@ -1,40 +1,31 @@
using System;
using System.Windows;
using System.Windows.Media;
namespace mbts.ui.controls {
// Column types are nested inside the DIYGrid
partial class DIYGrid {
public readonly struct ColumnGeometry {
public readonly ColumnBase column;
public readonly DIYGridSuperHeader super_column;
public readonly double
left,
// If the column was unclipped
unclipped_left,
right;
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double right)
: this(column, super_column, left, left, right) { }
public ColumnGeometry(ColumnBase column, DIYGridSuperHeader super_column, double left, double unclipped_left, double right) {
this.column = column;
this.super_column = super_column;
this.left = left;
this.unclipped_left = unclipped_left;
this.right = right;
}
public double width => right - left;
public struct ColumnGeometry {
public ColumnBase column;
public DIYGridSuperHeader super_column;
public double unclipped_left; /* If the column was unclipped */
public double left;
public double width { get { return column.ActualWidth; } }
public double right { get { return unclipped_left + width; } }
public bool is_partially_obscured { get { return unclipped_left < left; } }
public double visible_width => Math.Max(0, width - left + unclipped_left);
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public Rect to_rect(double row_height) =>
new Rect(left, 0, width, row_height);
public override string ToString() {
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
}
public string group { get { return super_column == null ? "" : super_column.FullHeader; } }
public RectangleGeometry get_clip(double row_height) =>
new RectangleGeometry(new Rect(left, 0, width - left + unclipped_left, row_height));
public IDisposable clip_draw(DrawingContext dc, double row_height) {
dc.PushClip(get_clip(row_height));
return Disposable.Create(dc.Pop);
}
}
}
}
@@ -1,138 +0,0 @@
using bts.utils;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.ui {
class ColumnSortManager {
readonly List<ISortableDIYGridColumn> _visible_sorting = new List<ISortableDIYGridColumn>();
readonly IList<ISortableDIYGridColumn> _columns;
readonly string _primary_key;
public ColumnSortManager(IList<ISortableDIYGridColumn> columns, string primary_key) {
_columns = columns;
_primary_key = primary_key;
}
public event Action active_sorting_changed;
public IEnumerable<ISortableDIYGridColumn> ActiveSorting {
get {
bool has_primary = false;
foreach (var col in _visible_sorting) {
if (!has_primary && col.id == _primary_key) {
has_primary = true;
}
yield return col;
}
if (!has_primary) {
var pcol = _find_column(_primary_key);
if (pcol != null)
yield return pcol;
}
}
}
public IEnumerable<ISortableDIYGridColumn> VisibleSorting =>
_visible_sorting.AsEnumerable();
public IEnumerable<SortingCriteria> SortingCriteria {
get {
foreach (var col in _visible_sorting) {
var dir = col.SortDirection;
if (dir.HasValue)
yield return new SortingCriteria(col.id, dir.Value == ListSortDirection.Ascending);
}
}
set {
var snapshot = ActiveSorting.ToList();
_clear();
if (value != null) {
foreach (var sc in value) {
var col = _find_column(sc.col_name);
if (col != null) {
col.SortDirection = sc.direction();
_visible_sorting.Add(col);
}
}
}
if (!ActiveSorting.SequenceEqual(snapshot))
active_sorting_changed?.Invoke();
}
}
public void OnColumnClick(string id, bool is_shift_pressed) =>
OnColumnClick(_find_column(id), is_shift_pressed);
public void OnColumnClick(ISortableDIYGridColumn col, bool is_shift_pressed) {
if (col == null)
return;
var old_dir = col.SortDirection;
if (!is_shift_pressed) {
_clear();
col.SortDirection = _reverse(old_dir);
_visible_sorting.Add(col);
} else {
col.SortDirection = _reverse(old_dir);
if (!old_dir.HasValue)
_visible_sorting.Add(col);
}
active_sorting_changed?.Invoke();
}
static ListSortDirection _reverse(ListSortDirection? dir) =>
dir.HasValue && dir.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
void _clear() {
_visible_sorting.Clear();
foreach (var c in _columns)
c.SortDirection = null;
}
ISortableDIYGridColumn _find_column(string id) =>
_columns.FirstOrDefault(c => c.id == id);
}
static class ColumnSortManagerExtensions {
public static IComparer<TRow> BuildComparer<TRow>(
this ColumnSortManager sort_manager,
Func<string, IComparer<TRow>> get_column_comparer,
IComparer<TRow> primary_key_comparer = null) {
//
var bd = new ComparerBuilder<TRow>();
foreach (var col in sort_manager.ActiveSorting) {
var dir = col.SortDirection;
if (dir.HasValue) {
var comp = get_column_comparer(col.id);
if (comp != null)
bd.add(_reverse_if_descending(comp, dir.Value));
}
}
if (primary_key_comparer != null)
bd.add(primary_key_comparer);
return bd.to_comparer();
}
public static IComparer<TRow> BuildComparer<TColumnKey, TRow>(
this ColumnSortManager sort_manager,
Func<TColumnKey, IComparer<TRow>> get_column_comparer) {
//
var bd = new ComparerBuilder<TRow>();
foreach (var col in sort_manager.ActiveSorting.OfType<ISortableDIYGridColumn<TColumnKey>>()) {
var dir = col.SortDirection;
if (dir.HasValue) {
var comp = get_column_comparer(col.id);
if (comp != null)
bd.add(_reverse_if_descending(comp, dir.Value));
}
}
return bd.to_comparer();
}
static IComparer<T> _reverse_if_descending<T>(IComparer<T> comp, ListSortDirection direction) =>
direction == ListSortDirection.Descending ? comp.reverse() : comp;
}
}
@@ -1,8 +1,9 @@
using mbts.spark.filters;
using mbts.ui.converters;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
@@ -21,33 +22,19 @@ namespace mbts.ui.controls {
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
public HyperlinkCellClickMode HyperlinkClickMode { get; set; }
public override FrameworkElement view_factory() {
var hl = new HyperlinkCell {
HyperlinkClickMode = HyperlinkClickMode,
Padding = padding
};
var hl = new HyperlinkCell { Padding = padding };
hl.SetBinding(HyperlinkCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
hl.SetBinding(HyperlinkCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(HyperlinkCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
hl.SetBinding(HyperlinkCell.BackgroundProperty, new Binding(nameof(background_brush)) { Source = this });
hl.SetBinding(
CursorProperty,
new Binding {
Source = hl,
Path = new PropertyPath(HyperlinkCell.IsMouseOverClickAreaProperty),
Converter = new BooleanToObjectConverter {
ValueForFalse = Cursors.Arrow,
ValueForTrue = Cursors.Hand
}
});
return hl;
}
}
}
}
}
@@ -1,50 +1,44 @@
using mbts.spark.filters;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
internal class TextColumn<TKey> : FrameworkElementColumnBase<TKey>, IFrameworkElementColumn, IFilterableColumn, IKeyedColumn<TKey> {
static readonly Thickness _default_padding = new Thickness(6, 0, 6, 0);
internal class TextColumn<TColKey> : FrameworkElementColumnBase<TColKey>, IFilterableColumn {
readonly BindingBase _binding;
public TextColumn(
TKey key, IDataGridColumnFilter filter, string filter_template) : base(key) {
public TextColumn(TColKey key, BindingBase binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
this.filter = filter;
this.filter_template = filter_template;
_binding = binding;
}
public Style ElementStyle { get; set; }
public IDataGridColumnFilter filter { get; }
public string filter_template { get; }
public Thickness padding { get; set; } = _default_padding;
// bindings
public BindingBase TextBinding { get; set; }
public BindingBase ToolTipBinding { get; set; }
public BindingBase background_binding { get; set; }
public BindingBase font_weight_binding { get; set; }
public BindingBase foreground_binding { get; set; }
public BindingBase tooltip_binding { get; set; }
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
public override FrameworkElement view_factory() {
var v = new TextCell {
Padding = padding
};
// bindings
if (TextBinding != null)
v.SetBinding(TextCell.TextProperty, TextBinding);
if (ToolTipBinding != null)
v.SetBinding(ToolTipProperty, ToolTipBinding);
v.SetBinding(TextCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
var v = new TextCell { Foreground = font_brush, VerticalAlignment = vertical_alignment };
v.SetBinding(TextCell.TextProperty, _binding);
v.SetBinding(TextCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
v.SetBinding(TextCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
v.SetBinding(TextCell.HorizontalContentAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
v.SetBinding(FrameworkElement.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
// background
var bg_brush = new SolidColorBrush();
BindingOperations.SetBinding(bg_brush, SolidColorBrush.ColorProperty, new Binding(nameof(background_color)) { Source = this });
v.Background = bg_brush;
// style
if (ElementStyle != null)
v.Style = ElementStyle;
v.SetBinding(TextCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
v.SetBinding(TextCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
try_set_binding(TextCell.BackgroundProperty, background_binding);
try_set_binding(TextCell.ForegroundProperty, foreground_binding);
try_set_binding(TextCell.FontWeightProperty, font_weight_binding);
try_set_binding(TextCell.ToolTipProperty, tooltip_binding);
return v;
//
void try_set_binding(DependencyProperty p, BindingBase b) {
if (b != null)
v.SetBinding(p, b);
}
}
}
}
}
}
@@ -1,9 +0,0 @@
namespace mbts.ui {
public interface IDIYGridColumn {
string id { get; }
}
public interface IDIYGridColumn<T> : IDIYGridColumn {
new T id { get; }
}
}
@@ -1,9 +0,0 @@
using System.ComponentModel;
namespace mbts.ui {
interface ISortableDIYGridColumn : IDIYGridColumn {
ListSortDirection? SortDirection { get; set; }
}
interface ISortableDIYGridColumn<T> : ISortableDIYGridColumn, IDIYGridColumn<T> { }
}
@@ -2,7 +2,6 @@
using bts.utils;
using mbts.spark.filters;
using mbts.ui.controls.adorners;
using mbts.ui.dpi;
using mbts.ui.extensions;
using mbts.ui.settings;
using mbts.ui.utility;
@@ -231,13 +230,11 @@ namespace mbts.ui.controls {
_scroll_viewer = this.descendants_of_type<ScrollViewer>().First();
if (SelectionMode != SelectionMode.Single) {
_drag_rect_adorner = new DragRectAdorner(this, _scroll_viewer, _row_height);
_drag_rect_adorner.SetCurrentValue(DragRectAdorner.FillProperty, _grid.SelectionHighlight);
_drag_rect_adorner.SetCurrentValue(DragRectAdorner.StrokeProperty, new Pen(_grid.SelectionHighlight.CloneCurrentValue().with(1), 1).frozen());
_drag_rect_adorner.SetResourceReference(DragRectAdorner.FillProperty, DIYGrid.ActiveSelectionHighlightKey);
_drag_rect_adorner.SetResourceReference(DragRectAdorner.StrokeProperty, DIYGrid.SelectionStrokeKey);
_drag_rect_adorner.attach();
}
_rsa = new RowSelectionAdorner(this, _scroll_viewer);
_rsa.SetCurrentValue(RowSelectionAdorner.FillProperty, _grid.SelectionHighlight);
_rsa.SetCurrentValue(RowSelectionAdorner.StrokeProperty, new Pen(_grid.SelectionHighlight.CloneCurrentValue().with(1), 1).frozen());
_rsa.attach();
};
Unloaded += (s, e) => {
@@ -446,12 +443,8 @@ namespace mbts.ui.controls {
static DIYGrid() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(typeof(DIYGrid)));
UseLayoutRoundingProperty.OverrideMetadata(typeof(DIYGrid), new FrameworkPropertyMetadata(OnUseLayoutRoundingChanged));
DefaultSelectionHighlight.Freeze();
DefaultSelectionStroke.Freeze();
void OnUseLayoutRoundingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
d.CoerceValue(RowHeightProperty);
}
public DIYGrid() {
@@ -1077,7 +1070,7 @@ namespace mbts.ui.controls {
if (v < min_row_height)
return min_row_height;
}
return o.UseLayoutRounding ? v.RoundLayoutValue() : v;
return v;
}, DefaultRowHeight);
public event Action<double> RowHeightChanged;
@@ -1154,30 +1147,33 @@ namespace mbts.ui.controls {
double frozen_offset = 0;
int vis_frozen_col_count = _get_visible_frozen_column_count();
foreach (var col in _visible_columns.Take(vis_frozen_col_count)) {
double right = frozen_offset + col.ActualWidth;
right = right.RoundLayoutValue();
yield return new ColumnGeometry(col, null, frozen_offset, frozen_offset, right);
yield return new ColumnGeometry() {
column = col,
left = frozen_offset,
unclipped_left = frozen_offset,
};
frozen_offset += col.ActualWidth;
}
if (_frozen_column_count > 0) {
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
}
// frozen_offset will be off by 2 pixels because of the GridViewHeaderRowPresenter PaddingHeader (see c_PaddingHeaderMinWidth in referencesource)
// We could use the _header_frozen.ActualWidth but due to the async nature of layout it get updated slightly too late
frozen_offset += 2;
// Adjust by scroll amount
double offset = frozen_offset - _scroll_horizontal.Value;
offset = offset.RoundLayoutValue();
double offset = frozen_offset - (_scroll_horizontal.Value);
int count = 0;
foreach (var col in _visible_columns.Skip(vis_frozen_col_count)) {
double left = Math.Max(offset, frozen_offset);
double right = offset + col.ActualWidth;
right = right.RoundLayoutValue();
// If right < frozen_offset column is completely obscured
if (include_obscured_cols || (right > frozen_offset && left <= ActualWidth)) {
// fully visible
yield return new ColumnGeometry(col, _superheader_column_index.ElementAtOrDefault(count), left, offset, right);
yield return new ColumnGeometry() {
column = col,
super_column = _superheader_column_index.ElementAtOrDefault(count),
unclipped_left = offset,
left = left
};
}
offset = right;
offset += col.ActualWidth;
++count;
}
}
@@ -55,7 +55,6 @@
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="SelectionMode" Value="Extended"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="GridViewColumnHeader">
@@ -26,12 +26,12 @@ namespace mbts.ui.controls {
}
}
public class HighPerformanceRow<TRowVM> : HighPerformanceRowBase where TRowVM : class, IRowVM {
public class HighPerformanceRow<TRowVM, TColKey> : HighPerformanceRowBase where TRowVM : class, IRowVM {
double _row_height;
ObservableCollection<ColumnBase> _columns;
List<ColumnGeometry> _column_layout = new List<ColumnGeometry>();
Dictionary<string, ColumnGeometry> _column_layout_map = new Dictionary<string, ColumnGeometry>();
VisualDictionary<string, FrameworkElement> _element_cells;
Dictionary<TColKey, ColumnGeometry> _column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
VisualDictionary<TColKey, FrameworkElement> _element_cells;
RecycleBin<DIYGrid> _grid_backing;
RecycleBin<ListBoxItem> _container_backing;
TRowVM _row;
@@ -39,7 +39,7 @@ namespace mbts.ui.controls {
public HighPerformanceRow() : this(DefaultRowHeight) { }
public HighPerformanceRow(double row_height) {
_row_height = row_height;
_element_cells = new VisualDictionary<string, FrameworkElement>(this);
_element_cells = new VisualDictionary<TColKey, FrameworkElement>(this);
_grid_backing = new RecycleBin<DIYGrid>(
g => { g.ColumnLayoutChanged += _on_column_layout_changed; g.RowHeightChanged += _on_row_height_changed; },
g => { g.ColumnLayoutChanged -= _on_column_layout_changed; g.RowHeightChanged -= _on_row_height_changed; });
@@ -123,7 +123,7 @@ namespace mbts.ui.controls {
protected override Size ArrangeOverride(Size finalSize) {
foreach (var kv in _column_layout_map) {
if (_element_cells.TryGetValue(kv.Key, out FrameworkElement ec))
ec.Arrange(kv.Value.to_rect(_row_height));
ec.Arrange(new Rect(new Point(kv.Value.left, 0), new Size(kv.Value.width, _row_height)));
}
return finalSize;
}
@@ -148,18 +148,16 @@ namespace mbts.ui.controls {
}
void _add_columns(IList items) {
foreach (ColumnBase col in items) {
if (col is IFrameworkElementColumn el_col) {
var cell_view = el_col.view_factory();
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.id));
_element_cells.Add(col.id, cell_view);
}
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>()) {
var cell_view = col.view_factory();
CellRender.change_position(cell_view, _row_height, _column_layout_map.GetNullable(col.key));
_element_cells.Add(col.key, cell_view);
}
}
void _remove_columns(IList items) {
foreach (ColumnBase col in items)
_element_cells.Remove(col.id);
foreach (var col in items.OfType<FrameworkElementColumnBase<TColKey>>())
_element_cells.Remove(col.key);
}
void _on_column_layout_changed(List<ColumnGeometry> column_layout) {
@@ -173,10 +171,10 @@ namespace mbts.ui.controls {
}
void _set_column_layout_map() {
_column_layout_map = new Dictionary<string, ColumnGeometry>();
_column_layout_map = new Dictionary<TColKey, ColumnGeometry>();
foreach (var cg in _column_layout) {
if (cg.column is IFrameworkElementColumn)
_column_layout_map.Add(cg.column.id, cg);
if (cg.column is FrameworkElementColumnBase<TColKey> elc)
_column_layout_map.Add(elc.key, cg);
}
}
#endregion
Loaded 100 of 451 files, more files were not shown because too many files have changed in this diff. Show more