Compare commits

...
224 Commits
Author SHA1 Message Date
Taha Afzal b3369db7cc Release notes 2019-11-04 13:07:02 -05:00
Taha Afzal 279abcfefd Made the fractional_hedging test a bit easier to do 2019-11-04 10:41:27 -05:00
Taha Afzal 872e2465a6 Fixing the test 2019-11-01 14:15:11 -04:00
Taha Afzal 89fb79c556 Manual tests added 2019-10-29 18:53:17 -04:00
Taha Afzal c92e9df77b Merge branch 'hide_deleted_accounts' 2019-10-29 14:58:11 -04:00
Taha Afzal 81aaa853d5 Merging SP-248 2019-10-29 14:40:00 -04:00
Taha Afzal 4f308faba2 Merge branch 'SP-247-order-entry-sounds' 2019-10-29 14:09:30 -04:00
Taha Afzal 0b924033c2 Merge branch 'SP-252-root-spreader-security' 2019-10-29 14:08:29 -04:00
Taha Afzal 3606c707e4 Merge branch 'SP-204-local-fills' 2019-10-29 14:07:42 -04:00
Taha Afzal 5998107e6a Merge branch 'SP-225-investigate-the-lack-of-price-rep' 2019-10-29 14:03:12 -04:00
Taha Afzal 297982433b Merge branch 'SP-249' 2019-10-29 13:58:28 -04:00
Taha Afzal 15ac9d945b Modified tests 2019-10-29 13:53:20 -04:00
Jake Norbut a0f1d11692 adding manual fills don't show admin fills test 2019-10-28 16:15:01 -05:00
Jake Norbut e7890a459c test for order sent sound 2019-10-28 15:57:27 -05:00
Jake Norbut f7fb5ed400 adding test for deleted accounts 2019-10-28 15:48:47 -05:00
Jake Norbut 75e98d353f adding left panel test steps 2019-10-28 15:41:39 -05:00
John Colvin 5fc6ff4c00 SP-204 Local fills UI erroneously shows admin fills too 2019-10-26 16:40:32 -05:00
Taha Afzal 6951272633 Just has to update the last_price 2019-10-25 13:05:44 -04:00
Taha Afzal 548611b44f Adding tooltips, and hooking to the right proto 2019-10-24 18:04:54 -04:00
Taha Afzal 322da04723 Merge branch 'SP-249' of bitbucket.org:bluetradesys/spark into SP-249 2019-10-24 17:09:30 -04:00
Taha Afzal 3544eac815 Cleaned up the quote throttling code 2019-10-24 17:09:24 -04:00
John Colvin 7fef7d88ce refactor QuoteInsideMarketDistance to new proto type (cannot recycle old type bc it must be null in existing synths) 2019-10-24 15:41:30 -05:00
John Colvin 7531cf5eb4 remove unused synth param 'QuoteInsideMarketDistanceAwayModel' 2019-10-24 15:38:15 -05:00
John Colvin 5826a083c0 SyntheticEditor - remove Quote Inside Market Away Distance param 2019-10-24 12:44:47 -05:00
John Colvin ec0d5d22f1 SyntheticEditor - disable Quote Throttle/Inside Market Distance by default 2019-10-24 12:37:13 -05:00
John Colvin d433368795 fix typo in 'order sent alert' predicate (creation time comparison was backwards) 2019-10-24 11:51:39 -05:00
Jake Norbut dc4453e510 SP-256 - hide deleted accounts 2019-10-24 08:32:38 -05:00
Jake Norbut 1b1583abda removing OJ and KC IFU entries; regen cme ref data file 2019-10-23 09:24:28 -05:00
Jake Norbut 9d2f2888bb updating reference data with latest for RodrigoSGU and CNH 2019-10-23 09:08:20 -05:00
John Colvin 26816f0048 SP-249 New spreader quoting throttle parameter 2019-10-22 12:12:17 -05:00
Taha Afzal e97e6b93c7 Adding proto 2019-10-21 16:44:00 -04:00
John Colvin 0d8252e131 SP-252 Parent spreader badge should show the spreader name as well 2019-10-21 12:58:21 -05:00
Taha Afzal 801b9622f8 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-10-17 15:09:19 -04:00
Taha Afzal a5df3f2da2 Dynamic Algos are now loaded only when explicitely enabled by name in Toggles config 2019-10-17 15:09:10 -04:00
John Colvin f03a413855 SP-247 Order Entry Sounds 2019-10-17 13:55:52 -05:00
Michael Harding f3a692bda2 Delete ex_sim_orders_venue_sim in system tests 2019-10-17 13:30:11 -04:00
Kevin Darby 6c3bdbdc34 make sim datapaths pretty again 2019-10-17 12:09:05 -04:00
Eric Harding d4749dd381 Tool to test c# api latency 2019-10-17 11:57:20 -04:00
Taha Afzal a008f4d0fd Hiding pyramid, ignoring timer (which was faking market status) 2019-10-17 11:38:35 -04:00
Taha Afzal 0ee9521454 Merge branch 'TAS' 2019-10-17 11:16:03 -04:00
Kevin Darby 62cef7c2f7 release notes 2019-10-17 09:44:53 -04:00
Taha Afzal 1b39f4e47c Merge branch 'master' into TAS 2019-10-16 21:54:24 -04:00
Taha Afzal 3212b2fcee Added logs for TAS Algo 2019-10-16 16:38:12 -04:00
Taha Afzal 1576541bd1 Clean up 2019-10-16 14:59:34 -04:00
Kevin Darby 62e97364fb Merged in ice_exec3 (pull request #230)
Ice execution

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-10-16 18:47:10 +00:00
Taha Afzal 93b954ce46 Some cleanup remains, mostlty done 2019-10-16 11:37:32 -04:00
John Colvin c9da022c58 SP-248 Ladder left panel scroll 2019-10-15 12:12:20 -05:00
John Colvin 0772a13b09 introduce ladder settings 'profile-equivalent' logic to avoid disconnecting ladders from profiles due to trivial settings changes 2019-10-14 20:55:00 -05:00
John Colvin c53f6ad906 finally remove mutable members from LadderSettings GetHashCode impl 2019-10-14 16:01:24 -05:00
Taha Afzal 1058c8a80e Merge branch 'pyramid' into TAS 2019-10-14 11:45:56 -04:00
Taha Afzal 7ecb106880 Done 2019-10-14 11:39:27 -04:00
Taha Afzal b0c1d93c50 Corner cases fixed 2019-10-14 11:08:29 -04:00
Kevin Darby 0c5a039226 tested buffered cancel and replace with lean_controlled.txt, verified in fix 2019-10-11 14:04:32 -04:00
Kevin Darby 2677144bfa buffer cancel and updates because ice sucks 2019-10-10 17:43:28 -04:00
Kevin Darby 6b30c42c0d added gt_cert jetfix rule to conf per Pete 2019-10-10 14:56:32 -04:00
Kevin Darby 2f62d29f66 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-10-09 14:03:11 -04:00
Taha Afzal 828f867a80 Merge branch 'pyramid' of bitbucket.org:bluetradesys/spark into pyramid 2019-10-09 13:46:19 -04:00
Taha Afzal c9663cf7ab made the child orders configurable 2019-10-09 13:46:08 -04:00
John Colvin b85de51d0c bring back DefaultAlgoVM for 'None' case (dynamic children placeholders do not have specific impl) 2019-10-09 12:33:40 -05:00
John Colvin dc8e65923d fix adminModel typo '&&' -> '||' (users need admin OR trade permissions to see account) 2019-10-09 12:00:25 -05:00
Eric Harding 1469373b9c fix Sparkcmd -h help printer 2019-10-08 15:59:15 -04:00
Kevin Darby 820e937336 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-10-08 12:49:58 -04:00
John Colvin 8b73878782 null check for TradingPermissions (fixes login crash) 2019-10-08 11:46:32 -05:00
Kevin Darby b15106dac4 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-10-08 11:43:22 -04:00
Michael Harding 100316b791 SP-246: Fix System Tests 2019-10-08 11:06:41 -04:00
Kevin Darby 3c98425226 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-10-07 12:53:44 -04:00
John Colvin 1aa9c46367 fix build (bad using directives after merge) 2019-10-07 10:48:12 -05:00
Kevin Darby 5211cab351 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-10-07 10:24:36 -04:00
Pedro Pinto e89a4cce53 Merged in sim_prorata (pull request #350)
RTM. Documented OrderMatcher better.
2019-10-07 13:38:41 +00:00
Eric Harding cb56b90737 Merged in SP-229-shell-crash-dumps (pull request #346)
RTM: SP-229 shell crash dumps

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-10-07 13:38:00 +00:00
Pedro Pinto 761a89f052 Updated release notes 2019-10-07 09:37:00 -04:00
Pedro Pinto cab6b56606 Merged master 2019-10-07 09:36:21 -04:00
Pedro Pinto 8a743a91a5 Updated release notes. 2019-10-07 09:29:30 -04:00
Taha Afzal 5dec5dc392 Merged in SP-232 (pull request #345)
RTM. Fixing SP-232

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-10-07 13:28:30 +00:00
Eric Harding 27aa4fca26 Merged in system-tests (pull request #349)
RTM: System tests

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-10-07 13:20:35 +00:00
Pedro Pinto 84a2e50bf7 Updated release notes. 2019-10-07 09:19:18 -04:00
John Colvin 297eb5b25b Merged in SP-181-no-spreads-in-man-fills (pull request #352)
RTM SP-181 Remove the option to enter manual fills of spreads

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-10-07 13:18:26 +00:00
Pedro Pinto d9d0e1f4ef Updated release notes. 2019-10-07 09:15:21 -04:00
Eric Harding 6ec0cc8bbe Merged in SP-236-websocketpp (pull request #353)
RTM: SP-236 Disable websocketpp open_handshake timeout
2019-10-07 13:13:30 +00:00
Pedro Pinto 795b77ea3f Updated release notes. 2019-10-07 09:12:45 -04:00
John Colvin 91c97d32bb Merged in SP-223-som-synth-crash (pull request #355)
RTM add dispatcher thread name to assert log

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-10-07 13:11:57 +00:00
John Colvin e0c50703d7 Merged in SPARK-2534-fills-time-filter-bug (pull request #357)
RTM SPARK-2534 fills time filter bug

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-10-07 13:11:03 +00:00
Pedro Pinto dab1e76766 Updated release notes. 2019-10-07 09:10:32 -04:00
Pedro Pinto b8c927df27 Merge branch 'master' into SPARK-2534-fills-time-filter-bug 2019-10-07 09:02:25 -04:00
Pedro Pinto 2684d25f09 Updated release notes 2019-10-07 09:01:48 -04:00
John Colvin 7c95a77ae2 Merged in update-naggle-test (pull request #360)
RTM unit test for update.naggle fix
2019-10-07 13:00:06 +00:00
John Colvin 31b115d13b Merged in bts-admin-permissions (pull request #356)
RTM Bts admin permissions
2019-10-07 12:58:42 +00:00
Pedro Pinto d681a6924b Updated release notes. 2019-10-07 08:39:01 -04:00
Pedro Pinto d0cce84d2e Merge branch 'master' into bts-admin-permissions 2019-10-07 08:38:07 -04:00
Pedro Pinto 532591c34f Updated release notes. 2019-10-07 08:36:14 -04:00
Taha Afzal 562b9419bd hedge is a lean now 2019-10-04 17:32:03 -04:00
John Colvin 7f187224c1 add .Replay(1).RefCount() to AdminModel.on_accounts 2019-10-02 13:40:29 -05:00
John Colvin 79b2121f5e OrderBookVM disposes order_mod_vm 2019-10-02 13:40:28 -05:00
John Colvin 0bd477594c FillsVM disposes filters 2019-10-02 13:36:58 -05:00
John Colvin 149cd90fb4 apply permission filter to pnl stream 2019-10-02 11:54:38 -05:00
John Colvin f74e719a9f sort accounts/users in filter dropdowns for OrderBook/Fills/PNL 2019-10-02 11:54:38 -05:00
John Colvin 8b68beae96 Merge remote-tracking branch 'origin/master' into bts-admin-permissions 2019-10-02 10:52:45 -05:00
Pedro Pinto 9cc0b471f2 Added support for tag 1031, defaults to Y. 2019-10-02 10:32:23 -04:00
Taha Afzal 3e035197d0 Top Spread is also a security (forgot to mark it as a security, so it was showing as a uint in the ui) 2019-10-01 20:42:06 -04:00
Taha Afzal c9d8a640b6 Merge branch 'pyramid' of bitbucket.org:bluetradesys/spark into pyramid 2019-10-01 19:44:49 -04:00
John Colvin 1d97e620fd add security map to view's resources for dynamic security key param's ContractPickerPopup 2019-10-01 17:17:31 -05:00
John Colvin f6299ab5c3 clean up DynamicOrderParamVM - don't store value with the descriptor, make 'param' prop readonly 2019-10-01 17:08:32 -05:00
John Colvin eaff4774e0 refactor some Disposable.replace calls so that the prev hook is disposed before the new one is created 2019-10-01 17:08:32 -05:00
John Colvin bed52db98a dynamic order SecurityKey DataTemplate 2019-10-01 17:08:32 -05:00
John Colvin bd3ef33607 AlgoStudio dynamic order 'SecurityKey' template 2019-10-01 17:08:32 -05:00
John Colvin 25a594895e ladder/ticket/algo studio views store SecurityMap in resource dictionary 2019-10-01 17:08:31 -05:00
John Colvin 4d468c8026 DynamicOrderVM 'SecurityKey' descriptor impl 2019-10-01 17:08:31 -05:00
John Colvin 68ed90487b AlgoParamVM template key impl 2019-10-01 17:08:31 -05:00
John Colvin ddc2c67fea add SelectedSecurityKey dp to ContractPickerPopup 2019-10-01 17:08:31 -05:00
Taha Afzal 77459e9c64 price changes dont have the algo params filled in 2019-10-01 16:15:36 -04:00
John Colvin db64f2f45b migrate common ManualFills resource keys into central location 2019-09-30 18:21:06 -05:00
John Colvin 3f7ea71de7 [non-functional] normalize whitespace in AlgoContentTemplateSelector.xaml 2019-09-30 18:11:18 -05:00
John Colvin d334418655 PyramidAlgo uses 'SecurityKey' dynamic param descriptor 2019-09-30 18:09:11 -05:00
John Colvin d1844afeae server side impl for 'SecurityKey' dynamic param descriptor 2019-09-30 18:08:41 -05:00
John Colvin b6dc2bd4d4 add 'SecurityKey' dynamic param descriptor 2019-09-30 18:03:52 -05:00
Taha Afzal 58906eb3b6 Code complete 2019-09-30 16:55:35 -04:00
Kevin Darby 50e61604ba move default keycode exp to 12/31/2019 to fix unit tests 2019-09-30 11:53:44 -04:00
Kevin Darby 6bd23f106f Merged in fix_nan_pnl (pull request #361)
add temporary config to ignore nan day pnl to stop the loss limit from keeping traders out of market
2019-09-30 15:07:11 +00:00
Kevin Darby dfd8348724 add temporary config to ignore nan day pnl to stop the loss limit from keeping traders out of market 2019-09-30 11:03:03 -04:00
Taha Afzal 963a4207e2 Code complete, need cleanup and testing 2019-09-26 19:00:15 -04:00
John Colvin d2e9352e48 unit test for update.naggle fix 2019-09-25 12:01:31 -05:00
Kevin Darby da42e95754 fixed externals and docker 2019-09-25 10:59:58 -04:00
Kevin Darby a878a55ac1 moved externals 2019-09-25 10:00:20 -04:00
John Colvin 4305d560aa Merged in pnl-rounding-error (pull request #359)
RTM Pnl rounding error

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-09-25 13:57:52 +00:00
John Colvin 055f0015e7 Update.naggle pr edit: 'TryPop' is an innappropriate name (just use 'Remove') 2019-09-24 17:28:06 -05:00
Kevin Darby a39d91ceb3 Merge branch 'pnl-rounding-error' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-24 14:38:26 -04:00
John Colvin ddc50e1fba pnl initial calc type was not being applied to data 2019-09-24 13:32:54 -05:00
John Colvin ffb571d1cf fix pnl rounding issues (use 9 decimal places) 2019-09-24 13:32:53 -05:00
John Colvin 2a36c05d75 fix PNLRowData Equals impl (was not comparing market prices) 2019-09-24 13:32:53 -05:00
John Colvin a3730eb203 Update.naggle merge maintains changes (could get into state where changes became adds/removes) 2019-09-24 13:32:53 -05:00
John Colvin d033275694 remove unused member OrdersModel.on_account_pnl 2019-09-24 13:32:53 -05:00
Taha Afzal 4e58342677 Merge branch 'master' into Pyramid 2019-09-24 13:12:56 -04:00
Taha Afzal 5eeb5a534c Adding logs 2019-09-24 13:12:16 -04:00
Jake Norbut 016ddf9b69 fixing pay less definition 2019-09-24 08:21:56 -05:00
John Colvin 0cc5817932 expand synthetic leg columns to fit name 2019-09-24 01:04:28 -05:00
Michael Harding 032836dea4 Test updates 2019-09-23 15:54:33 -04:00
Taha Afzal f20c53bced Organized the code better 2019-09-23 14:08:21 -04:00
Kevin Darby a06461eb5e Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-23 13:58:22 -04:00
Kevin Darby 34e7d6a617 remove ice bust handling 2019-09-23 13:57:43 -04:00
Kevin Darby 276e9ad0ef Merged in fix_expired_fill_crash (pull request #358)
Fix expired fill crash
2019-09-23 17:50:02 +00:00
Pedro Pinto 5968ac10d2 The fix is right, but the code was a bit clumsy. 2019-09-23 13:48:26 -04:00
Kevin Darby 31e44d8c10 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-23 13:03:20 -04:00
John Colvin 9bf7666c30 downgrade pnl rounding error Log assert to warning 2019-09-23 11:56:57 -05:00
Kevin Darby 8fdab4a510 note about expired securities in sod tests 2019-09-23 12:47:48 -04:00
Kevin Darby 385adaa1b0 Merge branch 'fix_expired_fill_crash' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-23 12:36:16 -04:00
Kevin Darby f9506bc9ad Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-23 12:36:00 -04:00
Kevin Darby b9fd0e2e28 in SIM you can trade an expired security and if we leave it out of this map
we will later attempt to add this fill twice while testing SOD because the test
asks to delete the fills db
2019-09-23 12:31:03 -04:00
Kevin Darby 6b5b517d70 slight nit in GLBXExecutionGateway 2019-09-23 12:29:38 -04:00
John Colvin 80850146c1 TableUpdate.AggregateLatest shouldn't share seed across subscriptions 2019-09-23 00:08:12 -05:00
John Colvin 0680dddab4 normalize line endings in TickUpDown.xaml 2019-09-22 15:22:57 -05:00
John Colvin 70aa62c0cf add border to DateTimePicker 'time' part view 2019-09-22 15:22:20 -05:00
John Colvin 06165b3842 SPARK-2534 Fills Form seems to always have SOD orders in there 2019-09-22 15:06:03 -05:00
John Colvin f21902d60d remove unused files AdminModelWrapper/PermissionFilter.cs 2019-09-22 14:46:52 -05:00
John Colvin a27f383b62 remove LoginContainer MEF Export consts (replace usages with UserTuple) 2019-09-22 12:58:46 -05:00
John Colvin 35d4fcc513 make UserTuple public (it's referenced in multiple assemblies) 2019-09-22 12:58:46 -05:00
John Colvin 8a96b00f64 add 'User' column to Synthetic Management window 2019-09-22 12:58:46 -05:00
John Colvin 2d06fd3d92 [non-functional] style cop/whitespace changes to SyntheticManagementDialogVM.cs 2019-09-22 12:58:45 -05:00
John Colvin 76050e0722 SyntheticManagement vm uses 'DataGridFilterHelper.hook_all' method to eliminate risk of omitting column filters 2019-09-22 12:58:45 -05:00
John Colvin a3f0c2fbf6 pass UserTuple to SyntheticManagement row (for imminent user column), remove dependency on securitymd 2019-09-22 12:58:45 -05:00
John Colvin 1ccc4c3746 add 'ShowLadderAutoLookupTag' to eschew securitymd depependency 2019-09-22 12:58:45 -05:00
John Colvin e5d85db888 SP-151 BTS user can view/edit all synths/algos 2019-09-22 12:11:15 -05:00
John Colvin e47b1c3048 [non-functional] style cop/whitespace changes in MDSource/Daemon.cs 2019-09-22 12:11:15 -05:00
John Colvin 039791a296 simplify AdminModel, api 'permission filters' use new UserTuple.Permissions enum 2019-09-22 12:11:15 -05:00
John Colvin e6884b6654 add 'Permissions' enum to UserTuple 2019-09-22 11:39:26 -05:00
John Colvin 8cbae28dd8 move UserTuple.cs into spark_api 2019-09-22 11:39:26 -05:00
John Colvin 010a1fecb1 remove unused function 'OrderFactory.subscribe_user_tuples' 2019-09-22 11:39:26 -05:00
Pedro Pinto ec1d925dbe Added to .gitignore 2019-09-22 08:25:10 -04:00
Kevin Darby 96bec55ee7 fix build, we will have to handle these ice cpps someplace else 2019-09-20 15:01:56 -04:00
Kevin Darby 424bb36fc7 update jakes script for ice cpps 2019-09-20 14:30:27 -04:00
Kevin Darby c24700195f fixed cpp coming from market data for ice, added the obligatory overrides 2019-09-20 14:15:27 -04:00
Jake Norbut f7184b0949 updating tests 2019-09-20 09:13:01 -05:00
John Colvin 633ebf38a2 restore original ComboBox default padding on the left 2019-09-19 18:58:19 -05:00
John Colvin 1eb8352b6b OrdersModel blocks GlbxIceberg orders from going out to non-glbx exchange 2019-09-19 17:21:48 -05:00
John Colvin 33c201b395 remove unused SparkTag.CreateOrder 2019-09-19 17:21:17 -05:00
Eric Harding 999973c349 upgrade websocketpp 0.7.0 to 0.8.1 (externals) 2019-09-19 14:59:09 -04:00
Eric Harding 4663ae9e4d SP-236 Disable websocketpp open_handshake timeout 2019-09-19 14:38:00 -04:00
Kevin Darby 34fc6af137 fix mview test 2019-09-19 14:17:53 -04:00
Kevin Darby 6cc25a7337 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-19 13:56:32 -04:00
Kevin Darby 0c9a9fcb5e git lfs 2019-09-19 13:56:01 -04:00
Kevin Darby 48b62f1cab new externals 2019-09-19 13:40:01 -04:00
Kevin Darby 5a0ceafa66 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-19 13:28:31 -04:00
Kevin Darby d31969606e Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-19 13:27:52 -04:00
Kevin Darby 6c6fb74506 Merged in minimal_api_tests (pull request #348)
RTM. Minimal api tests

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-09-19 17:26:14 +00:00
Pedro Pinto a69ed30e70 Updated release notes. 2019-09-19 13:19:52 -04:00
Eric Harding a654f302a2 Merged in SP-106-md-crashy (pull request #351)
SP-106 md crashy
2019-09-19 17:10:35 +00:00
John Colvin 9ca3285888 miniob 'CheckColumn' is visible 2019-09-19 11:56:47 -05:00
John Colvin d7d170f228 SP-181 Remove the option to enter manual fills of spreads 2019-09-19 11:23:36 -05:00
Eric Harding df4c3b9376 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-106-md-crashy 2019-09-19 09:11:10 -04:00
Taha Afzal cf977befd1 odds and ends 2019-09-18 17:22:09 -04:00
Eric Harding 1d538b4cb0 Code review feedback. Checked DeleteValue 2019-09-18 17:04:38 -04:00
Pedro Pinto f21c9f96d1 Merged from prod 2019-09-18 16:25:35 -04:00
Pedro Pinto 16a71a9f63 Updated release notes. 2019-09-18 16:24:09 -04:00
Pedro Pinto b42febdf60 Documented OrderMatcher better. 2019-09-18 16:11:16 -04:00
Eric Harding 71915de4b0 sparkcmd CaseInsensitiveEnumValues 2019-09-18 15:47:53 -04:00
Kevin Darby facb3249d0 Merge branch 'minimal_api_tests' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-18 15:24:51 -04:00
Kevin Darby f323efc61a Merge branch 'master' of bitbucket.org:bluetradesys/spark into ice_exec3 2019-09-18 15:24:14 -04:00
Eric Harding be5f54326e sparkcmd test should always respect test type (unit/system) 2019-09-18 15:16:28 -04:00
Kevin Darby a8689aee06 verified client api examples work 2019-09-18 15:13:53 -04:00
Kevin Darby 85b2d257a7 updated the example excel sheet, added some manual tests to vet api docs and excel 2019-09-18 14:57:40 -04:00
Michael Harding f49ca9f7bd Merged in system-tests (pull request #347)
System tests
2019-09-18 18:39:11 +00:00
Eric Harding 297233eae6 Clientbuild probably needs this too 2019-09-18 13:28:00 -04:00
Eric Harding 0114dd0276 SP-106 MDConsumerHandle crash both managed+native 2019-09-18 12:58:15 -04:00
Eric Harding ffd35aebac Enable btree_map.h on windows 2019-09-18 09:52:41 -04:00
Taha Afzal c9b2ac290c payup logic implemented 2019-09-17 22:10:53 -04:00
Pedro Pinto b92fed4f47 Merged from prod 2019-09-17 16:59:03 -04:00
Taha Afzal 340d65a47c Merge branch 'master' into pyramid 2019-09-17 16:32:53 -04:00
Taha Afzal f89fb76cb2 Merge branch 'master' into SP-232 2019-09-17 14:11:26 -04:00
Taha Afzal 16adb9ee18 Fixing SP-232 2019-09-17 14:09:34 -04:00
Eric Harding 154a637ee1 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-229-shell-crash-dumps 2019-09-16 17:34:36 -04:00
Eric Harding 399e4bf654 SP-229 Detect unclean shell shutdown and automatically upload an error report 2019-09-16 13:48:12 -04:00
Eric Harding 5908b4478e Error reports should be logged on the client 2019-09-16 13:20:03 -04:00
Eric Harding 44571a5af5 spark shell should dispose daemons 2019-09-16 10:41:58 -04:00
Eric Harding 62274f5129 Append windows event log and crash dumps to error reports 2019-09-12 17:18:47 -04:00
Eric Harding b5b5101faf SP-229 Allow ErrorReportService to accept an arbitrary list of files 2019-09-12 17:17:57 -04:00
Eric Harding bb232d8f1e Add easy convert to immutable function c# 2019-09-12 17:17:15 -04:00
Taha Afzal 1b3f5bc8a6 Fixed a bug with reporting 2019-09-11 13:44:37 -04:00
Taha Afzal 1b767f6e5e Cleaning up 2019-09-09 18:03:07 -04:00
Taha Afzal fe647044f8 Adding comments to SpreaderUtils 2019-09-09 15:03:52 -04:00
John Colvin aaca502500 add dispatcher thread name to assert log 2019-09-09 11:42:11 -05:00
Taha Afzal 69dcfdb2bd Delete parameter added 2019-09-03 16:57:41 -04:00
Taha Afzal e179f7e5a4 Some error handling (originating the algo from the wrong top spread) 2019-08-28 12:27:36 -04:00
Taha Afzal dba3747d98 Need more robust error checking, functionality works 2019-08-28 11:17:33 -04:00
Taha Afzal 3e7627ac24 Almost done, need to hook up hedging (cover) and parameter change 2019-08-24 13:08:26 -04:00
Taha Afzal 1b043f77cb Basic scaffolding in place for pyramid algo, shows up in the UI 2019-08-22 11:01:42 -04:00
190 changed files with 4003 additions and 1240 deletions

No files matched your search

+1
View File
@@ -33,3 +33,4 @@ trace_data/
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
.ionide/*
/.ignore
@@ -154,7 +154,7 @@ insert_anchor_links = "right"
- Pay Less - If enabled by checking the checkbox, the spreader will
attempt to pay less when the quantity at the level of the calculated
hedge price is more than the threshold defined.  The hedge order is
hedge price is less than the threshold defined.  The hedge order is
paid less by the “Payless Ticks” as defined per leg.
- Pay Up - If enabled by checking the checkbox, the spreader will pay
up when the quantity at the level of the calculated hedge price is
+36 -2
View File
@@ -4,19 +4,53 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.13.0
### User Visible Changes
- Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows
- Added support for sounds for new orders
- Added a new quoting throttle parameter for the spreader, that throttles quotes that are moving toward the inside market but are still a configurable number of ticks away
- Enabled the ladder to remember the left panel scroll position across front end restarts
### Bug Fixes
- Fixed the local fills window so that it does not show the admin fills
- Removed the deleted accounts from the ladder accounts dropdown
- Fixed the orderbook price for iceberg of spreaders
## 2.12.0
### User Visible Changes
- Added support for ICE market data and order execution
### Internal Changes
- Lots of permission / admin changes to facilitate ICE
## 2.11.0
### User Visible Changes
- Fixed fills form time filter to also apply to SOD fills
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
- Removed the option to enter manual spread fills
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
### Internal Changes
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
- To facilitate support, allowed the bts user to view and edit algos from all other users
- Improved system tests
- Improved log messages in UI to include dispatcher thread name
- Added support for Windows core dumps in the UI
## 2.10.0
### User Visible Changes
- Fills now include information about the algo that originated the fill
- A new, more powerful PnL form is now available
### API Changes
- Added security state information (pre open, halted, normal, closed, etc)
### Bug Fixes
- Brought back the "select all" check box in the mini order book
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
### Internal Changes
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
## 2.9.3
### Internal Changes
- Fixed system test scripts
## 2.9.2
### Bug Fixes
- Fixed an issue with the exchange simulator that could lead to crossed markets
## 2.9.1
### Bug Fixes
- Fixed an issue with trade sound alerts, where in some cases the use of a "In Between" condition could lead to a UI failure.
+53 -62
View File
@@ -1,83 +1,74 @@
using bts;
using bts.api.admin;
using bts.api.core;
using bts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using bts.api.core;
using mbts.ui.shell;
namespace mbts.spark {
[Export]
class AdminModel : IDisposable {
public readonly UserTuple user;
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
IReadOnlyDictionary<uint, string>
_account_map = new Dictionary<uint, string>(),
_user_map = new Dictionary<uint, string>();
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(new AdminModelWrapper());
IDisposable _dispose;
IDisposable _dispose;
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
[ImportingConstructor]
AdminModel(AdminService service, [Import]UserTuple user) {
AdminModel(AdminService service, UserTuple user) {
this.user = user;
_dispose = service.subscribei_admin_data().Select(update => {
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
IEnumerable<BTSUserImmutable> active_users =
from admin_data in update.all
from bts_user in admin_data.Users
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
select bts_user;
IEnumerable<BTSAccountImmutable> active_accounts =
from admin_data in update.all
from bts_account in admin_data.Accounts
where bts_account.IsPresent && _wrapper_subject.Value.can_view_account(bts_account.Name)
join account_id in (
from bts_user in active_users
from account_id in bts_user.UserData.AccountPermissions.AccountPermissions.Keys
select account_id
) on bts_account.Id equals account_id
select bts_account;
var users = active_users.ToDictionary(u => u.Id, u => u.Name);
var accounts = active_accounts.ToDictionary(a => a.Id, a => a.Name);
return (users, accounts);
}).Subscribe(t => {
_account_map = t.accounts;
_user_map = t.users;
_users_subject.OnNext(_user_map = t.users);
_accounts_subject.OnNext(_account_map = t.accounts);
});
// accounts
on_accounts = service.subscribei_admin_data()
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
usr.UserData.AccountPermissions.AccountPermissions
.Where(kv => (kv.Value.TradingPermission != null && kv.Value.TradingPermission.HasPermissions) || kv.Value.AdminPermission.HasPermissions)
.Select(kv => ad.Accounts.Where(a => a.Id == kv.Key).TryGetSingle(out var acc) ? acc : (BTSAccountImmutable?)null)
.Where(acc => acc.HasValue && acc.Value.IsPresent)
.Select(acc => acc.Value)
.ToDictionary(acc => acc.Id, acc => acc.Name) :
new Dictionary<uint, string>(0))
.Replay(1)
.RefCount();
// users
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
service.subscribei_admin_data()
.Select(u => u.all.TryFirst())
.Where(ad => ad.HasValue)
.Select(ad => ad.Value.Users
.Where(u => user.can_access_user_data(u.Id))
.Select(u => new UserTuple(u.Id, u.Name))
.ToDictionary(UserTuple.key_fn))
.Table(UserTuple.key_fn)
.Cache() :
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
_dispose = on_accounts.Subscribe(map => _account_map = map);
}
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
public IObservable<IReadOnlyList<UserTuple>> on_user_tuples => on_users.Select(d => d.Select(kv => new UserTuple(kv.Key, kv.Value)).ToList());
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
public AccountTuple? get_default_account() {
foreach (var kv in _account_map) {
return new AccountTuple(kv.Key, kv.Value);
}
return null;
}
public uint? get_user_id(string user) => _id_for_name(user, _user_map);
public uint? get_account_id(string account) => _id_for_name(account, _account_map);
public void Dispose() => Disposable.dispose(ref _dispose);
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
user.permissions == UserTuple.Permissions.Admin ?
positions :
positions.Filter(on_accounts.Select(map => new Func<T, bool>(p => map.ContainsKey(get_account_id(p)))));
static uint? _id_for_name(string name, IReadOnlyDictionary<uint, string> map) {
foreach (var kv in map) {
if (kv.Value == name)
return kv.Key;
}
return null;
}
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, string> get_account_name) where TKey : IEquatable<TKey> =>
user.permissions == UserTuple.Permissions.Admin ?
positions :
positions.Filter(on_accounts.Select(map => {
var hash = new HashSet<string>(map.Values);
return new Func<T, bool>(v => hash.Contains(get_account_name(v)));
}));
public AccountTuple? get_default_account() =>
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
public void Dispose() =>
Disposable.dispose(ref _dispose);
}
}
+3 -8
View File
@@ -3,15 +3,10 @@ using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
@@ -23,8 +18,8 @@ namespace bts.ui {
IDisposable _dispose;
[ImportingConstructor]
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
_user = username;
public AboutDaemon(IBus bus, UserTuple user) {
_user = user.name;
_bus = bus;
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
}
@@ -63,4 +58,4 @@ namespace bts.ui {
Disposable.dispose(ref _dispose);
}
}
}
}
+44 -7
View File
@@ -7,13 +7,11 @@ using mbts.ui;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows.Threading;
using System.Windows;
using System.Threading.Tasks;
using mbts.ui.shell;
using System.Windows;
using System.Windows.Threading;
namespace bts.spark
{
namespace bts.spark {
[Export, Export(typeof(IShellDaemon))]
public class ErrorReporter : IShellDaemon, IDisposable {
readonly ErrorReportService _svc;
@@ -21,9 +19,9 @@ namespace bts.spark
IDisposable _dispose;
[ImportingConstructor]
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
public ErrorReporter(ErrorReportService svc, IBus bus, UserTuple user) {
_svc = svc;
_username = username;
_username = user.name;
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
}
@@ -48,6 +46,45 @@ namespace bts.spark
ErrorMessage = message,
});
}
public async Task report_silently(string contact, string description, string userdata) {
var request = append_logs(new ErrorReport {
User = _username,
ErrorMessage = description,
UserDescription = userdata,
Contact = contact
}.immutable());
var reply = await _svc.send_report_async(request);
}
public static ErrorReportImmutable append_logs(ErrorReportImmutable src) {
var result = src.CloneToMutable();
var windows_log = LogUtil.read_windows_event_log();
if (!windows_log.IsEmpty) {
result.Files.Add(new FileData {
Name = "Application.evtx.gz",
Contents = windows_log
});
}
var (data,date) = LogUtil.read_latest_crash_dump();
if (!data.IsEmpty) {
result.Files.Add(new FileData {
Name = $"spark-{date.ToString("s")}.dmp.gz",
Contents = data
});
}
var spark_log = LogUtil.read_logs_compressed();
if (!spark_log.IsEmpty) {
result.Files.Add(new FileData {
Name = "log.gz",
Contents = spark_log
});
}
return result.immutable();
}
}
static class ErrorReporterExtensions {
+13 -21
View File
@@ -1,5 +1,6 @@
using bts.api;
using bts.api.synthetic;
using bts.md;
using mbts;
using mbts.co2;
using mbts.spark;
@@ -7,15 +8,10 @@ using mbts.ui;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Linq;
using SecurityKey = System.UInt32;
using System.Collections.Concurrent;
using System.Threading;
using bts.api.user;
using bts.md;
using mbts.ui.shell;
namespace bts {
[Export(typeof(IInitializable))]
@@ -25,17 +21,21 @@ namespace bts {
readonly Lazy<SyntheticSecurityService> _ss;
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
[ImportingConstructor]
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, UserTuple user)
: base(mbts.config.Configuration.get<md.RelayOptions>().naggle_ms, MDSourceOptions.DeferInit, user) {
_bus = bus;
_token = token;
_ss = ss;
_ss = ss;
}
override public void initialize() {
public int priority => 55;
[Export]
MDConsumer consumer => _consumer;
IBus bus => _bus.Value;
public override void initialize() {
_relay.set_token(_token.Value);
_dispose.Add(
_ss.Value.subscribei_metadata()
@@ -60,16 +60,8 @@ namespace bts {
// Post full dictionary of syntheics here
bus.post(SparkTags.SyntheticstrategiesTag, t);
}));
_dispose.Add( this.on_securities.Subscribe( (md) => {
bus.post(SparkTags.SecuritiesTag, md);
}));
_dispose.Add(on_securities.Subscribe(md => bus.post(SparkTags.SecuritiesTag, md)));
Task.Run(run);
}
[Export]
MDConsumer consumer => _consumer;
public int priority => 55;
IBus bus => _bus.Value;
}
}
+55
View File
@@ -0,0 +1,55 @@
using System;
using System.ComponentModel.Composition;
using mbts.ui;
using mbts;
using Newtonsoft.Json;
using System.Threading.Tasks;
namespace bts.spark {
[Export(typeof(IShellDaemon))]
class OopsDaemon : IShellDaemon, IDisposable {
[ImportingConstructor]
public OopsDaemon(ErrorReporter e) {
var pid = get_pid();
if (pid.HasValue) {
handle_unclean_shutdown(e, pid.Value);
}
save_pid();
}
public void Dispose() {
clear_pid();
}
void handle_unclean_shutdown(ErrorReporter e, int oldpid) {
string extra = "";
try {
var conf = mbts.config.Configuration.get<bts.config.System>();
extra = JsonConvert.SerializeObject(conf, Formatting.Indented);
}
catch { }
Task.Run(() => e.report_silently(
$"Host: {Environment.MachineName}",
$"pid: {oldpid}\n{extra}",
"Automated crash report")
);
}
const string _subkey = @"Software\BTS";
private void save_pid() {
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
key.SetValue("pid", System.Diagnostics.Process.GetCurrentProcess().Id, Microsoft.Win32.RegistryValueKind.DWord);
}
}
private void clear_pid() {
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
key.DeleteValue("pid",false);
}
}
private int? get_pid() {
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
return key.GetValue("pid") as int?;
}
}
}
}
@@ -43,6 +43,11 @@ namespace bts.shell {
void _show_order_window(Window window) {
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
window.Resources[kv.Key] = kv.Value;
// the dynamic algo 'SecurityKey' template has a ContractPickerPopup that expects the security map to be available as a DynamicResource.
var sub = _bus.subscribe(SparkTags.SecuritiesTag)
.OnDispatcher()
.Subscribe(sec_map => window.Resources[ResourceKeys.Securities] = sec_map);
window.Closed += (s, e) => Disposable.dispose(ref sub);
window.Show(-200, -160, true);
}
@@ -3,17 +3,13 @@ using bts.metrics;
using mbts;
using mbts.config;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
namespace bts.perf {
[Export, Export(typeof(IShellDaemon))]
class ShellLatencyDaemon : IShellDaemon, IDisposable {
readonly ShellLatencyService _service;
@@ -24,10 +20,10 @@ namespace bts.perf {
Ping _pinger = new Ping();
[ImportingConstructor]
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
public ShellLatencyDaemon(ShellLatencyService service, UserTuple user) {
_service = service;
_version = Configuration.get<bts.config.System>().version;
_username = user;
_username = user.name;
_hostname = Environment.MachineName;
_forever_ping_host(_pinger);
@@ -105,4 +101,4 @@ namespace bts.perf {
Disposable.dispose(ref _subs);
}
}
}
}
@@ -5,7 +5,6 @@ using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
@@ -13,13 +12,11 @@ using System.Threading.Tasks;
using System.Windows.Media;
using static mbts.ui.shell.ProtoBufSerializationHelper;
namespace bts.shell
{
namespace bts.shell {
[Export(typeof(IShellModule))]
class MarketShellModule : IShellModule
{
class MarketShellModule : IShellModule {
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -227,7 +227,7 @@ namespace bts.shell {
public string strip_name;
[ProtoMember(26)]
public string last_price_from_spread;
[ProtoMember(27)]
[ProtoMember(28)]
public string mic_code;
}
@@ -227,7 +227,7 @@ namespace mbts.ui.spark {
}
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter) { Width = 75, };
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter);
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
return col;
@@ -1,7 +1,9 @@
using bts.api;
using bts;
using bts.api;
using bts.api.core;
using bts.api.synthetic;
using bts.collections;
using bts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -18,11 +20,36 @@ namespace mbts.spark {
readonly uint _user_id;
[ImportingConstructor]
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
public SpreaderModel(IBus bus, SyntheticSecurityService service, AdminModel admin_model, ErrorReporter error, UserTuple user) {
_bus = bus;
_service = service;
_error = error;
_user_id = user_id;
_user_id = user.id;
// synthetic tuples
var strategy_source = _bus.subscribe(SparkTags.SyntheticstrategiesTag)
.Select(map => MappedDictionary.create<uint, ExecutionStrategyImmutable, (uint security_id, ExecutionStrategyImmutable strategy)>(map, (k, v) => (k, v)))
.Table(tup => tup.security_id);
var synthetic_source = _bus.subscribe(SparkTags.SecuritiesTag)
.Select(map => new FilteredDictionary<uint, SecurityMD>(map, smd => smd.security.ContractCase == Security.ContractOneofCase.Synthetic))
.Table(smd => smd.security.Id);
var synthetic_user_join = synthetic_source.LeftJoin(
admin_model.all_user_tuples,
SecurityMD.key_fn,
UserTuple.key_fn,
smd => smd.security.Synthetic.UserId,
(smd, sid, ut, uid) => new SyntheticTuple(smd.security, ut.name, null),
(smd, sid, uid) => new SyntheticTuple(smd.security, null, null),
SyntheticTuple.key_fn);
synthetic_tuples = synthetic_user_join.LeftJoin(
strategy_source,
SyntheticTuple.key_fn,
tup => tup.security_id,
(synth_tup, id, strat_tup) => new SyntheticTuple(synth_tup.security, synth_tup.user_name, strat_tup.strategy),
(synth_tup, id) => synth_tup,
SyntheticTuple.key_fn);
}
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
@@ -36,6 +63,8 @@ namespace mbts.spark {
.LeftJoin(strategies, (security, strategy) => (new ExecutionStrategyImmutable?(strategy), security), security => (new ExecutionStrategyImmutable?(), security))
);
public IObservable<TableUpdate<SyntheticTuple, uint>> synthetic_tuples { get; }
public IObservable<ExecutionStrategyImmutable> security_strategy(uint security_key) =>
strategy.TrySelect(s => {
bool success = s.TryGetValue(security_key, out ExecutionStrategyImmutable result);
@@ -81,9 +81,9 @@ namespace bts.shell {
#region QuoteParams
public static QuoteParams QuoteParams => new QuoteParams {
LeanQuantityRatio = 1,
QuoteInsideMarketDistance = QuoteInsideMarketDistance,
//QuoteInsideMarketDistance = QuoteInsideMarketDistance,
QuoteMaxOrderMove = QuoteMaxOrderMove,
QuoteMuteZone = QuoteMuteZone,
//QuoteMuteZone = QuoteMuteZone,
QuoteRepriceThrottle = QuoteRepriceThrottle,
QuoteInsideMarketDistanceWithTravel = QuoteInsideMarketDistanceWithTravel,
};
@@ -113,6 +113,7 @@ namespace bts.shell {
};
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
#endregion
#region PreHedge
@@ -7,8 +7,15 @@ namespace mbts.ui.spark {
/// Interaction logic for SyntheticManagementView.xaml
/// </summary>
partial class SyntheticManagementView : UserControl {
public enum Columns {
Name,
Description,
n_legs,
n_working_orders,
SecurityKey,
User
}
public enum Columns { Name, Description, n_legs, n_working_orders, SecurityKey}
public SyntheticManagementView() =>
InitializeComponent();
}
@@ -10,6 +10,7 @@ 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;
@@ -27,7 +28,7 @@ namespace mbts.spark {
readonly IWindowSettingsManager<GridSettings> _settings_manager;
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
_orders_model = orders_model;
_state = state;
@@ -66,8 +67,8 @@ namespace mbts.spark {
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
current_row_connectable_builder.create(observe_row_strategy),
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
), SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder).complete(
vm_connectable_builder.create(vm => vm.current_security_observable.Select(s => s.HasValue ? ImmutableHashSet<uint>.Empty.Add(s.Value.Id) : null)), id => id != null
), SparkCommands.ShowOrders.complete(() => {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
@@ -1,4 +1,5 @@
using bts.api;
using bts.settings;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -7,6 +8,7 @@ using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
@@ -33,13 +35,8 @@ namespace bts.shell {
public async void show(SparkTags.Nothing nothing) =>
_bus.post(SparkTags.ShowWindow, await load(new StartupData()));
IShellWindow _create(StartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.synthetic_management_grid,
set: (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
return new SyntheticManagementWindow(this, _bus, _orders_model, state, settings_manager);
}
IShellWindow _create(StartupData state) =>
new SyntheticManagementWindow(this, _bus, _orders_model, state, _settings_hub.create_synth_settings_manager(state.grid));
[ProtoContract]
public class StartupData {
@@ -67,6 +64,7 @@ namespace bts.shell {
public static GridColumnSettings n_legs => new GridColumnSettings(col_id(Columns.n_legs), DefaultColumnHeaders.N_Legs, width: DefaultColumnWidths.N_Legs);
public static GridColumnSettings n_working_orders => new GridColumnSettings(col_id(Columns.n_working_orders), DefaultColumnHeaders.N_Working_Orders, width: DefaultColumnWidths.N_Working_Orders);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, is_visible: false, width: DefaultColumnWidths.User);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
@@ -81,6 +79,7 @@ namespace bts.shell {
yield return n_legs;
yield return n_working_orders;
yield return security_key;
yield return user;
}
}
@@ -101,6 +100,15 @@ namespace bts.shell {
public UIntMinMaxState n_working_orders;
[ProtoMember(6)]
public string security_key;
[ProtoMember(7)]
public uint[] user;
}
// migration rules
[Export(typeof(ISyntheticManagementMigrationRule))]
class AddMissingColumnsMigrationRule : ISyntheticManagementMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
}
}
}
}
@@ -0,0 +1,33 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteInsideMarketDistanceEnabledModel : RowModelBase<bool> {
readonly QuoteInsideMarketDistanceTowardModel _toward;
public QuoteInsideMarketDistanceEnabledModel(QuoteInsideMarketDistanceTowardModel toward) =>
_toward = toward;
public override string display_name { get; } = string.Empty;
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle != null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle == null) {
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceThrottle;
_toward.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = null;
}
_toward.set_enabled(leg_tup, value);
}
}
}
}
@@ -0,0 +1,39 @@
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteInsideMarketDistanceTowardModel : RowModelBase<uint> {
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public override string display_name { get; } = "Toward Distance";
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup).OnTowardDistance = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance ?
inside_market_distance.OnTowardDistance :
_value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.SecurityKey] = value;
if (_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance)
inside_market_distance.OnTowardDistance = value;
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_inside_market_distance(leg_tup) != null;
QuoteInsideMarketDistanceThrottle _get_inside_market_distance(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey);
}
}
}
@@ -115,6 +115,11 @@ namespace mbts.spark {
"dictates if this logic applies to the quoting market or to the synthetic spread " +
"market");
public static ParameterSection InsideMarketDistance =>
new ParameterSection("Inside Market Distance", true,
"Reduces re-quoting by throttling quotes that are moving toward the inside market " +
"but are more than 'Toward Distance' away from the inside market");
public static ParameterSection InsideMarketThrottleThresholds =>
new ParameterSection("Inside Market Throttle Thresholds", true,
"Reduces re-quoting for quotes that are moving toward the inside market. " +
@@ -190,6 +190,8 @@ namespace mbts.spark {
var quote_inside_market_throttle_ticks = new QuoteInsideMarketThrottleTicksModel();
var quote_inside_market_throttle_min_move = new QuoteInsideMarketThrottleMinMoveModel();
var quote_inside_market_distance_toward = new QuoteInsideMarketDistanceTowardModel();
var pre_hedge_payless_threshold = new PreHedgePaylessThresholdModel();
var pre_hedge_payless_ticks = new PreHedgePaylessTicksModel();
@@ -227,6 +229,8 @@ namespace mbts.spark {
//quote_inside_market_throttle_ticks,
//quote_inside_market_throttle_min_move,
//new MaxTicksInsideMarketModel(),
new QuoteInsideMarketDistanceEnabledModel(quote_inside_market_distance_toward),
quote_inside_market_distance_toward,
new PreHedgePaylessEnabledModel(pre_hedge_payless_threshold, pre_hedge_payless_ticks),
pre_hedge_payless_threshold,
pre_hedge_payless_ticks,
@@ -1,12 +1,15 @@
using bts.api;
using bts;
using bts.api;
using bts.collections;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
@@ -17,43 +20,45 @@ using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.SyntheticManagementView;
namespace mbts.spark {
class SyntheticManagementDialogVM : SecurityMDGrid<SyntheticManagementDialogVM.Row, (ExecutionStrategyImmutable? strategy, SecurityMD md)> {
class SyntheticManagementDialogVM : SecurityGrid<SyntheticManagementDialogVM.Row, SyntheticTuple> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager) : this(bus, orders_model, settings_manager, null, null) { }
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
: base(orders_model.spreader.strategy_tuple.Select(map => map.Values), sorting) {
: base(orders_model.spreader.synthetic_tuples.Select(t => t.state.Values), sorting) {
_bus = bus;
_orders_model = orders_model;
_settings_manager = settings_manager;
// init filters
user_filter = DiscreteValuesFilterVM.CreateTableUpdate<Row, UserTuple, uint>(r => r.user_tuple, UserTuple.key_fn, out var user_filter_items_observer, out var user_filter_items_view, nameof(Row.user_tuple));
user_filter_items_view.CustomSort = Comparer<string>.Default.wrap<string, UserTuple>(ut => ut.name).to_non_generic();
if (filters != null) {
create_time_filter.load_state(filters.create_time);
name_filter.load_state(filters.name);
description_filter.load_state(filters.description);
n_legs_filter.load_state(filters.n_legs);
n_working_orders_filter.load_state(filters.n_working_orders);
security_key_filter.load_state(filters.security_key);
security_key_filter.load_state(filters.security_key);
user_filter.load_state(filters.user);
}
_settings_manager = settings_manager;
// columns
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
columns.Add(new HyperlinkElementColumn<Row, Columns>(
Columns.n_working_orders,
new Binding(nameof(Row.n_working_orders)),
Columns.n_working_orders,
new Binding(nameof(Row.n_working_orders)),
new Binding(nameof(Row.show_order_book)),
n_working_orders_filter,
DataGridFilterKnownTemplates.UIntMinMax) {
id = col_id(Columns.n_working_orders),
Header = DefaultColumnHeaders.N_Working_Orders,
Width = DefaultColumnWidths.N_Working_Orders
}
);
});
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user_name, nameof(Row.user_name), user_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
@@ -63,42 +68,39 @@ namespace mbts.spark {
Width = width
});
//
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>(), row_vms);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter),
orders_model.admin_model.all_user_tuples.OnDispatcher().Subscribe(user_filter_items_observer),
DataGridFilterHelper.hook_all<SyntheticManagementDialogVM, Row>(this, rows_view),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
}
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
new Row(_bus, row_model.md, row_model.strategy, _orders_model);
protected override void on_row_removed(Row row) => row.Dispose();
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
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 StartupData create() =>
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
#region Filters
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
public IDataGridColumnPersistentFilter<Row, string> description_filter { get; } = new RegexFilterVM<Row>(r => r.description, nameof(Row.description));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> n_legs_filter { get; } = new IntMinMax<Row>(r => r.n_legs, nameof(Row.n_legs));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, uint[]> user_filter { get; }
public GridSettings get_grid_save_state() =>
_settings_manager.instance_settings == null ? null : grid_settings_manager.create();
public StartupData create() =>
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
protected override Row row_factory(SyntheticTuple row_model) =>
new Row(_bus, row_model, _orders_model);
protected override void on_row_removed(Row row) =>
row.Dispose();
FilterStates _get_filter_states() =>
new FilterStates {
@@ -107,19 +109,19 @@ namespace mbts.spark {
name = name_filter.get_state(),
n_legs = n_legs_filter.get_state(),
n_working_orders = n_working_orders_filter.get_state(),
security_key = security_key_filter.get_state()
security_key = security_key_filter.get_state(),
user = user_filter.get_state()
};
#endregion
public class Row : SecurityMDRow, IEquatable<Row>, IRowVM, IActionableRowVM {
public readonly ExecutionStrategyImmutable? strategy;
public class Row : SecurityRow, IEquatable<Row>, IRowVM, IActionableRowVM {
public readonly SyntheticTuple synthetic_tuple;
readonly IBus _bus;
readonly Property<uint> _n_working_orders;
public Row(IBus bus, SecurityMD security_md, ExecutionStrategyImmutable? strategy, OrdersModel orders_model) : base(security_md) {
public Row(IBus bus, SyntheticTuple synthetic_tuple, OrdersModel orders_model) : base(synthetic_tuple.security) {
_bus = bus;
this.strategy = strategy;
this.synthetic_tuple = synthetic_tuple;
_n_working_orders = backing(nameof(n_working_orders),
orders_model.on_security_orders(security_key)
.Select(t => (uint)(t.state
@@ -133,25 +135,23 @@ namespace mbts.spark {
show_order_book = new DelegateCommand(_show_order_book);
}
public event Action invalidated;
public DateTime create_time => DateTime.MinValue; //TODO
public string name { get; }
public string user_name => synthetic_tuple.user_name;
public UserTuple user_tuple => new UserTuple(synthetic_tuple.security.Synthetic.UserId, user_name);
public int n_legs { get; }
public string description { get; }
public ExecutionStrategyImmutable? strategy => synthetic_tuple.strategy;
public bool strategy_present => strategy.HasValue;
public uint n_working_orders => _n_working_orders.Value;
public ICommand show_ladder { get; }
public ICommand show_order_book { get; }
public ICommand default_command => show_ladder;
public ObservableCollection<RowAnimation> row_animations => new ObservableCollection<RowAnimation>();
public IReadOnlyList<IGridCellStore> cell_stores => new IGridCellStore[0];
public event Action invalidated;
public void raise_invalidated() => invalidated?.Invoke();
public void raise_invalidated() =>
invalidated?.Invoke();
public bool Equals(Row other) =>
other != null &&
@@ -163,10 +163,8 @@ namespace mbts.spark {
public override int GetHashCode() =>
security_key.GetHashCode();
void _show_ladder() =>
_bus.post(SparkTags.ShowLadderTag, md);
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet<uint>.Empty.Add(security.Id));
void _show_order_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
@@ -177,4 +175,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -62,7 +62,10 @@ namespace mbts.spark {
_vm = new AlgoStudioVM(_model);
_vm.load_from(_state);
_view = new AlgoStudioView { DataContext = _vm };
_resources = new CompositeDisposable();
_resources = new CompositeDisposable(
_bus.subscribe(SparkTags.SecuritiesTag)
.OnDispatcher()
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
// add definition
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
// clone definition
@@ -27,7 +27,7 @@ namespace mbts.spark {
_error_reporter = error_reporter;
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
@@ -13,6 +13,9 @@ namespace mbts.spark {
string _template_key;
ICommand _set_value_cmd;
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent)
: this(algo, param, parent, (param as ITemplateSelectorKey)?.key) { }
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
this.param = param;
this.algo = algo;
@@ -247,6 +247,31 @@
</TextBlock>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
<DataTemplate>
<Grid>
<!--Note: Ideally, this 'securities BindingProxy' would be a static resource, but it only seems to detect the dynamic securities when it is in the visual tree.-->
<!--<Grid.Resources>
<u:BindingProxy x:Key="SecuritiesBindingProxy" x:Shared="False" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
</Grid.Resources>-->
<u:BindingProxy x:Name="SecuritiesBindingProxy" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
<TextBlock>
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:DictionaryLookupConverter/>
<x:Static MemberType="cv:ValueConverters" Member="SecuritySelectorText"/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="Value" ElementName="SecuritiesBindingProxy"/>
<Binding Path="param.value"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -320,6 +345,21 @@
</usp:PriceEntryControl>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
<DataTemplate>
<c:ContractPickerPopup
CloseOnCancel="True"
CloseOnOK="True"
Header="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
IsEditMode="False"
IsLazy="True"
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
SelectedSecurityKey="{Binding param.value}"
ShowDialogSkirt="True"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityKeyHandler}"
/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -118,8 +118,8 @@ namespace mbts.spark {
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
// all others (easily identified by the enum 'Columns.Foo')
add_col(Columns.FillTime, DefaultColumnHeaders.FillTime, r => r.fill_time.ToString("M/d h:mm:ss tt"), new TimeFilterVM<FillTuple>(r => r.fill.Timestamp.ToDateTimeOffset().LocalDateTime), DataGridFilterKnownTemplates.TimeFilter, DefaultColumnWidths.Time);
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values.Ordered()), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
@@ -147,6 +147,7 @@ namespace mbts.spark {
});
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
fills_source.OnDispatcher().Subscribe(t => {
last_unfiltered_fills = t;
@@ -284,7 +285,6 @@ namespace mbts.spark {
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
//public OrderType root_algo_type => _or
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
@@ -303,7 +303,7 @@ namespace mbts.spark {
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
public OrderMetadata root_algo_order_metadata => _model.factory.get_metadata(root_algo_order_type);
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.display_name();
public string root_algo_display_name => fill_tuple.root_order_security?.display_name() ?? OrderType.None.display_name();
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
public override bool Equals(object obj) => Equals(obj as Row);
@@ -75,7 +75,7 @@ namespace bts.shell.fills {
[DIYGridColumnID(nameof(Columns.Symbol))]
public string[] symbol { get; set; }
[ProtoMember(16)]
[ProtoMember(17)]
[DIYGridColumnID(nameof(Columns.FillStatus))]
public Fill.Types.FillStatus[] fill_status { get; set; }
}
@@ -23,6 +23,10 @@ namespace mbts.spark {
// TODO: somehow allow windows that are using the default settings to still have individual column widths.
[ProtoContract]
class LadderColumnSettings : IEquatable<LadderColumnSettings> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderColumnSettings).GetHashCode();
public static LadderColumnSettings Default => new LadderColumnSettings {
show = true,
width = 50,
@@ -74,14 +78,7 @@ namespace mbts.spark {
other.show == show;
public override bool Equals(object obj) => Equals(obj as LadderColumnSettings);
public override int GetHashCode() =>
width.GetHashCode() +
background.GetHashCode() +
foreground.GetHashCode() +
font_family.GetHashCode() +
font_size.GetHashCode() +
font_weight.GetHashCode();
public override int GetHashCode() => _type_hash;
public static bool operator ==(LadderColumnSettings x, LadderColumnSettings y) => x.safe_equals(y);
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
@@ -89,6 +86,9 @@ namespace mbts.spark {
[ProtoContract]
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(HistogramBarAnimation).GetHashCode();
[ProtoMember(1)]
public ColorShowProto bars { get; set; }
[ProtoMember(2)]
@@ -102,14 +102,14 @@ namespace mbts.spark {
show_numbers == other.show_numbers;
public override bool Equals(object obj) => Equals(obj as HistogramBarAnimation);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
bars.GetHashCode(),
show_numbers.GetHashCode());
public override int GetHashCode() => _type_hash;
}
[ProtoContract]
class VolumeColumnSettings : IEquatable<VolumeColumnSettings> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(VolumeColumnSettings).GetHashCode();
public static VolumeColumnSettings Default {
get {
var gen = LadderColumnSettings.Default;
@@ -149,19 +149,14 @@ namespace mbts.spark {
show_ltq.Equals(other.show_ltq);
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
general.GetHashCode(),
buy.GetHashCode(),
sell.GetHashCode(),
passive.GetHashCode(),
total.GetHashCode(),
show_ltq.GetHashCode());
public override int GetHashCode() => _type_hash;
}
[ProtoContract]
class LadderMarketBreakLine : IEquatable<LadderMarketBreakLine> {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderMarketBreakLine).GetHashCode();
[ProtoMember(1)]
public bool show { get; set; }
[ProtoMember(2)]
@@ -184,8 +179,7 @@ namespace mbts.spark {
Equals(obj as LadderMarketBreakLine);
public override int GetHashCode() =>
show.GetHashCode() +
(int)color;
_type_hash;
}
[ProtoContract]
@@ -255,6 +249,11 @@ namespace mbts.spark {
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
public static readonly IEqualityComparer<LadderSettings> profile_equality_comparer =
EqualityComparer.Create<LadderSettings>(profile_equals);
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderSettings).GetHashCode();
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
@@ -497,6 +496,9 @@ namespace mbts.spark {
[ProtoMember(55)]
public LadderMiniOrderBookSettings mini_ob { get; set; }
[ProtoMember(57)]
public double left_panel_scroll_position { get; set; }
public void sanitize() =>
sanitize(this);
@@ -533,112 +535,81 @@ namespace mbts.spark {
return s;
}
/// <summary>
/// Determines whether or not the specified LadderSettings are 'profile equivalent.'
/// This means that 2 ladders, each with a different one of the specified settings, could share the same profile.
/// </summary>
///
/// Note: the impetus for this version of equality is to avoid disconnecting a ladder from its profile due to 'trivial' settings changes.
/// Consider the '<see cref="default_algo"/>' property:
/// If a ladder is using a settings profile with Limit as the default algo, and the user decides to send a 'Market' order,
/// the ladder would suddenly be disconnected from the profile, because it's settings state would no longer be equal to the profile's settings,
/// since they changed the selected algo type to 'Market.'
/// To avoid such travesties, a separate equals logic is needed: this is called 'profile-equivalent' and is implemented in <see cref="profile_equals)"/>.
/// Devs: be sure to account for properties that are excluded in the 'profile-equals' logic in the [regular]'equals' logic.
public static bool profile_equals(LadderSettings a, LadderSettings b) =>
a == null ? b == null :
b != null &&
a.row_height == b.row_height &&
a.grid_line_color == b.grid_line_color &&
a.orders_column.safe_equals(b.orders_column) &&
a.bid_column.safe_equals(b.bid_column) &&
a.ask_column.safe_equals(b.ask_column) &&
a.price_column.safe_equals(b.price_column) &&
a.last_column.safe_equals(b.last_column) &&
a.piq_column.safe_equals(b.piq_column) &&
a.calculator_buttons.safe_equals(b.calculator_buttons, Enumerable.SequenceEqual) &&
a.scroll_bar_visible == b.scroll_bar_visible &&
a.default_account == b.default_account &&
a.default_time_in_force == b.default_time_in_force &&
a.size == b.size &&
a.animate_recenter == b.animate_recenter &&
a.badge_style == b.badge_style &&
a.auto_clear == b.auto_clear &&
a.bid_break_line.safe_equals(b.bid_break_line) &&
a.ask_break_line.safe_equals(b.ask_break_line) &&
a.mid_break_line.safe_equals(b.mid_break_line) &&
a.alternate_order_size == b.alternate_order_size &&
a.session_low_line.safe_equals(b.session_low_line) &&
a.session_high_line.safe_equals(b.session_high_line) &&
a.session_range_fill.safe_equals(b.session_range_fill) &&
a.volume_column.safe_equals(b.volume_column) &&
a.size_display.safe_equals(b.size_display) &&
a.day_visible == b.day_visible &&
a.high_visible == b.high_visible &&
a.low_visible == b.low_visible &&
a.open_visible == b.open_visible &&
a.settle_visible == b.settle_visible &&
a.volume_visible == b.volume_visible &&
a.account_visible == b.account_visible &&
a.tif_visible == b.tif_visible &&
a.order_type_visible == b.order_type_visible &&
a.account_position_visible == b.account_position_visible &&
a.day_position_visible == b.day_position_visible &&
a.raw_day_position_visible == b.raw_day_position_visible &&
a.size_presets_visible == b.size_presets_visible &&
a.cancel_buys_visible == b.cancel_buys_visible &&
a.cancel_sells_visible == b.cancel_sells_visible &&
a.cancel_all_visible == b.cancel_all_visible &&
a.auto_clear_visible == b.auto_clear_visible &&
a.clear_visible == b.clear_visible &&
a.order_entry_panel_initial_width == b.order_entry_panel_initial_width &&
a.zoom_slider_visible == b.zoom_slider_visible &&
a.zoom_factor == b.zoom_factor &&
a.use_defaults == b.use_defaults &&
a.favorite_algos == b.favorite_algos &&
a.net_change == b.net_change &&
a.show_warn_on_cross == b.show_warn_on_cross &&
a.mini_ob == b.mini_ob;
public bool Equals(LadderSettings other) =>
other != null &&
row_height == other.row_height &&
grid_line_color == other.grid_line_color &&
orders_column.safe_equals(other.orders_column) &&
bid_column.safe_equals(other.bid_column) &&
ask_column.safe_equals(other.ask_column) &&
price_column.safe_equals(other.price_column) &&
last_column.safe_equals(other.last_column) &&
piq_column.safe_equals(other.piq_column) &&
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
scroll_bar_visible == other.scroll_bar_visible &&
default_account == other.default_account &&
profile_equals(this, other) &&
default_algo == other.default_algo &&
default_time_in_force == other.default_time_in_force &&
size == other.size &&
animate_recenter == other.animate_recenter &&
badge_style == other.badge_style &&
auto_clear == other.auto_clear &&
bid_break_line.safe_equals(other.bid_break_line) &&
ask_break_line.safe_equals(other.ask_break_line) &&
mid_break_line.safe_equals(other.mid_break_line) &&
alternate_order_size == other.alternate_order_size &&
session_low_line.safe_equals(other.session_low_line) &&
session_high_line.safe_equals(other.session_high_line) &&
session_range_fill.safe_equals(other.session_range_fill) &&
volume_column.safe_equals(other.volume_column) &&
size_display.safe_equals(other.size_display) &&
day_visible == other.day_visible &&
high_visible == other.high_visible &&
low_visible == other.low_visible &&
open_visible == other.open_visible &&
settle_visible == other.settle_visible &&
volume_visible == other.volume_visible &&
account_visible == other.account_visible &&
tif_visible == other.tif_visible &&
order_type_visible == other.order_type_visible &&
account_position_visible == other.account_position_visible &&
day_position_visible == other.day_position_visible &&
raw_day_position_visible == other.raw_day_position_visible &&
size_presets_visible == other.size_presets_visible &&
cancel_buys_visible == other.cancel_buys_visible &&
cancel_sells_visible == other.cancel_sells_visible &&
cancel_all_visible == other.cancel_all_visible &&
auto_clear_visible == other.auto_clear_visible &&
clear_visible == other.clear_visible &&
order_entry_panel_initial_width == other.order_entry_panel_initial_width &&
zoom_slider_visible == other.zoom_slider_visible &&
zoom_factor == other.zoom_factor &&
use_defaults == other.use_defaults &&
favorite_algos == other.favorite_algos &&
net_change == other.net_change &&
show_warn_on_cross == other.show_warn_on_cross &&
mini_ob == other.mini_ob;
left_panel_scroll_position == other.left_panel_scroll_position;
public override bool Equals(object obj) => Equals(obj as LadderSettings);
// TODO: remove mutable props from hash logic (or conversely, make them immutable).
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
row_height.GetHashCode(),
grid_line_color.GetHashCode(),
EqualityComparer.safe_get_hash_code(orders_column),
EqualityComparer.safe_get_hash_code(bid_column),
EqualityComparer.safe_get_hash_code(ask_column),
EqualityComparer.safe_get_hash_code(price_column),
EqualityComparer.safe_get_hash_code(last_column),
EqualityComparer.safe_get_hash_code(piq_column),
EqualityComparer.safe_get_hash_code(calculator_buttons),
scroll_bar_visible.GetHashCode(),
default_time_in_force.GetHashCode(),
size.GetHashCode(),
animate_recenter.GetHashCode(),
badge_style.GetHashCode(),
EqualityComparer.safe_get_hash_code(bid_break_line),
EqualityComparer.safe_get_hash_code(ask_break_line),
EqualityComparer.safe_get_hash_code(mid_break_line),
alternate_order_size.GetHashCode(),
EqualityComparer.safe_get_hash_code(session_low_line),
EqualityComparer.safe_get_hash_code(session_high_line),
EqualityComparer.safe_get_hash_code(session_range_fill),
EqualityComparer.safe_get_hash_code(volume_column),
EqualityComparer.safe_get_hash_code(size_display),
day_visible.GetHashCode(),
high_visible.GetHashCode(),
low_visible.GetHashCode(),
open_visible.GetHashCode(),
settle_visible.GetHashCode(),
volume_visible.GetHashCode(),
account_visible.GetHashCode(),
tif_visible.GetHashCode(),
order_type_visible.GetHashCode(),
account_position_visible.GetHashCode(),
size_presets_visible.GetHashCode(),
cancel_buys_visible.GetHashCode(),
cancel_sells_visible.GetHashCode(),
cancel_all_visible.GetHashCode(),
auto_clear_visible.GetHashCode(),
clear_visible.GetHashCode(),
order_entry_panel_initial_width.GetHashCode(),
zoom_slider_visible.GetHashCode(),
zoom_factor.GetHashCode(),
use_defaults.GetHashCode(),
favorite_algos.GetHashCode(),
net_change.GetHashCode(),
show_warn_on_cross.GetHashCode());
public override int GetHashCode() => _type_hash;
public struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
@@ -1,4 +1,5 @@
using bts.api;
using bts.collections;
using bts.md;
using mbts;
using mbts.config;
@@ -50,6 +51,7 @@ namespace bts.shell {
_mbo = mbo;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderAutoLookupTag).Subscribe(_show_windows),
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
);
@@ -79,11 +81,9 @@ namespace bts.shell {
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
// profile was deleted
id = 0;
} else {
if (!s.Equals(result.value)) {
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
id = 0;
}
} else if (!LadderSettings.profile_equals(s, result.value)) {
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
id = 0;
}
}
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
@@ -147,7 +147,7 @@ namespace bts.shell {
var curr = vm.get_save_state();
if (settings_id == 0) {
last_value = curr;
} else if (!curr.Equals(last_value)) {
} else if (!LadderSettings.profile_equals(curr, last_value)) {
settings_id = 0;
last_value = curr;
}
@@ -161,7 +161,7 @@ namespace bts.shell {
// not on a profile, don't respond to default settings changes
return;
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
if (!last_value.Equals(vm.get_save_state())) {
if (!LadderSettings.profile_equals(last_value, vm.get_save_state())) {
settings_id = 0;
} else {
settings_subject.OnNext(last_value = profile.value);
@@ -209,6 +209,7 @@ namespace bts.shell {
_ => _,
vm.get_save_state(),
UserSettings.FileExtensions.LADDER,
LadderSettings.profile_equals,
settings_id);
dialog_vm.applied += apply;
// dialog
@@ -273,7 +274,21 @@ namespace bts.shell {
});
}
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
async void _show_windows(IImmutableSet<uint> securities) {
if (securities == null)
return;
var start_time = DateTime.Now;
do {
var found = securities.KeyJoin(_securities).ToList();
securities = securities.RemoveRange(found.Select(SecurityMD.key_fn));
show_windows(found);
if (securities.Count == 0)
break;
await Task.Delay(500);
} while ((DateTime.Now - start_time) < _ladder_conf.security_timeout);
}
void show_windows(IEnumerable<SecurityMD> securities) {
int n_allowed = (int)_ladder_conf.max_open_ladders - _wm.list_windows().Where(w => w.window_type == _window_type).Count();
int n_opened = 0;
foreach (var s in securities) {
@@ -35,7 +35,14 @@ namespace mbts.spark {
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double _position, _day_position, _raw_day_position;
double
_position,
_day_position,
_raw_day_position,
_left_panel_extent_height,
_left_panel_view_port_height,
_left_panel_scroll_position,
_left_panel_scroll_position_target;
bool _show_account_position, _show_day_position, _show_raw_day_position;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
@@ -76,6 +83,7 @@ namespace mbts.spark {
_volume_column_width = backing(nameof(volume_column_width), ws.volume_column_width);
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
_left_panel_scroll_position_target = init.left_panel_scroll_position;
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
_volume = backing(nameof(volume), 0ul);
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
@@ -333,6 +341,33 @@ namespace mbts.spark {
public bool show_cancel_sells => _show_cancel_sells.view;
public bool show_cancel_all => _show_cancel_all.view;
public bool show_zoom => _show_zoom.view;
// left panel scroll props
public double left_panel_view_port_height {
get => _left_panel_view_port_height;
set {
using (detect_property_changes(nameof(left_panel_scroll_position)))
_left_panel_view_port_height = value;
}
}
public double left_panel_scroll_position {
get {
if (!_left_panel_scroll_position_target.is_nan()) {
if (_left_panel_scroll_position_target < _max_left_panel_scroll_position) {
_left_panel_scroll_position = _left_panel_scroll_position_target;
_left_panel_scroll_position_target = double.NaN;
}
}
return Math.Min(_left_panel_scroll_position, _left_panel_extent_height);
}
set => Set(ref _left_panel_scroll_position, value);
}
public double left_panel_extent_height {
get => _left_panel_extent_height;
set {
using (detect_property_changes(nameof(left_panel_scroll_position)))
Set(ref _left_panel_extent_height, value);
}
}
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
public string position_display_text {
@@ -364,6 +399,10 @@ namespace mbts.spark {
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings { get; }
double _max_left_panel_scroll_position =>
Math.Max(0, _left_panel_extent_height - _left_panel_view_port_height);
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.price();
@@ -394,6 +433,7 @@ namespace mbts.spark {
zoom_factor,
piq_column_width));
rv.auto_clear = auto_clear;
rv.left_panel_scroll_position = left_panel_scroll_position;
if (order_properties_vm != null) {
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
if (order_properties_vm.selected_account.HasValue)
@@ -451,7 +491,7 @@ namespace mbts.spark {
price = temp_price;
}
}
await _model.order_gateway.send_order(order, price, size);
await _model.order_gateway.send_order(security, order, price, size);
if (auto_clear)
size_calculator.value = 0;
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
@@ -301,7 +301,14 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Grid.RowSpan="2" VerticalScrollBarVisibility="Hidden">
<ScrollViewer
x:Name="_left_panel_scroll"
Grid.Row="0"
Grid.RowSpan="2"
mbts:ScrollViewerBehavior.VerticalOffset="{Binding left_panel_scroll_position, Mode=TwoWay}"
mbts:ScrollViewerBehavior.EnableTwoWayVerticalOffsetBinding="True"
VerticalScrollBarVisibility="Hidden"
>
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
@@ -1,11 +1,17 @@
using bts;
using bts.collections;
using bts.utils;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
@@ -80,6 +86,8 @@ namespace mbts.spark {
_ladder.visible_columns_changed += () => _column_headers_presenter.Columns.SetItems(
_ladder.visible_column_types.Select(t => ColumnFactory.create(_ladder, t)));
this.vm = vm;
this.ConnectDataContext<LadderVM>(lvm => lvm.bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => Resources[ResourceKeys.Securities] = sec_map));
vm.SubscribeDisposed(_connect_left_panel_scroll());
DataContext = vm;
DataContextChanged += datacontext_changed;
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
@@ -87,7 +95,39 @@ namespace mbts.spark {
this.PreviewMouseDown += LadderView_PreviewMouseDown;
}
IDisposable _connect_left_panel_scroll() {
// extent height
bool busy = false;
var extent_height_hook = _left_panel_scroll.ObserveProperty(ScrollViewer.ExtentHeightProperty, (s, e) => {
if (!busy) {
double scroll_pos = vm.left_panel_scroll_position;
busy = true;
Dispatcher.InvokeAsync(() => {
vm.left_panel_extent_height = _left_panel_scroll.ExtentHeight;
if (_left_panel_scroll.VerticalOffset != scroll_pos)
_left_panel_scroll.TryScrollToVerticalOffset(scroll_pos);
busy = false;
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
});
// view port height
_left_panel_scroll.Loaded += on_loaded;
var dispose = Disposable.CreateDisposeOnce(dispose_impl);
return Disposable.Create(dispose, vm.SubscribeDisposed(dispose));
// local methods
void on_loaded(object sender, RoutedEventArgs e) {
if (_left_panel_scroll.descendants_of_type<ScrollBar>().TryGetFirst(sb => sb.Orientation == Orientation.Vertical, out var vscroll)) {
vscroll.SetBinding(ScrollBar.ViewportSizeProperty, new Binding(nameof(LadderVM.left_panel_view_port_height)) {
Converter = ValueConverter.create<double, Size>(d => new Size(0, d), sz => sz.Height),
Mode = BindingMode.OneWayToSource
});
}
}
void dispose_impl() {
Disposable.dispose(ref extent_height_hook);
_left_panel_scroll.Loaded -= on_loaded;
}
}
// (Left|Right) CTRL + Wheel to zoom
// This is neat b/c the user doesn't even need to have the slider visible to zoom
@@ -62,15 +62,15 @@
Header="{Binding security}"
IsEditMode="False"
IsLazy="True"
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
SecurityMap="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Securities}}"
SelectedSecurity="{Binding security, UpdateSourceTrigger=Explicit}"
ShowDialogSkirt="True"
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
/>
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
</Grid>
@@ -6,12 +6,6 @@ namespace mbts.ui.spark {
/// Interaction logic for ManualFillRowEditor.xaml
/// </summary>
partial class ManualFillRowEditor : UserControl {
public const string
SecuritiesKey = nameof(SecuritiesKey),
AccountsKey = nameof(AccountsKey),
UsersKey = nameof(UsersKey),
SidesKey = nameof(SidesKey);
public static readonly RoutedCommand
CommitCommand = new RoutedCommand(),
CancelCommand = new RoutedCommand();
@@ -70,7 +70,7 @@ namespace bts.shell.manual_fills {
mfd => new ManualFillRow(mfd),
mfr => mfr.data),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
security_map = m;
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
RaisePropertyChanged(nameof(security_map));
}),
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
@@ -50,11 +50,11 @@ namespace mbts.ui.spark {
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
// dynamic resources
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
row_editor.Resources[ResourceKeys.Accounts] = vm.accounts;
row_editor.Resources[ResourceKeys.Users] = vm.users;
row_editor.Resources[ResourceKeys.Sides] = vm.sides;
FocusManager.SetIsFocusScope(row_editor, true);
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ResourceKeys.Securities] = v);
// popup adorner
var popup_adorner = new PopupAdorner(
data_grid, data_grid,
@@ -121,7 +121,7 @@ namespace mbts.spark {
r => r.user,
DefaultColumnWidths.User,
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
model.factory.subscribe_users().Select(d => d.Values),
model.factory.subscribe_users().Select(d => d.Values.Ordered()),
ort => ort.order.order.user(),
nameof(OrderBookRowVM.user)),
DataGridFilterKnownTemplates.DiscreteValues,
@@ -133,7 +133,7 @@ namespace mbts.spark {
r => r.account,
DefaultColumnWidths.Account,
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
model.factory.subscribe_accounts().Select(d => d.Values),
model.factory.subscribe_accounts().Select(d => d.Values.Ordered()),
ort => ort.order.order.account(),
vals => vals.ToArray(),
vals => Observable.Return(vals),
@@ -750,6 +750,7 @@ namespace mbts.spark {
};
header_cb.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(OrderBookVM.all_rows_selected)));
this.Header = header_cb;
is_visible = true;
}
public override FrameworkElement view_factory() {
@@ -108,7 +108,7 @@ namespace mbts.spark {
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
public string root_algo_display_name => order_tuple.root.algo.name;
public string root_algo_display_name => order_tuple.root.display_name();
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
@@ -239,7 +239,7 @@ namespace mbts.spark {
grid_settings_manager,
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
order_mod_vm.algo_vm,
order_mod_vm,
attach_vm,
_show_fills.observe_current(true)
.Subscribe(is_visible => {
@@ -288,7 +288,14 @@ namespace mbts.spark {
double price = attach_vm.price;
uint? size = attach_vm.size;
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted : 0u;
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
// Don't pass any order tuples that don't have a security defined.
// The actual security is passed to the OrdersModel - instead of just the security key -
// so it can prevent GlbxIceberg orders from being sent to a non-glbx exchange (such as Ice).
/// Impl details: <see cref="OrdersModel.send_order(SecurityImmutable, OrderDefinitionImmutable?, double, uint)"/>
var order_tups = selected_rows
.Select(r => r.order_tuple)
.Where(ot => ot.order.security.HasValue)
.ToList();
var mod_algo_vm = attach_vm.algo_vm.algo_root;
await Task.WhenAll(order_tups.Select(t => {
var algo = t.order.order.Def.CustomDef.actual().CloneToMutable();
@@ -302,6 +309,7 @@ namespace mbts.spark {
};
}
return _model.send_order(
t.order.security.Value,
t.order.order.Def.with(
attach_target_order_key: t.order.order.Key,
custom_def: algo
@@ -727,17 +735,13 @@ namespace mbts.spark {
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
SecurityMD? _security_md;
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
: this(owner,
AlgoDefinitionVM.create(
owner._model,
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
init_format: init_format,
editing_permissions: init_permissions)) { }
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
_owner = owner;
this.algo_vm = algo_vm;
algo_vm = AlgoDefinitionVM.create(
owner._model,
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
init_format: init_format,
editing_permissions: init_permissions);
_accounts = backing(
nameof(selected_account),
_model.factory.subscribe_account_tuples(),
@@ -776,7 +780,8 @@ namespace mbts.spark {
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
}
}),
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
algo_vm);
// payup methods
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
void payup_ask(int ticks) => payup(bbo.ask, ticks);
@@ -66,6 +66,10 @@ namespace mbts.spark {
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
_vm.SubscribeDisposed(
_bus.subscribe(SparkTags.SecuritiesTag)
.OnDispatcher()
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
@@ -501,6 +501,12 @@ namespace bts.shell.pnl {
account_name == other.account_name &&
sod == other.sod &&
day == other.day &&
bid_price.Equals(other.bid_price) &&
ask_price.Equals(other.ask_price) &&
last_price.Equals(other.last_price) &&
mid_price.Equals(other.mid_price) &&
settlement_price.Equals(other.settlement_price) &&
calc_type.Equals(other.calc_type) &&
raw_day == other.raw_day &&
working.summary == other.working.summary;
@@ -97,22 +97,22 @@ namespace bts.shell.pnl {
if (security.HasValue && security.Value.ContractCase != Security.ContractOneofCase.Future) {
day_long = day_short = sod_long = sod_short = 0;
} else {
day_long = (decimal)DoubleUtil.nan_to_zero(data.day.long_pos);
day_short = (decimal)DoubleUtil.nan_to_zero(data.day.short_pos);
sod_long = (decimal)DoubleUtil.nan_to_zero(data.sod.long_pos);
sod_short = (decimal)DoubleUtil.nan_to_zero(data.sod.short_pos);
day_long = DoubleUtil.round_to_decimal(data.day.long_pos);
day_short = DoubleUtil.round_to_decimal(data.day.short_pos);
sod_long = DoubleUtil.round_to_decimal(data.sod.long_pos);
sod_short = DoubleUtil.round_to_decimal(data.sod.short_pos);
}
var nd = new PNLNormalizedData(
(decimal)DoubleUtil.nan_to_zero(day_pnl.value),
DoubleUtil.round_to_decimal(day_pnl.value),
day_long,
day_short,
(decimal)DoubleUtil.nan_to_zero(data.raw_day.long_pos),
(decimal)DoubleUtil.nan_to_zero(data.raw_day.short_pos),
(decimal)DoubleUtil.nan_to_zero(sod_pnl.value),
DoubleUtil.round_to_decimal(data.raw_day.long_pos),
DoubleUtil.round_to_decimal(data.raw_day.short_pos),
DoubleUtil.round_to_decimal(sod_pnl.value),
sod_long,
sod_short,
(decimal)DoubleUtil.nan_to_zero(data.working_long_pos),
(decimal)DoubleUtil.nan_to_zero(data.working_short_pos));
DoubleUtil.round_to_decimal(data.working_long_pos),
DoubleUtil.round_to_decimal(data.working_short_pos));
if (_normalized_data != nd) {
_normalized_data = nd;
updated?.Invoke(nd);
@@ -103,7 +103,7 @@ namespace bts.shell.pnl {
load_from(settings);
_dispose = Disposable.Create(
pnl_source.Operate(
_calculation_type.observe_current(),
_calculation_type.observe_current(true),
(data, calc) => data.with(calc_type: calc)
).FilterWithRaw(filter_src, PNLRowData.key_fn, null)
.OnDispatcher()
@@ -373,7 +373,9 @@ namespace bts.shell.pnl {
var less_last = data - last_data;
var plus_new = less_last + new_data;
data += new_data - last_data;
Log.assert(plus_new == data);
if (plus_new != data) {
Log.Ui.warn($"Detected PNL arithmetic rounding error in the group sum for '{string.Join(" ", GroupNamePath)}' (this bug is under investigation).");
}
last_data = new_data;
}
void on_is_selected_changed(bool is_selected) {
+11 -6
View File
@@ -1,4 +1,5 @@
using mbts.ui.shell;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,8 +7,9 @@ using System.Reactive.Linq;
using System.Runtime.CompilerServices;
namespace mbts.spark {
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged {
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged, ITemplateSelectorKey {
readonly IEqualityComparer<T> _comparer;
readonly string _template_key;
AlgoParamPermissions _permissions;
bool _is_enabled, _has_value;
T _committed_value, _value;
@@ -15,15 +17,16 @@ namespace mbts.spark {
public AlgoParamVM(string id, string display_name)
: this(id, display_name, default, EqualityComparer<T>.Default) { }
public AlgoParamVM(string id, string display_name, T value)
: this(id, display_name, value, EqualityComparer<T>.Default) { }
public AlgoParamVM(string id, string display_name, T value, string template_key = null)
: this(id, display_name, value, EqualityComparer<T>.Default, template_key) { }
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer) {
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null) {
this.id = id;
this.display_name = display_name;
_value = value;
_committed_value = value;
_comparer = comparer;
_template_key = template_key;
}
public event PropertyChangedEventHandler PropertyChanged;
@@ -50,7 +53,9 @@ namespace mbts.spark {
public bool is_enabled { get => _is_enabled; set => _set(ref _is_enabled, value); }
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
public bool is_dirty { get; private set; }
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
string ITemplateSelectorKey.key => _template_key;
public void revert() {
if (is_dirty) {
+3 -1
View File
@@ -36,6 +36,8 @@ namespace mbts.spark {
public AlgoDefinitionMetadata? root_algo_meta =>
_order_tuple.HasValue ? _order_tuple.Value.root.meta : null;
public OrderSecurityAlgo? root_order_security => _order_tuple?.root;
public string account {
get {
var account_string = string.Empty;
@@ -85,4 +87,4 @@ namespace mbts.spark {
public static string user(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
}
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ namespace mbts.spark {
send_order = new DelegateCommand<Side>(side => {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
model.send_order(properties_vm.create(side), price, size_calculator.value.Value).ignore();
model.send_order(security, properties_vm.create(side), price, size_calculator.value.Value).ignore();
RequestClose?.Invoke(this, new EventArgs());
}
});
@@ -67,7 +67,6 @@ namespace mbts.spark.utilities {
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_users() => _admin_model.on_users;
public IObservable<IReadOnlyList<UserTuple>> subscribe_user_tuples() => _admin_model.on_user_tuples;
public IObservable<IReadOnlyList<AccountTuple>> subscribe_account_tuples() => _admin_model.on_account_tuples;
#region Defaults
@@ -62,6 +62,7 @@ namespace mbts.spark {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey:
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: param.AsQuantity = new bts.spark.QuantityMeasure(); break;
}
@@ -12,7 +12,7 @@ using System.Reactive.Linq;
namespace mbts.spark {
interface IDynamicOrderParamVM {
DynamicOrderParamDescriptor descriptor { get; }
DynamicOrderParam param { get; set; }
DynamicOrderParam param { get; }
}
interface IDynamicOrderParamVM<T> : IDynamicOrderParamVM {
@@ -21,26 +21,16 @@ namespace mbts.spark {
}
abstract class DynamicOrderParamVM : DisposableViewModelBase, IDynamicOrderParamVM {
DynamicOrderParam _param;
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
private DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
this.descriptor = descriptor;
_param = param;
this.param = param;
}
public DynamicOrderParamDescriptor descriptor { get; set; }
public DynamicOrderParam param {
get => _param;
set {
_param = value;
on_param_changed();
}
}
protected abstract void on_param_changed();
public DynamicOrderParam param { get; }
}
class DynamicOrderParamVM<TView, TModel> : DynamicOrderParamVM, IDynamicOrderParamVM<TView> {
@@ -53,14 +43,12 @@ namespace mbts.spark {
_field_desc = field_descriptor;
_convert = convert;
_convert_back = convert_back;
value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
}
public TView value {
get => _value;
set {
_field_desc.Accessor.SetValue(param, _convert_back(_value = value));
RaisePropertyChanged();
}
set => Set(ref _value, value);
}
public IObservable<TView> subscribe_value() =>
@@ -70,11 +58,6 @@ namespace mbts.spark {
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
o.OnNext(value);
});
protected override void on_param_changed() {
_value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
RaisePropertyChanged(nameof(value));
}
}
class DynamicOrderEnumeratedVM : DynamicOrderParamVM, IDynamicOrderParamVM<string> {
@@ -105,11 +88,6 @@ namespace mbts.spark {
o.OnNext(selected_element);
});
protected override void on_param_changed() {
_sync_elements();
RaisePropertyChanged(nameof(selected_element));
}
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
@@ -148,10 +126,5 @@ namespace mbts.spark {
o.OnNext(quantity);
});
}
protected override void on_param_changed()
{
RaisePropertyChanged(nameof(quantity));
}
}
}
@@ -20,6 +20,7 @@ namespace mbts.ui.spark
public DataTemplate StringTemplate { get; set; }
public DataTemplate OrderDefTemplate { get; set; }
public DataTemplate QuantityMeasureTemplate { get; set; }
public DataTemplate SecurityKeyTemplate { get; set; }
public DataTemplate EnumTemplate { get; set; }
@@ -38,6 +39,7 @@ namespace mbts.ui.spark
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DoubleTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return IntTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return UIntTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return SecurityKeyTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return StringTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: return OrderDefTemplate;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: return QuantityMeasureTemplate;
@@ -4,6 +4,7 @@ using bts.collections;
using bts.spark;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -73,9 +74,9 @@ namespace mbts.spark {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return mk<double, double>(DynamicOrderParam.AsDoubleFieldNumber, v => v, v => v, double.NaN);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<int?, int>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault());
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault(), template_key: TemplateKeys.SecurityKey);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return mk<string, string>(DynamicOrderParam.AsStringFieldNumber, v => v, v => v);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity:
{
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: {
var qm = new DynamicOrderQuantityVM(descriptor);
return attached(qm, DynamicOrderHelper.get_quantity_value, DynamicOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
}
@@ -88,16 +89,16 @@ namespace mbts.spark {
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
}
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default) {
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default, string template_key = null) {
var fdesc = DynamicOrderParam.Descriptor.FindFieldByNumber(field_number);
var pvm = new DynamicOrderParamVM<TView, TModel>(descriptor, fdesc, convert, convert_back);
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value, template_key);
TView get_from_param(DynamicOrderParam param) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
void assign_to_param(DynamicOrderParam param, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
}
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default, string template_key = null) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
add_property<T>(
pvm.descriptor.ParamName,
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
@@ -105,7 +106,8 @@ namespace mbts.spark {
() => pvm.value,
v => pvm.value = v,
pvm.subscribe_value(),
default_value);
default_value,
template_key: template_key);
return pvm;
}
}
@@ -89,6 +89,7 @@ namespace mbts.spark {
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
case AlgoType.Composite: vm = composite(key, name, editing_permissions); break;
case AlgoType.None: vm = new DefaultAlgoVM(); break;
default: vm = generic(key, name, algo_type, editing_permissions); break;
//default:
// Log.Ui.debug_fatal($"Failed to create {typeof(IAlgoVM)}: unknown algo type '{algo_type}.'");
@@ -166,7 +167,8 @@ namespace mbts.spark {
};
void subscribe_new_algo(IAlgoVM old_algo, IAlgoVM new_algo) {
RaisePropertyChanged(sd.prop_name);
Disposable.replace(ref def_sub, new CompositeDisposable());
Disposable.dispose(ref def_sub);
def_sub = new CompositeDisposable();
if (assert(new_algo != null, sd.prop_name, "No algo specified.")) {
def_sub.Add(new_algo.subscribe_definition().Subscribe(_ => _def_change_notifier.execute()));
if (new_algo is INotifyDataErrorInfo ndei)
@@ -255,14 +257,14 @@ namespace mbts.spark {
_security_changed += o.OnNext;
return Disposable.Create(() => _security_changed -= o.OnNext);
});
Disposable.replace(ref _pending_load, Disposable.Create(
Disposable.dispose(ref _pending_load);
_pending_load = Disposable.Create(
// load children
ObservableAutoWrapper.connect_item_hooks(
_child_algo_selector_data.AsCollection(),
sd => security_src.SubscribeEach(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
// load regular props
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom)));
}
void selector_load_impl(AlgoSelectorData sd) {
@@ -298,11 +300,12 @@ namespace mbts.spark {
_security_changed += o.OnNext;
return Disposable.Create(() => _security_changed -= o.OnNext);
});
Disposable.replace(ref _pending_load, Disposable.Create(
Disposable.dispose(ref _pending_load);
_pending_load = Disposable.Create(
ObservableAutoWrapper.connect_item_hooks(
_child_algo_selector_data.AsCollection(),
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos))));
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos)));
void selector_load_impl(AlgoSelectorData sd) {
// **assume that algos has more than 1 element
@@ -542,8 +545,19 @@ namespace mbts.spark {
}
}
protected void add_property<TValue>(string prop_name, Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Func<TValue> get_local, Action<TValue> set_local, IObservable<TValue> subscribe_local, TValue non_uniform_value = default, AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite) {
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local()) { permissions = permissions };
protected void add_property<TValue>(
string prop_name,
Func<TMutable, TValue> get_from_def,
Action<TMutable, TValue> assign_to_def,
Func<TValue> get_local,
Action<TValue> set_local,
IObservable<TValue> subscribe_local,
TValue non_uniform_value = default,
AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite,
string template_key = null) {
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local(), template_key) {
permissions = permissions
};
_algo_params.Add(param);
_auto_props[prop_name] = new AutoProp(prop_name,
def => {
@@ -816,12 +830,11 @@ namespace mbts.spark {
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
foreach (var sd in _child_algo_selectors)
sd.load_algo_data_recursive(algo_data, default_algo);
Disposable.replace(
ref _selector_data_hooks,
ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
}));
Disposable.dispose(ref _selector_data_hooks);
_selector_data_hooks = ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
});
}
}
+11 -2
View File
@@ -158,7 +158,9 @@ namespace mbts.spark {
public static string display_name(this OrderSecurity order_tuple) =>
display_name(order_tuple.order, order_tuple.algo);
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
public static string display_name(OrderRecordImmutable order, AlgoData? algo, SecurityImmutable? security = null) {
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader && security.HasValue)
return security.Value.ToString();
if (algo.HasValue) {
return algo.Value.name;
} else if (order.HasDef && order.Def.HasCustomDef) {
@@ -200,6 +202,13 @@ namespace mbts.spark {
public static string short_description(this OrderRecordImmutable order, ContractSpecificationImmutable? spec) =>
$"{(order.HasDef ? order.Def.Side.ToString() : "(unknown-side)")} {(order.HasState ? ($@"{spec.format(order.State.Price)} @ {order.State.Quantity}") : "(unknown-price) @ (unknown-quantity)")}";
public static string short_description(SecurityImmutable security, Side side, double price, uint quantity) =>
new StringBuilderEx(
security.ToString(), " ",
side.ToString(), " ",
price_quantity_description(price, quantity, security.spec_or_default())
).ToString();
public static string short_description(this OrderRecordImmutable order, SecurityImmutable? security) {
var spec = security.spec_or_default();
return new StringBuilderEx(
@@ -243,7 +252,7 @@ namespace mbts.spark {
order_type.ToString().ToUpper();
public static string display_name(this OrderSecurityAlgo order_sec_algo) =>
display_name(order_sec_algo.order, order_sec_algo.algo);
display_name(order_sec_algo.order, order_sec_algo.algo, order_sec_algo.security);
public static string display_name(this CustomOrderDefinitionImmutable algo) =>
algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic ? algo.Dynamic.DynamicOrderType : algo.CustomCase.display_name();
+48 -21
View File
@@ -59,23 +59,30 @@ namespace mbts.spark {
this.md_auxiliary_model = md_auxiliary_model;
var scheduler = ThreadPoolScheduler.Instance;
var interval = TimeSpan.FromMilliseconds(100);
on_orders =
_service
.subscribei_orders()
.Naggle(interval, scheduler)
.PermissionFilter(admin_model.on_admin_data)
.join(sstore, order => order.security_key())
on_orders = admin_model
.permission_filter(
_service.subscribei_orders()
.Naggle(interval, scheduler),
o => o.account_id()
).join(sstore, order => order.security_key())
.Table()
.Cache();
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).join(sstore, pos => pos.SecurityId).Table().Cache();
on_pnl = _position_service.subscribei_pnl_data()
.Naggle(interval, scheduler)
on_positions = admin_model
.permission_filter(
_position_service.subscribei_position_data().Naggle(interval, scheduler),
p => p.AccountId)
.join(sstore, pos => pos.SecurityId)
.Table()
.Cache();
on_pnl = admin_model
.permission_filter(
_position_service.subscribei_pnl_data().Naggle(interval, scheduler),
p => p.Pos.AccountId)
.join(sstore, p => p.Pos.SecurityId)
.TableCheckedAutoCorrect(EqualityComparer<(SecurityImmutable? sec, PNLUnitImmutable data)>.Default)
.Where(t => t.has_changes())
.Cache();
on_account_pnl = _position_service.subscribei_pnl_account_data().Naggle(interval, scheduler).Table().Cache();
var working_orders = on_orders.Filter(tup => tup.order.is_active());
this.working_orders =
on_orders.Scan(
@@ -174,10 +181,11 @@ namespace mbts.spark {
.Where(t => t.has_changes())
.Cache();
// fill tuples
var on_fills =
_service.subscribei_fills()
.Naggle(interval, scheduler)
.PermissionFilter(admin_model.on_admin_data)
var on_fills = admin_model
.permission_filter(
_service.subscribei_fills()
.Naggle(interval, scheduler),
f => f.Account)
.join(sstore, f => f.SecurityKey)
.Table().Cache();
var fill_order_tuples_join = on_fills.LeftJoin(
@@ -202,6 +210,7 @@ namespace mbts.spark {
// manual fills
on_manual_fills_securities = _position_service.subscribei_manual_fills()
.Naggle(interval, scheduler)
.Filter(f => f.FillType == ManualFill.Types.FillType.Local)
.join(sstore, mf => mf.SecurityKey)
.Table().Cache();
var manual_fill_tuples_account_join = on_manual_fills_securities.LeftJoin(
@@ -241,7 +250,6 @@ namespace mbts.spark {
.Filter(os => os.order.is_active() && os.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
.Subscribe(_security_working_orders_with_piq_demuxer),
on_positions.Subscribe(_security_positions_demuxer),
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
_bus.subscribe(SparkTags.DetachOrders).Subscribe(x => detach_orders(x).ignore()),
@@ -266,7 +274,6 @@ namespace mbts.spark {
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders { get; }
public IObservable<TableUpdate<(SecurityImmutable?, PNLUnitImmutable), int>> on_pnl { get; }
public IObservable<TableUpdate<PNLRowData, PNLKey>> security_account_pnl { get; }
public IObservable<TableUpdate<AccountPNLImmutable, uint>> on_account_pnl { get; }
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_positions { get; }
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
public IObservable<TableUpdate<(SecurityImmutable?, ManualFillImmutable), int>> on_manual_fills_securities { get; }
@@ -287,13 +294,33 @@ namespace mbts.spark {
#endregion
#region Input
public async Task<uint?> send_order(OrderDefinitionImmutable? order, double price, uint size) {
// the security arg is needed for a temporary hack to prevent GlbxIceberg orders from going out in non-glbx exchanges (like Ice)
// eventually, we'll need an algo type model to handle this stuff.
// This hack leverages the fact that every place that calls 'send_order' already has the security handy (not just the sec id).
public Task<uint?> send_order(SecurityImmutable security, OrderDefinitionImmutable? order, double price, uint size) {
if (order.HasValue) {
var result = (await send_order_get_error(order.Value, price, size));
_activity.on_send_order(order.Value, price, size, result.key, result.error);
return result.key;
#if DEBUG
if (security.Exchange != Venue.Glbx && security.Exchange != Venue.Sim) {
#else
if (security.Exchange != Venue.Glbx) {
#endif
var algo_structure = OrderDataHelper.enumerate_algo_structure(order.Value.CustomDef);
if (algo_structure.Any(t => t.type == CustomOrderDefinition.CustomOneofCase.GlbxIceberg)) {
Log.Ui.info($"OrdersModel prevented a GlbxIceberg order from going out to a non-glbx exchange ({OrderUtils.short_description(security, order.Value.Side, price, size)}).");
return Task.FromResult<uint?>(null);
}
}
return send_order(order.Value, price, size);
}
return 0;
return Task.FromResult<uint?>(null);
}
/// Note: this method is private to force outside callers to use this overload <see cref="send_order(SecurityImmutable, OrderDefinitionImmutable?, double, uint)"/>.
/// This allows us to control which orders can be sent to which security/exchange (e.g., GlbxIceberg orders are only permitted for securities on the Glbx exchange).
async Task<uint?> send_order(OrderDefinitionImmutable order, double price, uint size) {
var result = (await send_order_get_error(order, price, size));
_activity.on_send_order(order, price, size, result.key, result.error);
return result.key;
}
async Task<(uint? key, string error)> send_order_get_error(OrderDefinitionImmutable order, double price, uint size) {
@@ -0,0 +1,80 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts.spark;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace mbts {
static class OrderSentEventPredicateVMFactory {
public static Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> get_oneofs(IBus bus) =>
new Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> {
{ OrderSentEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderSentEventPredicate>(p => p.security = null) },
{ OrderSentEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderSentEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
(p, s) => {
if (p.security == null) {
p.security = new SecurityPredicate();
}
p.security.is_value = s;
})
}
};
}
[ProtoContract]
class OrderSentEventPredicate : IEquatable<OrderSentEventPredicate> {
static readonly int _type_hash = typeof(OrderSentEventPredicate).GetHashCode();
// arbitrary short time period (can override in config)
static readonly TimeSpan _stale_order_threshold = TimeSpan.FromSeconds(config.Configuration.get<bts.config.OrderAlerts>().stale_order_threshold_seconds);
public enum PredicateTypeOneofCase {
[Description("(any order)")]
AnyOrder,
[Description("choose securities")]
SecurityOrder
}
[ProtoMember(1)]
public SecurityPredicate security { get; set; }
public PredicateTypeOneofCase predicate_type_case {
get {
if (security != null)
return PredicateTypeOneofCase.SecurityOrder;
return PredicateTypeOneofCase.AnyOrder;
}
}
public IPredicate<OrderSecurity> create(object obj) =>
create_predicate((INotificationServiceDataCache)obj);
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) {
var sec_pred = security?.create();
return PredicateBuilder.create<OrderSecurity>(os =>
// verify security
(sec_pred == null || (os.security.HasValue && sec_pred.trigger(os.security.Value))) &&
// verify not stale
((DateTime.Now - os.order.created()) < _stale_order_threshold) &&
// verify order hasn't been added yet
// Note: the notification service should offer a way to connect predicates so that they only receive items in TableUpdate.added,
// however, the notification service predated TableUpdate.Changes, so for now, just check that the history.Count == 1)
(data_cache.order_history.TryGetValue(os.order.Key, out var hist) && hist.Count == 1));
}
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
throw new NotImplementedException();
public bool Equals(OrderSentEventPredicate other) =>
other != null && other.security.safe_equals(other.security);
public override bool Equals(object obj) =>
Equals(obj as OrderSentEventPredicate);
public override int GetHashCode() =>
EqualityComparer.auto_phase(_type_hash, security?.GetHashCode() ?? 0);
}
}
@@ -5,14 +5,9 @@ using bts.utils;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.utility;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using PTOC = mbts.OrderRecordTuplePredicate.PredicateTypeOneofCase;
namespace mbts {
@@ -20,6 +15,7 @@ namespace mbts {
// TODO: uncomment the remaining cases when ready
public static Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> get_oneofs(IBus bus, OrdersModel orders_model) {
return new Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> {
{ PTOC.SentEvent, wrap(rp => rp.sent_event, (rp, p) => rp.sent_event = p, p => p.predicate_type_case, OrderSentEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.FilledEvent, wrap(rp => rp.filled_event, (rp, p) => rp.filled_event = p, p => p.predicate_type_case, OrderFilledEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CompletedEvent, wrap(rp => rp.completed_event, (rp, p) => rp.completed_event = p, p => p.predicate_type_case, OrderCompletedEventPredicateVMFactory.get_oneofs(bus)) },
{ PTOC.CanceledEvent, wrap(rp => rp.canceled_event, (rp, p) => rp.canceled_event = p, p => p.predicate_type_case, OrderCanceledEventPredicateVMFactory.get_oneofs(bus)) },
@@ -77,6 +73,7 @@ namespace mbts {
OrderFilledEventPredicate _filled_event;
OrderCompletedEventPredicate _completed_event;
OrderCanceledEventPredicate _canceled_event;
OrderSentEventPredicate _sent_event;
public enum PredicateTypeOneofCase {
None,
@@ -120,6 +117,8 @@ namespace mbts {
OrderKey,
[PredicateCase(is_toastable:false)]
ParentKey,
[PredicateCase("When order is sent", PredicateCategory.Event)]
SentEvent,
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
FilledEvent,
[PredicateCase("When order is completed", PredicateCategory.Event)]
@@ -176,6 +175,8 @@ namespace mbts {
public OrderCompletedEventPredicate completed_event { get => _completed_event; set => _set(ref _completed_event, value); }
[ProtoMember(23)]
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
[ProtoMember(26)]
public OrderSentEventPredicate sent_event { get => _sent_event; set => _set(ref _sent_event, value); }
[ProtoMember(24)]
public bool is_toast_enabled { get; set; }
[ProtoMember(25)]
@@ -206,6 +207,7 @@ namespace mbts {
if (_filled_event != null) return PTOC.FilledEvent;
if (_completed_event != null) return PTOC.CompletedEvent;
if (_canceled_event != null) return PTOC.CanceledEvent;
if (_sent_event != null) return PTOC.SentEvent;
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
return PTOC.None;
}
@@ -236,6 +238,7 @@ namespace mbts {
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
case PTOC.SentEvent: return _sent_event.create(data_cache);
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key.create(), r => r.order.attached_target_order_key());
default: return null;
}
@@ -285,6 +288,7 @@ namespace mbts {
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
case PTOC.SentEvent: return _sent_event.Equals(other._sent_event);
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
case PTOC.None: return true;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
@@ -320,6 +324,7 @@ namespace mbts {
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
case PTOC.SentEvent: raw_hash = _sent_event.GetHashCode(); break;
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
case PTOC.None: raw_hash = 0; break;
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
@@ -355,6 +360,7 @@ namespace mbts {
_filled_event = null;
_completed_event = null;
_canceled_event = null;
_sent_event = null;
_attached_target_order_key = null;
}
field = value;
+1 -1
View File
@@ -339,7 +339,7 @@
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Padding" Value="3,3,1,3"/>
<Setter Property="Padding" Value="6,3,1,3"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="Both"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
@@ -8,6 +8,7 @@ using mbts.spark;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Windows.Input;
namespace bts.spark {
@@ -41,6 +42,9 @@ namespace bts.spark {
SparkCommand.create("P&L (2.0)", Icons.Accounting, SparkTags.ShowPNLTag);
// Ladder
public static PartialCommand<IImmutableSet<uint>> ShowLadderAutoLookup(KeyGesture key_gesture = null) =>
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderAutoLookupTag, key_gesture);
public static PartialCommand<SecurityMD> ShowLadder(KeyGesture key_gesture = null) =>
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderTag, key_gesture);
+3 -1
View File
@@ -7,6 +7,7 @@ using bts.spark;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace mbts.spark {
@@ -17,7 +18,6 @@ namespace mbts.spark {
#endregion
#region Order
public static ITag<(OrderDefinitionImmutable definition, double price, uint size)> CreateOrder { get; } = new SparkTag<(OrderDefinitionImmutable, double, uint)>();
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
@@ -56,6 +56,8 @@ namespace mbts.spark {
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
// For now, both tags go through the same logic in the LadderWindowFactory anyways.
public static ITag<SecurityMD> ShowLadderTag { get; } = new SparkTag<SecurityMD>("Show Ladder");
// TODO: get rid of the SecurityMD ShowLadderTag. This has created burdensome dependency issues.
public static ITag<IImmutableSet<uint>> ShowLadderAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>("Show Ladder Auto Lookup");
public static ITag<IReadOnlyList<SecurityMD>> ShowLaddersTag { get; } = new SparkTag<IReadOnlyList<SecurityMD>>(nameof(ShowLaddersTag));
public static ITag<IObservable<SecurityMD>> ShowLinkedLadder { get; } = new SparkTag<IObservable<SecurityMD>>("Linked Ladder");
public static ITag<SecurityCriteria> ShowMarketViewTag { get; } = new SparkTag<SecurityCriteria>("Show MarketView");
@@ -1,7 +1,36 @@
using System.Linq;
using System;
using System.Linq;
namespace mbts {
public static class DoubleHelper {
public static bool any_nan_or_infinity(params double[] values) {
for (int i = 0; i < values.Length; i++) {
if (double.IsNaN(values[i]) || double.IsInfinity(values[i]))
return true;
}
return false;
}
public static bool are_close(double v1, double v2, double threshold) =>
!any_nan_or_infinity(v1, v2, threshold) && Math.Abs(threshold) > Math.Abs(v1 - v2);
// bc double.IsNaN gets annoying write, and a lazy programmer is a happy programmer ;)
public static bool is_nan(this double value) =>
double.IsNaN(value);
/// <summary>
/// If the specified value is a <see cref="double"/>, <see cref="int"/>, or <see cref="uint"/>, returns the equivalent <see cref="double"/>; otherwise, returns <see cref="double.NaN"/>.
/// </summary>
/// <param name="value">The value to convert to a <see cref="double"/>.</param>
/// <returns>The <see cref="double"/> equivalent value if the cast succeeds, otherwise <see cref="double.NaN"/>.</returns>
public static double nan_cast(object value) {
// devs: add numeric type checks as needed
if (value is double d) return d;
if (value is int i) return i;
if (value is uint u) return u;
return double.NaN;
}
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
@@ -11,6 +11,7 @@ using bts.api.persistence;
using System.Text.RegularExpressions;
using System.Reactive.Linq;
using bts.spark;
using bts;
namespace mbts.persist
{
@@ -146,16 +147,18 @@ namespace mbts.persist
}
[Export(typeof(IUserPersist))]
public class UserUISettings : UISettingsBase
{
public class UserUISettings : UISettingsBase {
string _user;
[ImportingConstructor]
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
public UserUISettings(uint user_id, [Import("settings")]PersistenceService persist, ErrorReporter err)
: base(persist,err) {
_user = $"User {user}";
_user = $"User {user_id}";
}
[ImportingConstructor]
UserUISettings(UserTuple user, [Import("settings")]PersistenceService persist, ErrorReporter err)
: this(user.id, persist, err) { }
protected override string user() { return _user; }
}
}
@@ -17,6 +17,7 @@ namespace bts.settings {
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
static class SettingsMigrationHelper {
public static bool apply<T>(T settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules, out T new_settings) {
@@ -25,7 +25,8 @@ namespace mbts.persist {
static readonly IImmutableProperty<UserSettings, GridSettings>
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs));
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
static readonly IImmutableProperty<UserSettings, PNLSettings>
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
@@ -36,6 +37,7 @@ namespace mbts.persist {
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
readonly IUserPersist _persist;
UserSettings _default_settings;
@@ -46,13 +48,15 @@ namespace mbts.persist {
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules) {
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
//
_persist = persist;
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
init();
async void init() {
UserSettings s;
@@ -62,7 +66,9 @@ namespace mbts.persist {
var wrapped_rules = new[] {
_fills_migration_rules.merge().wrap(_fills_propi),
_mview_migration_rules.merge().wrap(_mview_propi),
_pandl_migration_rules.merge().wrap(_pandl_propi)
_pandl_migration_rules.merge().wrap(_pandl_propi),
_pnl_migration_rules.merge().wrap(_pnl_propi),
_synth_migration_rules.merge().wrap(_synth_propi)
};
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
// TODO: move ladder sanitization logic into ISettingsMigrationRule
@@ -136,6 +142,12 @@ namespace mbts.persist {
_pnl_migration_rules,
_pnl_propi);
public IWindowSettingsManager<GridSettings> create_synth_settings_manager(GridSettings instance_settings) =>
_create_settings_manager(
instance_settings,
_synth_migration_rules,
_synth_propi);
public async Task save_default(UserSettings value) {
// notify observers before persisting so the changes are reflected immediately in the ui
lock (_default_settings_lock) {
@@ -15,12 +15,8 @@ namespace bts.utils {
}
static class ObjectSnapshot {
public static IEnumerable<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() where TSource : class where TValue : class {
var tval = typeof(TValue);
return typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => tval.IsAssignableFrom(p.PropertyType))
.Select(_prop_getter<TSource, TValue>);
}
public static IReadOnlyList<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() =>
PropertyGetterStore<TSource, TValue>.value;
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
_property_table(instance, Array.ConvertAll(prop_names, PropertyInfoStore<T>.type.GetProperty));
@@ -162,6 +158,18 @@ namespace bts.utils {
}
}
static class PropertyGetterStore<TSource, TValue> {
public static readonly IReadOnlyList<Func<TSource, TValue>> value;
static PropertyGetterStore() {
var tval = typeof(TValue);
value = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => tval.IsAssignableFrom(p.PropertyType))
.Select(_prop_getter<TSource, TValue>)
.ToReadOnlyList();
}
}
class PropNode {
public readonly PropertyInfo prop;
@@ -0,0 +1,46 @@
using bts.api;
using bts.spark;
using bts.utils;
using System;
namespace bts {
struct SyntheticTuple : IEquatable<SyntheticTuple>, IComparable<SyntheticTuple>, IComparable {
public static readonly Func<SyntheticTuple, uint> key_fn = a => a.security.Id;
public static readonly Func<SyntheticTuple?, uint> nullable_key_fn = a => a.HasValue ? a.Value.security.Id : 0u;
public readonly SecurityImmutable security;
public readonly string user_name;
public readonly ExecutionStrategyImmutable? strategy;
public readonly int? num_working_orders;
public SyntheticTuple(SecurityImmutable security, string user_name, ExecutionStrategyImmutable? strategy, int? num_working_orders = null) {
this.security = security;
this.user_name = user_name;
this.strategy = strategy;
this.num_working_orders = num_working_orders;
}
public int CompareTo(SyntheticTuple other) =>
SecurityHelper.Comparer.Compare(security, other.security);
int IComparable.CompareTo(object obj) =>
obj is SyntheticTuple other ? CompareTo(other) : 1;
public bool Equals(SyntheticTuple other) =>
security == other.security &&
user_name == other.user_name &&
EqualityComparer.nullable_equals(strategy, other.strategy);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() =>
security.Id.GetHashCode();
public override string ToString() =>
user_name == null ? security.ToString() : $"{user_name}:{security}";
public static bool operator ==(SyntheticTuple a, SyntheticTuple b) => a.Equals(b);
public static bool operator !=(SyntheticTuple a, SyntheticTuple b) => !a.Equals(b);
}
}
@@ -1,10 +1,11 @@
using System;
using System.Linq;
namespace mbts.ui.controls
{
internal class DoubleUtil
{
namespace mbts.ui.controls {
static class DoubleUtil {
public const int API_DECIMAL_PRECISION = 9;
// arbitrary small number that can be used to determine whether or not scroll changes are significant
public const double MIN_SCROLL_DELTA = 0.000015;
/* smallest such that 1.0+DBL_EPSILON != 1.0 */
private const double EPSILON = 2.2204460492503131e-016;
private const double BIG_EPSILON = EPSILON * 10;
@@ -36,6 +37,9 @@ namespace mbts.ui.controls
(double.IsNaN(right) ? double.NaN : -right) :
(double.IsNaN(right) ? left : left - right);
public static decimal round_to_decimal(double value, int decimals = API_DECIMAL_PRECISION) =>
double.IsNaN(value) ? 0 : (decimal)Math.Round(value, decimals, MidpointRounding.AwayFromZero);
internal static bool AreClose(double v1, double v2) {
if (v1 == v2) return true;
double delta = Math.Abs(v1 - v2);
@@ -1,11 +1,22 @@
using System;
using bts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace mbts.spark.filters {
static class DataGridFilterHelper {
public static IDisposable hook_filters<TRow>(ICollectionView rows, params IDataGridColumnFilter<TRow>[] filters) {
public static IDisposable hook_all<TSource, TRow>(TSource source, ICollectionView rows) =>
hook_filters(rows,
ObjectSnapshot.property_delegates_of_type<TSource, IDataGridColumnFilter<TRow>>()
.Select(f => f(source))
.ExceptNull()
.ToList());
public static IDisposable hook_filters<TRow>(ICollectionView rows, params IDataGridColumnFilter<TRow>[] filters) =>
hook_filters(rows, filters.AsEnumerable());
public static IDisposable hook_filters<TRow>(ICollectionView rows, IEnumerable<IDataGridColumnFilter<TRow>> filters) {
var icvls = (ICollectionViewLiveShaping)rows;
icvls.IsLiveFiltering = true;
return _hook_filters(set_filter, filters);
@@ -22,7 +33,7 @@ namespace mbts.spark.filters {
}
}
static IDisposable _hook_filters<TRow>(Action set_filter, params IDataGridColumnFilter<TRow>[] filters) {
static IDisposable _hook_filters<TRow>(Action set_filter, IEnumerable<IDataGridColumnFilter<TRow>> filters) {
foreach (var f in filters)
f.filter_changed += set_filter;
set_filter();
@@ -34,7 +45,7 @@ namespace mbts.spark.filters {
});
}
static List<IDataGridColumnFilter<TRow>> _get_active_filters<TRow>(IDataGridColumnFilter<TRow>[] filters) =>
static List<IDataGridColumnFilter<TRow>> _get_active_filters<TRow>(IEnumerable<IDataGridColumnFilter<TRow>> filters) =>
filters.Where(f => f.active.HasValue && f.active.Value).ToList();
}
@@ -68,10 +68,11 @@ namespace mbts.spark.filters {
Func<TRow, TValue> factory,
Func<TValue, TKey> key,
out IObserver<TableUpdate<TValue, TKey>> items_observer,
out ListCollectionView<TValue> items_view)
out ListCollectionView<TValue> items_view,
string prop_name = null)
where TKey : struct, IEquatable<TKey> =>
//
CreateTableUpdate(factory, key, keys => keys, keys => keys.ToArray(), out items_observer, out items_view);
CreateTableUpdate(factory, key, keys => keys, keys => keys.ToArray(), out items_observer, out items_view, prop_name);
public static IDataGridColumnPersistentFilter<TRow, TState> CreateTableUpdate<TRow, TValue, TKey, TState>(
Func<TRow, TValue> factory,
@@ -79,10 +80,11 @@ namespace mbts.spark.filters {
Func<TState, IEnumerable<TKey>> load_state,
Func<IEnumerable<TKey>, TState> get_state,
out IObserver<TableUpdate<TValue, TKey>> items_observer,
out ListCollectionView<TValue> items_view)
out ListCollectionView<TValue> items_view,
string prop_name = null)
where TKey : struct, IEquatable<TKey> {
//
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TState>(factory, key, load_state, get_state);
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TState>(factory, key, load_state, get_state, prop_name);
items_observer = filter;
items_view = filter.items_view;
return filter;
@@ -291,8 +293,8 @@ namespace mbts.spark.filters {
readonly HashSet<TKey> _selected_keys = new HashSet<TKey>(), _desired_keys = new HashSet<TKey>();
ListCollectionView<TValue> _items_view;
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state)
: base(factory, null) {
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state, string prop_name)
: base(factory, prop_name) {
_factory = factory;
_key = key;
_load_state = load_state;
@@ -24,6 +24,7 @@ namespace mbts.ui {
public TimeTextTuple end => _end;
public bool is_active =>
_mode == TimeFilterMode.Today ||
_mode == TimeFilterMode.CMEOpen ||
(_mode == TimeFilterMode.Period && (_begin.result.HasValue || _end.result.HasValue));
@@ -1,10 +1,4 @@
using bts.support;
using Google.Protobuf;
using mbts;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -18,6 +12,13 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using bts.support;
using Google.Protobuf;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.ui.utility;
namespace bts.view.controls {
partial class SupportRequestWindow : Window {
@@ -50,28 +51,40 @@ namespace bts.view.controls {
_base = report_base;
send_command = new DelegateCommand(send_report, _ => !string.IsNullOrWhiteSpace(_text.Value));
_text = backing(nameof(user_text), "");
_text.Changed += (_,__) => send_command.TryRaiseCanExecuteChanged();
_text.Changed += (_, __) => send_command.TryRaiseCanExecuteChanged();
_contact = backing(nameof(user_contact), "");
_contact.Changed += (_,__) => send_command.TryRaiseCanExecuteChanged();
_contact.Changed += (_, __) => send_command.TryRaiseCanExecuteChanged();
}
async void send_report(object _) {
ByteString screenshot = null;
if (include_screenshot) {
var stream = ScreenCapture.capture_encoded("jpeg");
screenshot = await ByteString.FromStreamAsync(stream);
}
var reply = await _service.send_report_async(_base.with(
user_description: _text.Value,
contact: user_contact,
log: include_logs ? LogUtil.read_logs() : "",
image: screenshot
));
if (reply.success) {
BTSMessageBox.Show("Your message has been received.", "Thank You", BTSMessageBoxButtons.OK, MessageBoxImage.Information, is_modal: false, top_most:true);
}
else {
BTSMessageBox.Show($"There was an error submitting your message. '{reply.error_message}'", "Error", BTSMessageBoxButtons.OK, MessageBoxImage.Error, is_modal: false, top_most: true);
using (var d = new BusyDialog { Text = "Sending your error report...", Topmost = true }) {
d.Show();
ByteString screenshot = null;
if (include_screenshot) {
var stream = ScreenCapture.capture_encoded("jpeg");
screenshot = await ByteString.FromStreamAsync(stream);
}
var request = await Task.Run(() => {
var result = _base.with(
user_description: _text.Value,
contact: user_contact,
image: screenshot);
if (include_logs) {
result = spark.ErrorReporter.append_logs(result);
}
return result;
});
var reply = await _service.send_report_async(request);
if (reply.success) {
Log.Ui.info("Support request message has been received.");
BTSMessageBox.Show("Your message has been received.", "Thank You", BTSMessageBoxButtons.OK, MessageBoxImage.Information, is_modal: false, top_most: true);
}
else {
var errmsg = $"There was an error submitting your message. '{reply.error_message}'";
Log.Ui.error(errmsg);
BTSMessageBox.Show(errmsg, "Error", BTSMessageBoxButtons.OK, MessageBoxImage.Error, is_modal: false, top_most: true);
}
}
}
@@ -1145,6 +1145,30 @@
</Grid>
</DataTemplate>
</usp:DynamicOrderParamTemplateSelector.UIntTemplate>
<usp:DynamicOrderParamTemplateSelector.SecurityKeyTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<c:ContractPickerPopup
Grid.Column="2"
CloseOnCancel="True"
CloseOnOK="True"
Header="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
IsEditMode="False"
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
IsLazy="True"
SecurityMap="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Securities}}"
SelectedSecurityKey="{Binding value}"
ShowDialogSkirt="True"
/>
</Grid>
</DataTemplate>
</usp:DynamicOrderParamTemplateSelector.SecurityKeyTemplate>
<usp:DynamicOrderParamTemplateSelector.StringTemplate>
<DataTemplate>
<Grid>
@@ -1181,30 +1205,35 @@
</Grid>
</DataTemplate>
</usp:DynamicOrderParamTemplateSelector.EnumTemplate>
<usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center"
Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
<uag:QuantityMeasureControl
Grid.Column="2" Focusable="False" HorizontalAlignment="Stretch"
QuantityMeasure="{Binding quantity, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
</uag:QuantityMeasureControl>
</Grid>
</DataTemplate>
</usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
<usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
VerticalAlignment="Center"
Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"
/>
<uag:QuantityMeasureControl
Grid.Column="2"
Focusable="False"
HorizontalAlignment="Stretch"
QuantityMeasure="{Binding quantity, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl"
>
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
</uag:QuantityMeasureControl>
</Grid>
</DataTemplate>
</usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
</usp:DynamicOrderParamTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
@@ -457,15 +457,15 @@
Name="PART_TextBox"
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
BorderThickness="0">
<TextBox.Style>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</Style>
</TextBox.Style>
</TextBox>
</c:TickUpDown>
</ControlTemplate>
@@ -93,7 +93,6 @@
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
u:Behavior.DisablePaste="True"
Height="20"
BorderThickness="0"
>
<TextBox.InputBindings>
<KeyBinding Key="Up" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int 1}"/>
@@ -11,6 +11,9 @@ namespace mbts.ui.converters {
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert) =>
new AnonymousValueConverter<TIn, TOut>(convert);
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert, Func<TOut, TIn> convert_back) =>
new AnonymousValueConverter<TIn, TOut>(convert, convert_back);
public static IMultiValueConverter create<TIn1, TIn2, TOut>(Func<TIn1, TIn2, TOut> convert) =>
new AnonymousMultiValueConverter<TIn1, TIn2, TOut>(convert);
@@ -28,9 +31,17 @@ namespace mbts.ui.converters {
IntTextConverter = new IntTextConverter(),
UIntTextConverter = new UIntTextConverter(),
QuantityMeasureTextConverter = ValueConverter.create<QuantityMeasureImmutable?, string>(fmt),
SecuritySelectorText,
SimpleString = new AnonymousValueConverter(obj => obj?.ToString()),
ThicknessToDouble = new ThicknessToDoubleConverter();
static ValueConverters() {
SecuritySelectorText = new NullSubstitutionConverterWrapper {
Converter = SimpleString,
ValueForNull = "(None)"
};
}
public static readonly IMultiValueConverter
AreEqualConverter = new AreEqualConverter();
@@ -0,0 +1,116 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace mbts.ui.converters {
class DictionaryLookupConverter : IMultiValueConverter {
[ThreadStatic]
Dictionary<Type, Lookup> _type_impls_backing;
Dictionary<Type, Lookup> _type_impls => _type_impls_backing ?? (_type_impls_backing = new Dictionary<Type, Lookup>());
delegate bool Lookup(object source, object key, out object result);
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values.Length == 2 && values[0] != null && values[1] != null) {
var lookup_type = values[0].GetType();
var lookup = _type_impls.GetOrAdd(lookup_type, TypeImplResolver.resolve);
if (lookup(values[0], values[1], out var result))
return result;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotImplementedException();
static class TypeImplResolver {
public static Lookup resolve(Type lookup_type) =>
_prioritized_impls(lookup_type)
.OrderBy(tup => tup.priority)
.Select(tup => tup.impl)
.FirstOrDefault() ?? _lookup_fail;
static bool _lookup_fail(object source, object key, out object result) {
result = null;
return false;
}
static IEnumerable<(int priority, Lookup impl)> _prioritized_impls(Type lookup_type) {
foreach (var itype in lookup_type.GetInterfaces()) {
if (itype.IsGenericType) {
var gen_def = itype.GetGenericTypeDefinition();
if (gen_def == typeof(IDictionary<,>))
yield return (1, _get_generic_impl(nameof(_GenericImpls<int, int>.idictionary), itype));
if (gen_def == typeof(IReadOnlyDictionary<,>))
yield return (2, _get_generic_impl(nameof(_GenericImpls<int, int>.ireadonly_dictionary), itype));
if (gen_def == typeof(ILookup<,>))
yield return (3, _get_generic_impl(nameof(_GenericImpls<int, int>.ilookup), itype));
}
if (itype == typeof(IDictionary))
yield return (4, _NonGenericImpls.idictionary);
}
}
static Lookup _get_generic_impl(string impl_name, Type impl_type) =>
_get_generic_impl(impl_name, impl_type.GetGenericArguments());
static Lookup _get_generic_impl(string impl_name, Type[] key_value_types) =>
(Lookup)typeof(_GenericImpls<,>)
.MakeGenericType(key_value_types)
.GetField(nameof(_GenericImpls<int, int>.idictionary))
.GetValue(null);
static class _GenericImpls<TKey, TValue> {
public static readonly Lookup
idictionary = _idictionary,
ireadonly_dictionary = _ireadonly_dictionary,
ilookup = _ilookup;
static bool _idictionary(object lookup, object key, out object result) {
if (key is TKey k && lookup is IDictionary<TKey, TValue> d && d.TryGetValue(k, out var value)) {
result = value;
return true;
}
result = null;
return false;
}
static bool _ireadonly_dictionary(object lookup, object key, out object result) {
if (key is TKey k && lookup is IReadOnlyDictionary<TKey, TValue> rod && rod.TryGetValue(k, out var value)) {
result = value;
return true;
}
result = null;
return false;
}
static bool _ilookup(object lookup, object key, out object result) {
if ((key is TKey k) && lookup is ILookup<TKey, TValue> l && l.Contains(k)) {
result = l[k];
return true;
}
result = null;
return false;
}
}
static class _NonGenericImpls {
public static readonly Lookup
idictionary = _idictionary;
static bool _idictionary(object lookup, object key, out object result) {
var dict = (IDictionary)lookup;
if (dict.Contains(key)) {
result = dict[key];
return true;
}
result = null;
return false;
}
}
}
}
}
@@ -9,6 +9,29 @@ using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
namespace mbts.ui.extensions {
static class FrameworkElementHelper {
/// <summary>
/// Invokes the specified action immediately if the element is already loaded, otherwise hooks up to the <see cref="FrameworkElement.Loaded"/> event,
/// and [conditionally]invokes the specified action if <paramref name="should_still_execute"/> returns true.
/// </summary>
/// <param name="element">The element that must be loaded before the specified action is invoked.</param>
/// <param name="action">The action to invoke when the element is loaded.</param>
/// <param name="should_still_execute">If the element is not already loaded, this delegate determines whether or not to invoke the action in the Loaded event callback.</param>
/// <returns></returns>
public static void InvokeOnLoaded(this FrameworkElement element, Action action, Func<bool> should_still_execute) {
if (action == null)
throw new ArgumentNullException(nameof(action));
if (element.IsLoaded) {
action();
} else {
element.Loaded += on_loaded;
void on_loaded(object sender, RoutedEventArgs e) {
element.Loaded -= on_loaded;
if (should_still_execute())
action();
}
}
}
public static Point GetCenter(this FrameworkElement element) =>
new Point(element.ActualWidth * 0.5, element.ActualHeight * 0.5);
@@ -0,0 +1,61 @@
using mbts.ui.controls;
using mbts.ui.extensions;
using System.Windows;
using System.Windows.Controls;
namespace mbts.ui {
static class ScrollViewerBehavior {
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerBehavior), new PropertyMetadata(double.NaN, OnVerticalOffsetChanged));
static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is ScrollViewer scroll) {
double voff = DoubleHelper.nan_cast(e.NewValue);
// Ignore small deltas to prevent strange layout rounding issues/loops.
// Be mindful that small deltas are not accumulated into a single larger delta.
if (!double.IsNaN(voff) && !DoubleHelper.are_close(DoubleHelper.nan_cast(e.OldValue), voff, DoubleUtil.MIN_SCROLL_DELTA)) {
scroll.InvokeOnLoaded(() => scroll.ScrollToVerticalOffset(voff),
() => scroll.GetValue(VerticalOffsetProperty) is double curr_voff && curr_voff == voff);
}
}
}
public static double GetVerticalOffset(DependencyObject d) => (double)d.GetValue(VerticalOffsetProperty);
public static void SetVerticalOffset(DependencyObject d, double value) => d.SetValue(VerticalOffsetProperty, value);
public static readonly DependencyProperty EnableTwoWayVerticalOffsetBindingProperty =
DependencyProperty.RegisterAttached("EnableTwoWayVerticalOffsetBinding", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, OnEnableTwoWayVerticalOffsetBindingChanged));
static void OnEnableTwoWayVerticalOffsetBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is ScrollViewer scroll) {
if ((bool)e.NewValue) {
scroll.ScrollChanged += EnableTwoWayVerticalOffsetBindingUtils.on_scroll_changed;
} else {
scroll.ScrollChanged -= EnableTwoWayVerticalOffsetBindingUtils.on_scroll_changed;
}
}
}
public static bool GetEnableTwoWayVerticalOffsetBinding(DependencyObject d) => (bool)d.GetValue(EnableTwoWayVerticalOffsetBindingProperty);
public static void SetEnableTwoWayVerticalOffsetBinding(DependencyObject d, bool value) => d.SetValue(EnableTwoWayVerticalOffsetBindingProperty, value);
public static readonly DependencyProperty VerticalOffsetMirrorBusyProperty =
DependencyProperty.RegisterAttached("VerticalOffsetMirrorBusy", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false));
static class EnableTwoWayVerticalOffsetBindingUtils {
public static void on_scroll_changed(object sender, ScrollChangedEventArgs e) {
if (e.VerticalChange != 0 && sender is ScrollViewer scroll) {
if (!(bool)scroll.GetValue(VerticalOffsetMirrorBusyProperty)) {
scroll.SetValue(VerticalOffsetMirrorBusyProperty, true);
// scroll changes could get noisy while layouts are being processed;
// let everything settle down before updating the VerticalOffset binding sources/targets.
scroll.Dispatcher.InvokeAsync(() => {
scroll.SetCurrentValue(VerticalOffsetProperty, scroll.VerticalOffset);
scroll.SetValue(VerticalOffsetMirrorBusyProperty, false);
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
}
}
}
}
}
@@ -236,6 +236,14 @@ namespace mbts.ui.extensions {
target.ScrollChanged += callback;
return Disposable.Create(() => target.ScrollChanged -= callback);
}
public static bool TryScrollToVerticalOffset(this ScrollViewer scroll_viewer, double vertical_offset) {
if (vertical_offset >= 0 && scroll_viewer.IsLoaded && vertical_offset < scroll_viewer.ExtentHeight - scroll_viewer.ViewportHeight) {
scroll_viewer.ScrollToVerticalOffset(vertical_offset);
return true;
}
return false;
}
}
public static class TextBoxExtensions {
@@ -0,0 +1,9 @@
namespace mbts.ui {
static class ResourceKeys {
public const string
Securities = nameof(Securities),
Accounts = nameof(Accounts),
Users = nameof(Users),
Sides = nameof(Sides);
}
}
@@ -0,0 +1,6 @@
namespace mbts.ui {
static class TemplateKeys {
public const string
SecurityKey = nameof(SecurityKey);
}
}
@@ -15,8 +15,7 @@ namespace mbts.ui.shell {
// The LoginContainer participates in the startup Import/Export dependency system
// Since the login form should not depend on *anything* it will be shown before this is created
// Once this is created it access the global LoginModel and retrieves the username
internal class LoginContainer
{
class LoginContainer {
[ImportingConstructor]
public LoginContainer(ServiceContext context) {
var model = LoginModel.instance;
@@ -28,10 +27,8 @@ namespace mbts.ui.shell {
model.Dispose();
}
[Export(Exports.UserName)]
public string UserName { get; private set; }
[Export(Exports.UserID)]
public uint UserId { get; private set; }
[Export]
@@ -42,13 +39,6 @@ namespace mbts.ui.shell {
[Export("workspace")]
public string Workspace { get; private set; }
public static class Exports {
public const string
UserName = "username",
UserID = "userid",
UserTuple = "usertuple";
}
}
public struct User {
@@ -130,8 +130,10 @@ namespace mbts.spark {
DialogSkirtItem _apply_item, _ok_item, _cancel_item;
MenuItemVM _import_item, _export_item, _load_factory_item;
public ProfiledShellSettingsDialogVM(ISettingsProfileService<T> profile_service, Func<UserSettings, T> get_fallback, Func<T, TVM> vm_factory, Func<T, T> normalize, T init, string file_ext, uint settings_id = 0) {
public ProfiledShellSettingsDialogVM(ISettingsProfileService<T> profile_service, Func<UserSettings, T> get_fallback, Func<T, TVM> vm_factory, Func<T, T> normalize, T init, string file_ext, Func<T, T, bool> profile_equals = null, uint settings_id = 0) {
_vm_factory = vm_factory;
if (profile_equals == null)
profile_equals = EqualityComparer<T>.Default.Equals;
// profile collections
var skip_local = profiles.WrapSkip(1);
var skip_local_wrapper = skip_local.WrapSelector(
@@ -185,12 +187,19 @@ namespace mbts.spark {
continue;
i = ~i;
var temp = ns.with(value: normalize(ns.value));
skip_local_wrapper.Insert(i, temp);
if (settings_id == temp.id) {
if (temp.value.Equals(init))
selected_profile = skip_local[i];
settings_id = 0;
if (profile_equals(temp.value, init)) {
if (!temp.value.Equals(init)) {
// they aren't exactly equal (don't overwrite any props in 'init' - use it to create the vm)
temp = temp.with(value: init);
}
skip_local_wrapper.Insert(i, temp);
selected_profile = skip_local[i];
continue;
}
}
skip_local_wrapper.Insert(i, temp);
}
}));
+7 -1
View File
@@ -114,10 +114,16 @@ namespace mbts.ui
}
public void Dispose() {
foreach (IShellModule module in _modules.OrderBy(m => m.priority).Reverse()) {
foreach (var module in _modules.OrderBy(m => m.priority).Reverse()) {
module.Dispose();
}
_modules.Clear();
foreach (var daemon in _services) {
if (daemon is IDisposable d) {
d.Dispose();
}
}
_services.Clear();
}
}
}
@@ -12,8 +12,7 @@ using System.Windows.Input;
namespace mbts.ui.shell {
[Export(typeof(ILauncher))]
public class LauncherVM : ViewModelBase, ILauncher
{
public class LauncherVM : ViewModelBase, ILauncher {
Window _window;
string _user;
readonly IBus _bus;
@@ -23,9 +22,9 @@ namespace mbts.ui.shell {
[ImportingConstructor]
public LauncherVM(
ObservableCollection<IShellModule> modules,
[Import(LoginContainer.Exports.UserName)]string username,
UserTuple user,
ErrorReporter errors, IBus bus) {
_user = username;
_user = user.name;
_modules = new ObservableAutoWrapper<ShellModuleVM, IShellModule>(modules, m => new ShellModuleVM(m, bus));
_error_service = errors;
_bus = bus;
@@ -123,7 +122,7 @@ namespace mbts.ui.shell {
_shown = true;
if (_window != null) {
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
if (shown != null) shown();
shown?.Invoke();
_window.Show();
}
}
@@ -140,4 +139,4 @@ namespace mbts.ui.shell {
#endregion
}
}
}
@@ -42,8 +42,8 @@ namespace mbts.ui.shell
try {
Dispatcher.Run();
}
catch (Exception e) {
Log.assert(false, e.ToString());
catch (Exception ex) {
Log.assert(false, $"Exception on dispatcher thread '{name}': {ex}.");
}
finally {
Log.Ui.info("Disposing slow {0} and cleaning up background thread.".fmt(name));
@@ -20,8 +20,8 @@ namespace mbts.spark {
readonly IRaiseCanExecuteChangedCommand _add_criteria_command, _remove_criteria_command, _ok_button_command;
bool _allow_empty, _is_edit_mode;
SecurityImmutable? _pending_sec;
(SecurityCriteria criteria, SecurityImmutable? sec, SecurityMD? md)? _snapshot;
uint? _pending_sec_key;
(SecurityCriteria criteria, uint? sec, SecurityMD? md)? _snapshot;
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null)
: this(bus.subscribe(SecuritiesTag), edit_mode, existing_criteria, world_view_filter) { }
@@ -209,9 +209,9 @@ namespace mbts.spark {
_update_security_types();
_update_symbols();
_update_securities();
if (_pending_sec.HasValue && changed_securities.ContainsKey(_pending_sec.Value.Id)) {
select_security_request?.Invoke(_pending_sec.Value);
_pending_sec = null;
if (_pending_sec_key.HasValue && changed_securities.TryGetValue(_pending_sec_key.Value, out var smd)) {
select_security_request?.Invoke(smd.security);
_pending_sec_key = null;
}
}
internal void criteria_selection_changed(IEnumerable<SecurityCriteriaClause> selected_clauses) {
@@ -229,6 +229,8 @@ namespace mbts.spark {
security_criteria_clause_changed?.Invoke(this, e);
}
public bool is_editing => _snapshot.HasValue;
public ObservableCollection<Venue> all_exchanges { get; } = new ObservableCollection<Venue>();
public List<Venue> selected_exchanges = new List<Venue>();
@@ -298,7 +300,22 @@ namespace mbts.spark {
if (all_securities.ContainsKey(value.Value.security.Id)) {
select_security_request?.Invoke(value.Value.security);
} else {
_pending_sec = value.Value.security;
_pending_sec_key = value.Value.security.Id;
}
}
}
}
}
public uint? selected_security_key {
get => selected_securities.Count == 1 && all_securities.TryGetValue(selected_securities[0], out var smd) ? smd.security.Id : (uint?)null;
set {
if (value != selected_security_key) {
if (value.HasValue) {
if (all_securities.TryGetValue(value.Value, out var smd)) {
select_security_request?.Invoke(smd.security);
} else {
_pending_sec_key = value.Value;
}
}
}
@@ -313,7 +330,7 @@ namespace mbts.spark {
if (all_securities.ContainsKey(value.Value.Id)) {
select_security_request?.Invoke(value.Value);
} else {
_pending_sec = value.Value;
_pending_sec_key = value.Value.Id;
}
}
}
@@ -336,18 +353,19 @@ namespace mbts.spark {
}
#region IEditableObject
public void BeginEdit() => _snapshot = (result_criteria, _pending_sec ?? selected_security, selected_security_md);
public void BeginEdit() => _snapshot = (result_criteria, _pending_sec_key ?? selected_security_key, selected_security_md);
public void EndEdit() => _snapshot = null;
public void CancelEdit() {
if (_snapshot.HasValue) {
var (criteria, sec, md) = _snapshot.Value;
security_criteria_clauses.SetItems(criteria.clauses);
selected_security = sec;
selected_security_key = sec;
/// A <see cref="_pending_sec"/> may have been used to create the snapshot when <see cref="BeginEdit"/> was called (this happens when securities are loaded lazily).
/// If this is the case, pending sec was assigned to the <see cref="_snapshot"/> security;
/// check if the security is now present in <see cref="all_securities"/>, if so, use that value instead of the null.
selected_security_md = sec.HasValue && !md.HasValue && all_securities.TryGetValue(sec.Value.Id, out var md_match) ? md_match : md;
selected_security_md = sec.HasValue && !md.HasValue && all_securities.TryGetValue(sec.Value, out var md_match) ? md_match : md;
_snapshot = null;
RaisePropertyChanged(nameof(selected_security));
_add_criteria_command.RaiseCanExecuteChanged();
_remove_criteria_command.RaiseCanExecuteChanged();
}
@@ -2,6 +2,7 @@
using mbts.spark;
using mbts.ui.converters;
using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -16,8 +17,11 @@ namespace mbts.ui.controls {
partial class ContractPickerPopup : PopupControl {
public static readonly EventHandler
FlushSelectedCriteriaHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedCriteriaProperty)?.UpdateSource(),
FlushSelectedSecurityKeyHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedSecurityKeyProperty)?.UpdateSource(),
FlushSelectedSecurityHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedSecurityProperty)?.UpdateSource();
readonly PropertyUpdateGate _selection_sync_gate = new PropertyUpdateGate();
IReadOnlyDictionary<uint, SecurityMD> _cached_security_map = DictionaryHelper.empty<uint, SecurityMD>();
ContractPickerPopupController _controller;
bool _allow_empty, _is_lazy;
@@ -97,12 +101,55 @@ namespace mbts.ui.controls {
}
#endregion
#region SelectedSecurityKey
public static readonly DependencyProperty SelectedSecurityKeyProperty =
WPFHelper.create_dp<uint?, ContractPickerPopup>(
nameof(SelectedSecurityKey),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, v) => {
o.Controller.model.selected_security_key = v;
o._selection_sync_gate.try_update(() => o._sync_selection_props(o.SelectedSecurityMD, o.SelectedSecurity, v, v));
});
//(o, base_value) => base_value is uint u && u != 0 ? u : (uint?)null);
public uint? SelectedSecurityKey {
get => GetValue(SelectedSecurityKeyProperty) as uint?;
set => SetValue(SelectedSecurityKeyProperty, value);
}
#endregion
enum SecurityProp {
Security,
SecurityMD,
ID
}
void _sync_selection_props(SecurityMD? curr_sec_md, SecurityImmutable? curr_sec, uint? curr_sec_key, uint? target_sec_key) {
if (curr_sec_key != target_sec_key) {
SetCurrentValue(SelectedSecurityKeyProperty, target_sec_key);
}
bool sec_md_dirty = target_sec_key != (curr_sec_md?.security)?.Id;
bool sec_dirty = target_sec_key != curr_sec?.Id;
if (sec_md_dirty || sec_dirty) {
curr_sec_md = target_sec_key.HasValue && _cached_security_map.TryGetValue(target_sec_key.Value, out var smd) ? smd : (SecurityMD?)null;
if (sec_md_dirty)
SetCurrentValue(SelectedSecurityMDProperty, curr_sec_md);
if (sec_dirty)
SetCurrentValue(SelectedSecurityProperty, curr_sec_md?.security);
}
if (!IsEditMode)
SetCurrentValue(SelectedCriteriaProperty, target_sec_key.HasValue ? SecurityCriteria.from_keys(new[] { target_sec_key.Value }) : null);
}
#region SelectedSecurity
public static readonly DependencyProperty SelectedSecurityProperty =
WPFHelper.create_dp<SecurityImmutable?, ContractPickerPopup>(
nameof(SelectedSecurity),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, v) => o.Controller.model.selected_security = v);
(o, v) => {
o.Controller.model.selected_security = v;
o._selection_sync_gate.try_update(() => o._sync_selection_props(o.SelectedSecurityMD, v, o.SelectedSecurityKey, v?.Id));
});
public SecurityImmutable? SelectedSecurity {
get => GetValue(SelectedCriteriaProperty) as SecurityImmutable?;
@@ -115,7 +162,10 @@ namespace mbts.ui.controls {
WPFHelper.create_dp<SecurityMD?, ContractPickerPopup>(
nameof(SelectedSecurityMD),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, v) => o.Controller.model.selected_security_md = v);
(o, v) => {
o.Controller.model.selected_security_md = v;
o._selection_sync_gate.try_update(() => o._sync_selection_props(v, o.SelectedSecurity, o.SelectedSecurityKey, (v?.security)?.Id));
});
public SecurityMD? SelectedSecurityMD {
get => GetValue(SelectedCriteriaProperty) as SecurityMD?;
@@ -248,12 +298,10 @@ namespace mbts.ui.controls {
}
void on_cancel() {
model.CancelEdit();
propagate_to_owner();
owner.CancelClick?.Invoke(owner, EventArgs.Empty);
}
void propagate_to_owner() {
owner.set_current_if_changed(SelectedSecurityMDProperty, owner.Controller.model.selected_security_md);
owner.set_current_if_changed(SelectedSecurityProperty, owner.Controller.model.selected_security);
owner.set_current_if_changed(SelectedSecurityKeyProperty, owner.Controller.model.selected_security_key);
owner.set_current_if_changed(SelectedCriteriaProperty, owner.Controller.model.result_criteria);
}
}
@@ -262,4 +310,4 @@ namespace mbts.ui.controls {
public SecurityCriteria selected_criteria => _selected_criteria.Value;
}
}
}
}
@@ -0,0 +1,13 @@
using System.Windows;
namespace mbts.ui {
class BindingProxy : FrameworkElement {
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(BindingProxy));
public object Value {
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
}
}
+64 -49
View File
@@ -8,7 +8,6 @@ using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
namespace bts.api.core {
@@ -94,38 +93,38 @@ namespace bts.api.core {
new Update<T, TKey>(new T[0], new T[0], new Change<T>[0], new T[0], key_extractor);
public Update<T,TKey> merge(Update<T, TKey> next) {
var comp = new RowEqualityComparer<T, TKey>(_key);
var added = this.added as HashSet<T> ?? new HashSet<T>(this.added, comp);
var added = this.added as KeyedValueSet<TKey, T> ?? new KeyedValueSet<TKey, T>(this.added, _key);
var changes = this.changed as ChangeDict<T,TKey> ?? new ChangeDict<T,TKey>(this.changes, _key);
var removed = this.removed as HashSet<T> ?? new HashSet<T>(this.removed, comp);
var removed = this.removed as KeyedValueSet<TKey, T> ?? new KeyedValueSet<TKey, T>(this.removed, _key);
// Add
added.UnionWith(next.added);
foreach (var v in next.added) {
var k = _key(v);
if (removed.Remove(k, out var oldval)) {
changes.AddOrMerge(new Change<T>(oldval, v));
} else {
added[k] = v;
}
}
// Change
foreach (var c in next.changes) {
if (added.Contains(c.Old)) {
added.Remove(c.Old);
added.Add(c.New);
}
else {
var k = _key(c.Old);
if (!added.Replace(k, c.New))
changes.AddOrMerge(c);
}
}
// Remove
foreach (var r in next.removed) {
if (!added.Remove(r)) {
removed.Add(r);
}
changes.Remove(_key(r));
var k = _key(r);
if (!added.Remove(k))
removed[k] = changes.TryRemove(k, out var ch) ? ch.Old : r;
}
return new Update<T, TKey>(
next.all,
added, changes, removed,
_key
);
_key);
}
public IEnumerable<T> all { get; }
@@ -148,6 +147,39 @@ namespace bts.api.core {
#endif
}
class KeyedValueSet<TKey, TValue> : IEnumerable<TValue> {
readonly Dictionary<TKey, TValue> _cache = new Dictionary<TKey, TValue>();
public KeyedValueSet(IEnumerable<TValue> value, Func<TValue, TKey> key) {
foreach (var v in value)
_cache[key(v)] = v;
}
public TValue this[TKey key] {
set => _cache[key] = value;
}
public bool Remove(TKey key) =>
_cache.Remove(key);
public bool Remove(TKey key, out TValue value) =>
_cache.TryGetValue(key, out value) && _cache.Remove(key);
public bool Replace(TKey key, TValue value) {
if (_cache.ContainsKey(key)) {
_cache[key] = value;
return true;
}
return false;
}
public IEnumerator<TValue> GetEnumerator() =>
_cache.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
class ChangeDict<T, TKey> : IEnumerable<Change<T>> {
Dictionary<TKey, Change<T>> _changes;
Func<Change<T>, TKey> _key;
@@ -167,9 +199,11 @@ namespace bts.api.core {
return null;
}
public void Remove(TKey key) {
public bool Remove(TKey key) =>
_changes.Remove(key);
}
public bool TryRemove(TKey key, out Change<T> result) =>
_changes.TryGetValue(key, out result) && _changes.Remove(key);
#region IEnumerable
public IEnumerator<Change<T>> GetEnumerator() {
@@ -181,31 +215,6 @@ namespace bts.api.core {
#endregion
}
struct RowEqualityComparer<T,TKey> : IEqualityComparer<T>, IEqualityComparer<Change<T>>
where TKey : IEquatable<TKey> {
readonly Func<T,TKey> _key;
public RowEqualityComparer(Func<T,TKey> key) {
_key = key;
}
public bool Equals(T x, T y) {
return _key(x).Equals(_key(y));
}
public bool Equals(Change<T> x, Change<T> y) {
return Equals(x.New, y.New);
}
public int GetHashCode(T obj) {
return _key(obj).GetHashCode();
}
public int GetHashCode(Change<T> c) {
return GetHashCode(c.New);
}
}
public class TableUpdate<T, TKey> where TKey : IEquatable<TKey> {
// warning: this does not have a key_extractor
public static readonly TableUpdate<T, TKey> empty = new TableUpdate<T, TKey>(ImmutableDictionary<TKey, T>.Empty, Array.Empty<T>(), Array.Empty<Change<T>>(), Array.Empty<T>(), null);
@@ -736,13 +745,19 @@ namespace bts.api.core {
return new CompositeDisposable(source.Subscribe(aggregator.on_left), right.Subscribe(aggregator.on_right));
});
public static IObservable<TResult> AggregateLatest<TLeft, TRight, TAggregate, TResult>(this IObservable<TLeft> source, IObservable<TRight> right,
Func<TAggregate, Change<TLeft>, TRight, TAggregate> update_left, Func<TAggregate, TLeft, Change<TRight>, TAggregate> update_right, TAggregate seed,
public static IObservable<TResult> AggregateLatest<TLeft, TRight, TAggregate, TResult>(
this IObservable<TLeft> source,
IObservable<TRight> right,
Func<TAggregate, Change<TLeft>, TRight, TAggregate> update_left,
Func<TAggregate, TLeft, Change<TRight>, TAggregate> update_right,
Func<TAggregate> seed_factory,
Func<TAggregate, TResult> result_selector,
TLeft init_left = default, TRight init_right = default) =>
TLeft init_left = default,
TRight init_right = default) =>
//
Observable.Create<TResult>(o => {
Log.DataFlow.diag($"Create AggregateLatest (Left: {typeof(TLeft)}, Right: {typeof(TRight)}).");
var aggregator = new ConcurrentAggregator<TLeft, TRight, TAggregate>(update_left, update_right, agg => o.OnNext(result_selector(agg)), seed, init_left, init_right);
var aggregator = new ConcurrentAggregator<TLeft, TRight, TAggregate>(update_left, update_right, agg => o.OnNext(result_selector(agg)), seed_factory(), init_left, init_right);
return new CompositeDisposable(source.Subscribe(aggregator.on_left), right.Subscribe(aggregator.on_right));
});
@@ -922,4 +937,4 @@ namespace bts.api.core {
$"T is '{typeof(T)}', TKey is '{typeof(TKey)}'";
}
}
}
}
@@ -18,7 +18,7 @@ namespace bts.api.core {
return left_source.AggregateLatest<TableUpdate<TLeft, TKey>, TableUpdate<TRight, TKey>, (TableUpdate<TResult, TKey> result, IReadOnlyDictionary<TKey, TLeft> left_state, IReadOnlyDictionary<TKey, TRight> right_state), TableUpdate<TResult, TKey>>(right_source,
process_left,
process_right,
(TableUpdate<TResult, TKey>.mk_empty(result_key), new Dictionary<TKey, TLeft>(0), new Dictionary<TKey, TRight>(0)),
() => (TableUpdate<TResult, TKey>.mk_empty(result_key), new Dictionary<TKey, TLeft>(0), new Dictionary<TKey, TRight>(0)),
t => t.result,
TableUpdate<TLeft, TKey>.mk_empty(left_key), TableUpdate<TRight, TKey>.mk_empty(right_key));
// left
@@ -20,7 +20,7 @@ namespace bts.api.core {
right_source,
process_left,
process_right,
(TableUpdate<TResult, TLeftKey>.mk_empty(result_key), new Dictionary<TRightKey, HashSet<TLeftKey>>()),
() => (TableUpdate<TResult, TLeftKey>.mk_empty(result_key), new Dictionary<TRightKey, HashSet<TLeftKey>>()),
state => state.result,
TableUpdate<TLeft, TLeftKey>.mk_empty(left_key),
TableUpdate<TRight, TRightKey>.mk_empty(right_key));
@@ -1,15 +1,28 @@
using System;
namespace bts {
struct UserTuple : IEquatable<UserTuple>, IComparable<UserTuple>, IComparable {
public readonly struct UserTuple : IEquatable<UserTuple>, IComparable<UserTuple>, IComparable {
const string _admin_name = "bts";
public static readonly Func<UserTuple, uint> key_fn = u => u.id;
public static UserTuple admin => new UserTuple(0, _admin_name, Permissions.Admin);
public readonly uint id;
public readonly string name;
public readonly Permissions permissions;
public UserTuple(uint id, string name) {
public UserTuple(uint id, string name)
: this(id, name, name == _admin_name ? Permissions.Admin : Permissions.Regular) { }
public UserTuple(uint id, string name, Permissions permissions) {
this.id = id;
this.name = name;
this.permissions = permissions;
}
public enum Permissions {
Regular,
Admin
}
public int CompareTo(UserTuple other) {
@@ -28,4 +41,9 @@ namespace bts {
public static bool operator ==(UserTuple a, UserTuple b) => a.Equals(b);
public static bool operator !=(UserTuple a, UserTuple b) => !a.Equals(b);
}
}
public static class UserTupleExtensions {
public static bool can_access_user_data(this UserTuple user, uint user_id) =>
user.id == user_id || user.permissions == UserTuple.Permissions.Admin;
}
}
+33 -39
View File
@@ -13,84 +13,78 @@ namespace bts.md {
enum MDSourceOptions {
DeferInit = 1,
}
class MDSource : IObserver<List<SecurityAndHandle>>, IDisposable
{
class MDSource : IObserver<List<SecurityAndHandle>>, IDisposable {
const uint _no_user_id = 0;
protected readonly MDConsumer _consumer = new MDConsumer();
protected readonly Co2RelayControl _relay;
protected DateTime _version = DateTime.MinValue;
protected readonly ConcurrentDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new ConcurrentDictionary<SecurityKey, SecurityMD>();
protected List<IDisposable> _dispose = new List<IDisposable>();
protected readonly MDSourceOptions _options;
readonly uint _user_id;
protected DateTime _version = DateTime.MinValue;
protected List<IDisposable> _dispose = new List<IDisposable>();
public MDSource(uint naggle_ms, MDSourceOptions options = 0, uint user_id = _no_user_id)
{
readonly UserTuple _user;
readonly BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>> _on_securities =
new BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>>(new ConcurrentDictionary<SecurityKey, SecurityMD>());
public MDSource(uint naggle_ms, MDSourceOptions options = 0)
: this(naggle_ms, options, UserTuple.admin) { }
public MDSource(uint naggle_ms, MDSourceOptions options, UserTuple user) {
_options = options;
_user_id = user_id;
_user = user;
_relay = new Co2RelayControl(_consumer, naggle_ms);
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
_dispose.Add(_consumer.on_security.Subscribe(this));
if(!options.HasFlag(MDSourceOptions.DeferInit))
if (!options.HasFlag(MDSourceOptions.DeferInit))
initialize();
}
readonly BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>> _on_securities =
new BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>>( new ConcurrentDictionary<SecurityKey, SecurityMD>() );
public IObservable<ConcurrentDictionary<SecurityKey, SecurityMD>> on_securities => _on_securities;
virtual public void initialize()
{
public virtual void initialize() =>
Task.Run(run);
}
public void set_token(AuthTokenImmutable token)
{
public void set_token(AuthTokenImmutable token) =>
_relay.set_token(token);
}
public void OnCompleted() { }
public void OnError(Exception error) => Log.Md.error(error.ToString());
public void OnError(Exception error) =>
Log.Md.error(error.ToString());
public void OnNext(List<SecurityAndHandle> values) {
if (values.Count > 0) {
foreach (var v in values) {
if (_user_id != _no_user_id &&
v.security.ContractCase == Security.ContractOneofCase.Synthetic &&
v.security.Synthetic.UserId != _user_id &&
v.security.Synthetic.UserId != _no_user_id) {
continue;
if (v.security.ContractCase != Security.ContractOneofCase.Synthetic || _user.can_access_user_data(v.security.Synthetic.UserId)) {
var smd = new SecurityMD(new SecurityImmutable(v.security), v.handle);
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
}
var smd = new SecurityMD(new SecurityImmutable(v.security), v.handle);
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
}
_version = DateTime.UtcNow;
}
}
public void Dispose() => Disposable.dispose(_dispose);
async protected Task run()
{
public void Dispose() =>
Disposable.dispose(_dispose);
protected async Task run() {
DateTime run_version = DateTime.MinValue;
while (true)
{
while (true) {
var exch = _exchange_sec_map;
var current_version = _version;
if (current_version > run_version)
{
if (current_version > run_version) {
run_version = current_version;
try
{
try {
Log.Md.info($"MDSource publishing {exch.Count} securities");
_on_securities.OnNext(exch);
}
catch (Exception e)
{
} catch (Exception e) {
Log.Md.error($"Error in publish_securities: {e}");
}
}
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
}
}
}
+4 -6
View File
@@ -1,13 +1,11 @@
using bts.api;
using mbts.co2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace mbts {
struct SecurityMD : IEquatable<SecurityMD> {
readonly struct SecurityMD : IEquatable<SecurityMD> {
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
public SecurityImmutable security { get; }
public MDConsumerHandle md { get; }
@@ -36,4 +34,4 @@ namespace mbts {
return !(left == right);
}
}
}
}
@@ -278,6 +278,9 @@ namespace System.Collections.Generic {
public static IComparer<KeyValuePair<TKey, TValue>> key_comparer<TKey, TValue>() =>
Impl<TKey, TValue>.key_comparer;
public static IReadOnlyDictionary<TKey, TValue> wrap_value<TKey, TValue>(TKey key, TValue value) =>
new Impl<TKey, TValue>.ValueWrapper(key, value);
static class Impl<TKey, TValue> {
public static readonly IReadOnlyDictionary<TKey, TValue> empty =
new ReadOnlyDictionary<TKey, TValue>(new Dictionary<TKey, TValue>(0));
@@ -297,6 +300,57 @@ namespace System.Collections.Generic {
public int Compare(object x, object y) =>
x is KeyValuePair<TKey, TValue> xkv ? (y is KeyValuePair<TKey, TValue> ykv ? Compare(xkv, ykv) : 1) : -1;
}
public class ValueWrapper : IReadOnlyDictionary<TKey, TValue> {
readonly TKey _key;
readonly TValue _value;
readonly IEqualityComparer<TKey> _key_comparer;
public ValueWrapper(TKey key, TValue value)
: this(key, value, EqualityComparer<TKey>.Default) { }
public ValueWrapper(TKey key, TValue value, IEqualityComparer<TKey> key_comparer) {
_key = key;
_value = value;
_key_comparer = key_comparer;
}
public TValue this[TKey key] =>
_key_comparer.Equals(key, _key) ? _value : throw new KeyNotFoundException();
public int Count => 1;
public IEnumerable<TKey> Keys {
get {
yield return _key;
}
}
public IEnumerable<TValue> Values {
get {
yield return _value;
}
}
public bool ContainsKey(TKey key) => _key_comparer.Equals(key, _key);
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
yield return new KeyValuePair<TKey, TValue>(_key, _value);
}
IEnumerator IEnumerable.GetEnumerator() {
yield return new KeyValuePair<TKey, TValue>(_key, _value);
}
public bool TryGetValue(TKey key, out TValue value) {
if (_key_comparer.Equals(key, _key)) {
value = _value;
return true;
}
value = default;
return false;
}
}
}
}
}
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff. Show more