Compare commits

...
Author SHA1 Message Date
Kevin Darby e0fdf74a73 fix crash where account name > 12 chars, tested with long account name 2019-07-11 17:51:45 -04:00
Kevin Darby ebedd17129 Merge branch 'master' of bitbucket.org:bluetradesys/spark into excel_algos 2019-07-11 14:59:19 -04:00
Kevin Darby 0f8ed5dacb range to vector mapping 2019-07-11 14:15:06 -04:00
Pedro Pinto 8553229119 Merge branch 'master' into prod 2019-07-11 10:45:10 -04:00
Pedro Pinto 4903c12533 _index.md edited online with Bitbucket 2019-07-11 14:23:13 +00:00
Jake Norbut 88f23530c6 Merged in SP-119 (pull request #298)
RTM - SP-119 Ladder settings NullRef

Approved-by: John Colvin <john.colvin@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-11 14:18:45 +00:00
Kevin Darby 14f4452599 . 2019-07-10 23:21:56 -04:00
Kevin Darby 22002c43cf getting there 2019-07-10 18:43:09 -04:00
Kevin Darby 7551e23877 spreadsheet 2019-07-10 00:06:27 -04:00
Kevin Darby 22c92c3e17 ranges... 2019-07-10 00:05:24 -04:00
Kevin Darby 613823e7c5 arrays starting to work 2019-07-09 23:22:18 -04:00
Kevin Darby f0a9a2415d adding notion of bts_parameters 2019-07-09 18:31:05 -04:00
Kevin Darby 708e63e36e almost have C 2019-07-09 14:39:11 -04:00
Jake Norbut 76ff36e514 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-119 2019-07-09 11:54:46 -05:00
Jake Norbut 740c14c261 add LadderSettings santization on paths to VM instantiation 2019-07-09 11:52:41 -05:00
Kevin Darby 53990297e8 parsing right along 2019-07-08 23:54:55 -04:00
Kevin Darby 9570be8bfa it builds, ship it. 2019-07-08 23:16:31 -04:00
Kevin Darby 0b15570d00 more parser 2019-07-08 18:22:12 -04:00
Jake Norbut 33a71f5f47 Merged in SP-105_account-in-ladder (pull request #294)
RTM - SP-105 selected account in ladder

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 22:21:46 +00:00
Pedro Pinto 2940601f4b Updated release notes 2019-07-08 18:15:31 -04:00
Pedro Pinto 1f7939f49a Merge branch 'SP-105_account-in-ladder' of bitbucket.org:bluetradesys/spark into SP-105_account-in-ladder 2019-07-08 18:14:45 -04:00
Pedro Pinto 48e273a607 Merge remote-tracking branch 'origin' into SP-105_account-in-ladder 2019-07-08 17:50:43 -04:00
John Colvin e35634020d Merged in SP-89-offset-algo (pull request #286)
RTM SP-89 offset algo

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:49:12 +00:00
Pedro Pinto 1ede053f59 Merged with master 2019-07-08 17:48:16 -04:00
Pedro Pinto 93824786c7 Updated release notes. 2019-07-08 17:32:33 -04:00
Pedro Pinto 25b8c0a728 Merged with master 2019-07-08 17:31:22 -04:00
Jake Norbut 0e4fe1b47e filter the SelectorProperty source for updates that actually have items; this keeps the currently selected account through server restarts 2019-07-08 16:29:19 -05:00
Jake Norbut cd744963ff Revert "don't revert to default selected setter when server reboots"
This reverts commit d5fdcc6796.
2019-07-08 16:21:46 -05:00
Kevin Darby fb9d877938 Merged in dme (pull request #293)
RTM add mic for DME

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:08:53 +00:00
Pedro Pinto 087b001510 Updated release notes. 2019-07-08 17:03:56 -04:00
Pedro Pinto 02a7c0ddca Merge branch 'master' into dme 2019-07-08 17:01:50 -04:00
Kevin Darby 6afa1d581d Merged in span (pull request #263)
RTM: Span

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:01:01 +00:00
Taha Afzal 5e04abe573 Combining the account lookup for loss_limitm and span 2019-07-08 16:50:30 -04:00
Pedro Pinto 3422a44c87 Updated release notes. 2019-07-08 16:49:32 -04:00
Jake Norbut d5fdcc6796 don't revert to default selected setter when server reboots 2019-07-08 15:37:38 -05:00
Kevin Darby f12217d76c Remove mapping of DME mic per Pedro 2019-07-08 20:12:47 +00:00
Pedro Pinto d85a914046 Merge branch 'master' into span 2019-07-08 15:58:06 -04:00
Pedro Pinto 4fdc890cb5 Merged in ticks_off (pull request #296)
RTM Added ticks_off to top algo server side.

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-07-08 19:55:01 +00:00
Pedro Pinto 3ed0bd9052 Merged with master 2019-07-08 15:54:39 -04:00
Jake Norbut dbdc915ff2 populating mini_ob (LadderMiniOrderBookSettings) in LadderSettings.Default 2019-07-08 14:24:36 -05:00
Jake Norbut 10476c6662 null ref check on LadderSettings.mini_ob.value 2019-07-08 14:22:55 -05:00
Kevin Darby 5fbc1397b5 messing with proj 2019-07-08 14:59:12 -04:00
Kevin Darby 05e09cb977 Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-07-08 14:33:37 -04:00
Kevin Darby 38bd2ef321 Merge branch 'master' of bitbucket.org:bluetradesys/spark into excel_algos 2019-07-08 14:29:03 -04:00
Eric Harding dab4f5acbb Merged in SP-104-HCL (pull request #289)
[RTM] SP-104 HCL

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 18:28:44 +00:00
Eric Harding a20c53b23d Merged in SP-68-crash (pull request #292)
[RTM] SP-68 Fix crash on exit by not touching queue on windows.  insert_or_replace() is a no-op on windows anyway.

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 18:27:59 +00:00
Pedro Pinto 8206f457b5 Updated release notes. 2019-07-08 14:27:37 -04:00
Pedro Pinto d4e6d6293d Merge branch 'master' into SP-68-crash 2019-07-08 14:26:48 -04:00
Kevin Darby d1a2197ba4 Merged in fix_auth_tests2 (pull request #297)
RTM fix auth tests to respect difference betwen admin and trader login
2019-07-08 18:20:09 +00:00
Kevin Darby 369fb55e90 fix auth tests to respect difference betwen admin and trader login 2019-07-08 14:20:14 -04:00
Pedro Pinto 37dacff74d Fixed a bug in top update 2019-07-08 14:09:37 -04:00
Pedro Pinto d297b703c7 Merge branch 'master' into ticks_off 2019-07-08 13:36:53 -04:00
Kevin Darby 59c00203a9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into excel_algos 2019-07-08 13:10:23 -04:00
Kevin Darby 33f533af9b set span to default off, hook up admin boolean to enforce span behind config key 2019-07-08 13:08:18 -04:00
Kevin Darby 085d6402f4 Merged in SPARK-2524-keycode_fixes (pull request #290)
RTM fixed SPARK-2524, allowing admin logins to not require a keycode session

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 16:26:51 +00:00
Pedro Pinto f4395ef833 Updated release notes. 2019-07-08 12:26:27 -04:00
Pedro Pinto e7a9412124 Updated release notes. 2019-07-08 12:21:39 -04:00
Pedro Pinto da4ee38abd Merge branch 'master' into SPARK-2524-keycode_fixes 2019-07-08 12:20:50 -04:00
Kevin Darby 706e5bc73b excel 2019-07-08 11:22:53 -04:00
Pedro Pinto af39fac368 Merge branch 'master' into SPARK-2524-keycode_fixes 2019-07-08 11:18:39 -04:00
Michael Harding 4f997505f3 Enforce Margin Limit in Spark Admin 2019-07-08 11:12:55 -04:00
Jake Norbut fcbb66361d Merged in SP-103_no_reset_on_agg_side_change (pull request #288)
RTM - SP-103 don't reset last when aggressor side changes

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:46:17 +00:00
Pedro Pinto fa16321691 Updated release notes. 2019-07-08 10:45:56 -04:00
Pedro Pinto ceb3542e15 Merge branch 'master' into SP-103_no_reset_on_agg_side_change 2019-07-08 10:30:25 -04:00
Jake Norbut 32e55d07ee Merged in SP-110_volume_tooltip_nit (pull request #295)
RTM - display tooltip again when re-entering same cell where tooltip was previouly closed

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:28:34 +00:00
Pedro Pinto e079acf91f Updated release notes. 2019-07-08 10:28:18 -04:00
Pedro Pinto fecedb203a Merge branch 'master' into SP-110_volume_tooltip_nit 2019-07-08 10:26:59 -04:00
Jake Norbut 6ac05a47e1 Merged in SP-103_bars_not_req (pull request #291)
RTM - SP-103 bars not required to show last text

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:15:36 +00:00
Pedro Pinto 36ed80dc87 Updated release notes. 2019-07-08 10:15:14 -04:00
Kevin Darby 6cf395199e working on visitor 2019-07-06 23:25:51 -04:00
Kevin Darby 1707c3268f excel formula grammar, antlr4 integration. Successful parse tree 2019-07-05 18:24:41 -04:00
John Colvin f5bbc4245f SP-111 / SP-113 Add ticks off parameter to top algo UI 2019-07-05 16:12:32 -05:00
Pedro Pinto 96fc54ff0f Added ticks_off to top algo server side. 2019-07-05 14:35:18 -04:00
Kevin Darby 25b69e4f7d fix small bug and add manual test 2019-07-05 13:29:22 -04:00
Kevin Darby d871ffca6d temporarily tie span enforcement to config while michael works 2019-07-05 12:59:41 -04:00
Jake Norbut 213567bcdb display tooltip again when re-entering same cell where tooltip was previouly closed 2019-07-05 11:49:34 -05:00
Kevin Darby b7e5c3a374 Merge branch 'span' of bitbucket.org:bluetradesys/spark into span 2019-07-05 12:30:10 -04:00
Kevin Darby ae4a8bbf46 more span 2019-07-05 12:28:58 -04:00
Taha Afzal 850386c1d4 Margin enforced in the limiter 2019-07-05 12:05:16 -04:00
Kevin Darby 2d4e94a636 Types.fs edited online with Bitbucket 2019-07-05 15:39:35 +00:00
Kevin Darby 9b73ad2486 playing with excel parsing 2019-07-05 11:06:19 -04:00
Pedro Pinto e447096733 Routed excess liquidity to OrderManager. 2019-07-05 11:00:42 -04:00
Jake Norbut cd68b95a3e add a mechanism for ignoring InlineUserProperty's force_override property 2019-07-05 08:19:05 -05:00
Kevin Darby 205eebb7da excel formulae 2019-07-04 23:43:22 -04:00
Kevin Darby ef4dc1fc65 more excel 2019-07-04 23:25:28 -04:00
Michael Harding ebad5b389f Fix Save button 2019-07-03 16:01:50 -04:00
John Colvin 160c49514a Merge remote-tracking branch 'origin/master' into SP-89-offset-algo 2019-07-03 14:38:50 -05:00
Michael Harding 4f7fcc5634 Move loss limits to the top of the limits page 2019-07-03 14:54:40 -04:00
Michael Harding 500e5b5fe4 Polling and commas 2019-07-03 14:19:51 -04:00
Michael Harding 9d924fa4ba Margin table formatting 2019-07-03 10:14:01 -04:00
John Colvin 092e63e300 OffsetAlgo attach impl (take 2) 2019-07-03 00:07:23 -05:00
Kevin Darby 4654cd4739 changes in margin workflow for webui 2019-07-02 18:29:10 -04:00
Kevin Darby f2f06d76de PR feedback 2019-07-02 17:43:28 -04:00
Kevin Darby 6bc293a58e add mic for DME 2019-07-02 17:35:29 -04:00
Kevin Darby fdb462eda4 simplify margin server states 2019-07-02 17:34:46 -04:00
Kevin Darby cc579034b2 fix bug in margin service 2019-07-02 17:05:22 -04:00
John Colvin 4b76e91757 SP-89 (pr edit) - 'can_consume_attachment()' returns false for 'Offset' 2019-07-02 15:48:24 -05:00
Eric Harding 8d34279ab0 SP-68 Fix crash on exit by not touching queue on windows. insert_or_replace() is a no-op on windows anyway. 2019-07-02 16:26:34 -04:00
John Colvin 7973b927a2 SP-89 (pr edit) - fwd price updates to child; cache update qty 2019-07-02 15:22:54 -05:00
Kevin Darby 6997aed9bd fixed SPARK-2524, allowing admin logins to not require a keycode session 2019-07-02 15:05:23 -04:00
Michael Harding b5d7d98098 Add DayPnL to SA Margin tab 2019-07-02 15:04:55 -04:00
Jake Norbut 8f22a326ab last on volume foreground text is now white 2019-07-02 14:01:50 -05:00
Jake Norbut b2c3488c5e draw volume numbers when bars not present 2019-07-02 14:01:07 -05:00
Michael Harding 23f10eac70 Add accountPnl to emptyMargin 2019-07-02 14:10:27 -04:00
Kevin Darby cb31269a70 added pnl impact to margin 2019-07-02 13:14:46 -04:00
Jake Norbut 5b75b8718a updating comment to reflect change 2019-07-02 11:14:01 -05:00
Eric Harding 5a256d4634 SP-104 PEBKAC buy with negative mult is nonsense 2019-07-02 11:55:55 -04:00
Jake Norbut db2de3e8c4 remove last_side; change StructLayout Size accordingly 2019-07-02 10:53:11 -05:00
Jake Norbut b9dcad9549 don't reset last trade size when aggressor changes 2019-07-02 10:52:21 -05:00
Jake Norbut ce104479e1 removing last_side; incrementing shm_version 2019-07-02 10:51:06 -05:00
Eric Harding 2d56eedf3e Bugfix in synthetic_book_merge_tests 2019-07-02 10:37:47 -04:00
Eric Harding 049381908c update gitignore 2019-07-02 10:34:28 -04:00
John Colvin 0067e52155 SP-89 (pr edit) - wait 4 mkt 2 launch; attach impl 2019-07-02 00:07:17 -05:00
Kevin Darby 8ed6d297a9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-07-01 18:01:34 -04:00
Kevin Darby 474401cddc new spreadsheet 2019-07-01 17:59:18 -04:00
Taha Afzal a06df5099a Merged in SP-24-loss-limit (pull request #285)
RTM SP-24 loss limit

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 21:44:41 +00:00
Pedro Pinto 8ddd5bdbb5 Merged with master 2019-07-01 17:06:00 -04:00
Taha Afzal 5cbe00beab Spread PNL is not counted towards the Account PNL 2019-07-01 16:33:56 -04:00
Kevin Darby 3ebcf9a270 example of join in excel 2019-07-01 16:21:38 -04:00
Jake Norbut 40cd826260 Merged in SP-79 (pull request #280)
RTM - SP-79 arrow indicator on last disappearing too fast

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 20:03:34 +00:00
Pedro Pinto 8bd63f11b1 Updated release notes. 2019-07-01 16:03:13 -04:00
Pedro Pinto 2198a8d6ed Merge remote-tracking branch 'origin' into SP-79 2019-07-01 16:02:15 -04:00
Pedro Pinto 8023dc7aa8 Merge branch 'master' into SP-79 2019-07-01 16:01:53 -04:00
John Colvin e2b0da8103 Merged in SP-78-miniob-settings (pull request #282)
RTM SP-78 In miniob settings changing the column width directly through the grid (i.e. not through the dialog) has no effect beyond the current popup.

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 19:36:01 +00:00
Pedro Pinto aea233d537 Updated release notes. 2019-07-01 15:35:07 -04:00
Pedro Pinto 5756c090f3 Merge remote-tracking branch 'origin' into SP-78-miniob-settings 2019-07-01 15:28:59 -04:00
John Colvin 5ab0acb0ef Merged in SP-81-private-synths (pull request #284)
RTM SP-81 private synths

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 19:28:00 +00:00
Pedro Pinto 3950e5b634 Updated release notes. 2019-07-01 15:27:29 -04:00
Pedro Pinto 7ce8f1ed97 Updated release notes 2019-07-01 14:57:00 -04:00
Michael Harding 5d53492ab5 Merged SP-38-span-admin into span 2019-07-01 14:53:52 -04:00
Michael Harding 90fd5bd382 Handle save errors better 2019-07-01 14:52:40 -04:00
Michael Harding beff060a4f Update unchanged margin values 2019-07-01 14:27:44 -04:00
Michael Harding bc3418734f Span Columns and Formatting 2019-07-01 13:22:27 -04:00
Michael Harding ab255c8777 update ui.fsproj 2019-07-01 12:51:17 -04:00
Michael Harding cc2bde1064 Merged span into SP-38-span-admin 2019-07-01 12:43:16 -04:00
Michael Harding 94d66a8e99 Add Margin tab to Spark Admin 2019-07-01 12:40:16 -04:00
Taha Afzal 92f08b89cc Added a sub test to the clip limits test to verify that spread clip limits are independent of futures 2019-07-01 09:01:46 -04:00
Taha Afzal 02769479ea SP-74 bundled into this (it was a small change) 2019-06-30 22:45:29 -04:00
John Colvin 4ada322c56 bring back OffsetOrder gui in ladder/ticket 2019-06-30 12:56:59 -05:00
John Colvin 7de7f6e496 remove old OffsetOrder.cpp/h, replace with new OffsetAlgo in CMakeDeps 2019-06-30 12:47:05 -05:00
John Colvin 0700e9e80d SP-89 Port Offset order to new api 2019-06-29 16:10:34 -05:00
Taha Afzal ccca85d1c5 loss limit manual test updated 2019-06-27 15:46:44 -04:00
Taha Afzal 60e933fca9 SOD also indexes its data on account_id, Cleaned up Limiter's account_data (there was an overlap, after introducing Loss limit related PNL data) 2019-06-27 15:20:12 -04:00
Taha Afzal e2aa6034ab merging master 2019-06-26 20:22:05 -04:00
Taha Afzal 8b4c64e8f4 PositionServiceServer not indixes the state based on account_id istead of names 2019-06-26 19:34:46 -04:00
John Colvin b5ea399e7e WorkspacesDialogView - remove 'Is Shared' checkbox 2019-06-26 15:22:14 -05:00
John Colvin 62baa6aa31 MEFExport UserTuple does not need a contract name 2019-06-26 14:19:49 -05:00
John Colvin 87433aa7c3 add comment for 'KnownUsers' prop 2019-06-26 13:41:37 -05:00
John Colvin 3252cab4bb SP-80 / SP-88 Login dialog should not expose user names (because they are private) 2019-06-26 13:22:02 -05:00
John Colvin fbe9b570a3 (forgot to run 'git add' again) 2019-06-26 12:43:48 -05:00
John Colvin 1bcb209961 remove unused delegate type, and worthless commented line 2019-06-26 11:42:47 -05:00
John Colvin b0726ad3ea just noticed some iobservables in OrderBookVM without OnDispatcher()s (adding them preemptively) 2019-06-26 11:40:10 -05:00
John Colvin e0ba27a0a9 SpreaderModel - remove unneccesary 'async/await' wrappers 2019-06-26 11:40:07 -05:00
John Colvin 345e45c5bf MEFExport UserTuple 2019-06-26 10:28:05 -05:00
John Colvin 490add84d9 replace magic consts w/ regular consts 2019-06-25 23:19:20 -05:00
John Colvin 94fc2c4248 refactor LoginModel.Exports -> LoginContainer.Exports 2019-06-25 23:18:43 -05:00
John Colvin 8a7e2db4ac SP-80 / SP-81 Synths should be private 2019-06-25 19:01:57 -05:00
John Colvin 71c576bbc0 SP-80 / SP-82 Algos should be private 2019-06-25 17:05:41 -05:00
John Colvin 2e4dff4d91 SP-76 In miniob settings, the miniob's width seems to have a minimum that does not reflect the currently displayed columns 2019-06-25 16:20:08 -05:00
John Colvin 73336df814 SP-78 In miniob settings changing the column width directly through the grid (i.e. not through the dialog) has no effect beyond the current popup. 2019-06-25 16:04:24 -05:00
Michael Harding 633edb43eb wait longer for the system to have products 2019-06-25 14:36:52 -04:00
Pedro Pinto f4add8d464 Merged in fix_service_init_concurrency (pull request #281)
Fix service init concurrency

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-25 16:44:35 +00:00
Pedro Pinto 894335d019 Updated release notes 2019-06-25 12:29:56 -04:00
Pedro Pinto 0fc977f5d3 Merged with master 2019-06-25 12:29:02 -04:00
Pedro Pinto 3d70d35b99 Changed Run.cpp to defer qbus dispatch past service initialization. 2019-06-25 12:08:20 -04:00
Pedro Pinto 66307edf91 Added a deferred start option to qbus. 2019-06-25 12:08:00 -04:00
Taha Afzal e3e525819e Merged in SPARK-2506_qho_ib_2 (pull request #279)
RTM
2019-06-25 15:03:53 +00:00
Pedro Pinto ece25dd946 Updated release notes. 2019-06-25 10:04:14 -04:00
Pedro Pinto 24f824fd81 Downgraded logging message. 2019-06-25 10:04:04 -04:00
Pedro Pinto 7d992c84ab Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-25 09:53:45 -04:00
Pedro Pinto 1cce474485 Suppressed updates on completed/canceled orders 2019-06-25 09:51:51 -04:00
Taha Afzal 7186c0349b Added logs for order callbacks on QueueHolderOrder 2019-06-24 20:17:29 -04:00
John Colvin 3d829da004 Merged in SP-49-miniob-settings (pull request #277)
RTM SP-49 miniob settings
2019-06-24 23:48:57 +00:00
John Colvin f20fd2136e SP-78 miniob settings - column width adjustments made directly through the grid should be saved in settings 2019-06-24 17:50:30 -05:00
John Colvin 30ce85b70f fix typo in AlgoID.Equals(object) 2019-06-24 17:41:42 -05:00
John Colvin fc7a04a805 LadderSettings must assign show_warn_on_cross in the constructor with the default value since it is 'true' 2019-06-24 16:58:26 -05:00
Jake Norbut 6d96c0e2a0 fix cross check 2019-06-24 16:52:18 -05:00
Pedro Pinto ae913962b3 Updated release notes 2019-06-24 17:41:04 -04:00
Pedro Pinto b52f6fdc24 Merge remote-tracking branch 'origin' into SP-49-miniob-settings 2019-06-24 17:24:27 -04:00
Taha Afzal 29d14404e6 PositionService has access to AdminService to resolve accounts now 2019-06-24 17:15:04 -04:00
John Colvin d13f1b04ed LadderWindowFactory - actually dispose the settings subscription when ladder closes 2019-06-24 16:06:11 -05:00
John Colvin 8d234d9b8b LadderSettingsView - 'Auto Clear' default value checkbox's checkmark was backwards 2019-06-24 15:32:22 -05:00
John Colvin d261d6fc31 LadderSettingsView - fix 'Order Quantities' tab 'hint text' vertical alignment (apparently RowDefinition.Height is a just a suggestion to wpf - using MaxHeight now) 2019-06-24 14:52:23 -05:00
John Colvin 343bf25aaf ShellSettingsDialog - increase LadderSettingsView width to fit all tabs in 1 row 2019-06-24 14:47:42 -05:00
John Colvin 0f93757506 Merge remote-tracking branch 'origin/SP-51_cross_warning' into SP-49-miniob-settings 2019-06-24 14:42:26 -05:00
Taha Afzal 60b7966f2f Loss limit manual test 2019-06-24 15:33:47 -04:00
John Colvin 6cad454f9e Merged in ladder-settings-round2 (pull request #273)
RTM Ladder settings round2

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-24 19:32:05 +00:00
Pedro Pinto b3f6649e7f Updated release notes. 2019-06-24 15:30:26 -04:00
Jake Norbut 1059351334 adding the settings_subscription to be disposed; fixes swallowed NullRef; not directly related to this branch 2019-06-24 14:15:53 -05:00
John Colvin 3263cc840a SP-77 in miniob settings, changing the column order does not seem to have an effect 2019-06-24 14:15:50 -05:00
Pedro Pinto 8d0e93a311 Merge remote-tracking branch 'origin' into ladder-settings-round2 2019-06-24 15:04:29 -04:00
Jake Norbut 179ee000a3 adding case for when the ladder is first brought up and the market has not yet moved 2019-06-24 14:02:24 -05:00
Jake Norbut 43129427f6 fix arrow disappearing from non-Last-based renders 2019-06-24 13:20:38 -05:00
Pedro Pinto f49d73640e Merge remote-tracking branch 'origin' into SPARK-2506_qho_ib_2 2019-06-24 14:18:34 -04:00
Jake Norbut 7afe1e103d refactor string formatting out of LadderRender 2019-06-24 13:18:29 -05:00
Jake Norbut c1b701ff2b Merged in SP-73 (pull request #274)
RTM - add null check on LadderVM

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-06-24 18:16:37 +00:00
John Colvin 06ca15ccac Merged in SP-75-ladder-security (pull request #275)
RTM SP-75 OrderBook's Ladder uses actual selected order's to get security (not roots)

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-24 18:15:15 +00:00
Pedro Pinto 027efa9d2a Updated release notes 2019-06-24 14:14:35 -04:00
Pedro Pinto 026695d0d1 Merged with master 2019-06-24 14:01:09 -04:00
Jake Norbut 50775b3925 Merged in SP-50-prime (pull request #278)
SP-50 prime last trade accumulation

Approved-by: John Colvin <john.colvin@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-24 17:59:31 +00:00
Pedro Pinto c695b43647 Updated release notes. 2019-06-24 13:58:13 -04:00
John Colvin 56ace9514a MiniOrderBook - set 'Cancel' column [default]width to 37, give other 'magic consts' widths fields in DefaultColumnWidths 2019-06-24 11:48:06 -05:00
Taha Afzal 7c6a26ebd9 All hooked up, on updates, loss limit is checked only on size increases 2019-06-23 19:25:06 -04:00
John Colvin c6c1659953 ShellSettingsDialog - adjust dialog skrit margin to stretch to the edges of the window 2019-06-23 13:36:59 -05:00
John Colvin dc696e9468 ShellSettingsDialog uses 'Admin' icon (gears) 2019-06-23 13:19:18 -05:00
John Colvin a6189d910c ShellSettingsDialog - apply NegativeResizeBorderThickness margin to content root (not just dialog skirt) 2019-06-23 12:25:01 -05:00
John Colvin 049cc69a4f settings dialogs title matches actual shell window titles 2019-06-23 12:18:10 -05:00
Taha Afzal b2766c283d Setting account id in PositionUnit 2019-06-22 22:05:32 -04:00
John Colvin 0b14b048e4 fix ViewModelBase.backing<string>() conflicts between 'string default_value' arg and 'string[] params invalidating props' 2019-06-22 17:26:07 -05:00
John Colvin ffc03a4055 ExpanderToggleButton was getting clipped in GridSettingsEditor 2019-06-22 16:56:45 -05:00
John Colvin 992eb7b104 added 'ImmutableHeader' flag for MiniOB 'Commit' column 2019-06-22 16:47:37 -05:00
John Colvin 314eca0e89 GridSettings - fix DefaultValue attributes/list initialization 2019-06-22 16:41:17 -05:00
John Colvin 626ec79af4 SortingCriteria implements IEquatable 2019-06-22 16:38:02 -05:00
John Colvin 953272b231 GridSettings (Simple)ColumnVM - remove unneccesary Property<T>s, simplify flags logic 2019-06-22 16:38:02 -05:00
John Colvin 4c185093bb make ShellSettingsDialog resizable 2019-06-22 16:38:02 -05:00
Pedro Pinto 6b5b4ea2b1 Merge branch 'SP-24-loss-limit' of bitbucket.org:bluetradesys/spark into SP-24-loss-limit 2019-06-22 16:30:39 -04:00
Pedro Pinto 6f7c6db918 Updated fill account_id in FillStore. 2019-06-22 16:29:55 -04:00
Taha Afzal a1ead3d1c1 cpp routed correctly now 2019-06-22 14:24:15 -04:00
Taha Afzal 1fc35ee3cd Merge branch 'SP-24-loss-limit' of bitbucket.org:bluetradesys/spark into SP-24-loss-limit 2019-06-21 18:43:31 -04:00
John Colvin af576bbee7 SP-37 / SP-49 Add miniob settings to ladder settings 2019-06-21 17:29:16 -05:00
John Colvin 7b9ed4326a DragDropListBox - null check AdornerLayer 2019-06-21 17:27:42 -05:00
John Colvin 615e585c82 simplify mini ob editable price/qty column insertion 2019-06-21 17:21:32 -05:00
Taha Afzal a014dcc984 abstracted _get_handle in PositionServicePNLHelper::HandleIndex 2019-06-21 17:58:08 -04:00
John Colvin 42645f1246 OrderBookGridSettings.with() was dropping algo_tree_display_mode value 2019-06-21 16:26:29 -05:00
Taha Afzal 1c5507a3f4 QueueHolderOrder's QHOOrderManager kills the spurious price/qty updates 2019-06-21 16:09:19 -04:00
Eric Harding a92dd5a841 SP-50 last_trade accumulation 2019-06-21 15:21:57 -04:00
Eric Harding 91a6c946f8 SP-50 last_trade should accumulate trades at the same price 2019-06-21 14:25:15 -04:00
John Colvin cfbd7855a5 OrderBookVM - remove a few unused methods 2019-06-21 11:51:15 -05:00
John Colvin 00e9862844 refactor 'begin_update()' -> 'defer_updates()' for consistency's sake 2019-06-21 11:46:31 -05:00
John Colvin 4d413fb92c OrderBook - remove security->security_md conversion logic from OrderParamsAggregator, get rid of SelectedSecurityMon 2019-06-21 11:45:19 -05:00
Pedro Pinto 6e07a0c109 Fixed build. 2019-06-21 11:51:20 -04:00
Pedro Pinto e936b80dc6 Forwarded pnl updates to order manager. 2019-06-21 11:45:50 -04:00
Taha Afzal 4046f82e06 refactoring accidentally omitted conditional check 2019-06-21 08:35:40 -04:00
Taha Afzal 699a6526f5 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-20 21:53:10 -04:00
Taha Afzal 12a4c67e4d Modified SingleLegQuoter::_compute_price_quantity to deal with show qty and qho 2019-06-20 21:52:06 -04:00
Jake Norbut e9562b55f3 pr refactoring 2019-06-20 20:16:32 -05:00
Jake Norbut 43735e1dab adding icon param Confirm overload 2019-06-20 20:16:12 -05:00
John Colvin 930723dd0e move ObservableExtensions into spark_core 2019-06-20 18:15:56 -05:00
John Colvin 3e4ee9d6b6 abstract SelectedParamsMon out of OrderBookVM into new class 'OrderParamsAggregator' 2019-06-20 17:11:55 -05:00
John Colvin c771aea311 RefCountMap implements IDictionary/IReadOnlyDictionary 2019-06-20 16:53:16 -05:00
Jake Norbut 7b35467d59 adding warn on cross message box for the approparite criteria 2019-06-20 15:12:01 -05:00
Jake Norbut 192354946d exposing a couple settings as readonly properties so we don't have to expose the entire settings object 2019-06-20 15:09:15 -05:00
Jake Norbut 1eacb7f0f0 adding new Safety Checks tab on which Warn on Market Cross is found 2019-06-20 15:07:16 -05:00
Jake Norbut d0695fd5d7 adding show_warm_on_cross to LadderSettingsVM 2019-06-20 15:05:46 -05:00
Jake Norbut ad8377d13f adding show_warn_on_cross to LadderSettings 2019-06-20 15:04:39 -05:00
Pedro Pinto 60ab67213f Ran clang format. 2019-06-20 15:57:09 -04:00
Pedro Pinto 4055a06601 Merged master 2019-06-20 15:28:27 -04:00
John Colvin 883582630d SP-75 OrderBook's Ladder uses actual selected order's to get security (not roots) 2019-06-20 14:01:47 -05:00
Jake Norbut e7728203cf PR feedback 2019-06-20 12:04:40 -05:00
Jake Norbut dd8586133f add null check on LadderGridHost and LadderVM 2019-06-20 10:18:39 -05:00
Kevin Darby e47af3d7bd update api docs 2019-06-20 10:34:06 -04:00
John Colvin f4aa5cfa8c sec picker in Ladder/Orderbook settings dialogs does not change security when cancel clicked 2019-06-20 00:10:52 -05:00
John Colvin bc3f2db0f9 add OK/Cancel buttons to sec pickers in ladder/orderbook settings dialogs 2019-06-19 19:56:20 -05:00
John Colvin ed08cb11d7 convert ContractPickerPopup ok/cancel actions to events 2019-06-19 19:43:53 -05:00
Pedro Pinto 719d460372 Merged with master 2019-06-19 16:59:50 -04:00
John Colvin 7b0549c4cb (forgot to add 'GlbxIceberg' case when fitting non-synth algos to synth ladders) 2019-06-19 15:47:16 -05:00
John Colvin 2ee0568159 ShellSettingsDialog - added 'not applicable to custom' tooltip for security picker menu item 2019-06-19 15:37:46 -05:00
John Colvin df907fcb96 SPARK-2521 / SPARK-2522 rename 'Local' -> 'Custom' (take 2) 2019-06-19 15:37:46 -05:00
John Colvin b945f9f090 Ladder Profles - synth ladders convert all non-synth default_algos to 'Spreader' 2019-06-19 15:37:46 -05:00
Jake Norbut 4cebe8b6be updating mini ob manual test 2019-06-19 15:25:46 -05:00
Jake Norbut c899904f3a Merged in miniob_deselect (pull request #272)
fixing unselect all
2019-06-19 20:20:50 +00:00
Jake Norbut 8e6ccd5b1b fixing unselect all 2019-06-19 15:16:42 -05:00
Taha Afzal 32eeeee94f Adding logs and removing the show qty hardcoded value in QHO 2019-06-19 16:00:23 -04:00
Pedro Pinto 007054f2b8 Updated release notes. 2019-06-19 15:45:54 -04:00
Jake Norbut 43822134e3 Merged in mini_ob_ireqeustrowselection (pull request #271)
miniob / DIYGrid - keep selected_rows in sync with row_vm.IsSelected

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-06-19 19:26:08 +00:00
John Colvin 68c1415cf8 Merged in SPARK-2489-ladder-settings (pull request #270)
SPARK-2489 ladder settings

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-06-19 19:16:25 +00:00
John Colvin d9b25fb778 threadstatic fields cannot be initialized inline 2019-06-19 14:10:50 -05:00
John Colvin 0a3e12e012 SPARK-2521 / SPARK-2522 rename 'Local' -> 'Custom' 2019-06-19 14:10:50 -05:00
John Colvin 8b33c7fb5c Ladder Profile's 'default_algo' is fit to the security type (allows profiles to be shared between outrights/synths) 2019-06-19 14:10:50 -05:00
Pedro Pinto 831b042c35 Updated release notes. 2019-06-19 15:04:29 -04:00
Pedro Pinto c9af55f59d Fixed clang build. 2019-06-19 14:56:07 -04:00
Pedro Pinto 056176ff47 Hide Cover in the lader when its done with its child. 2019-06-19 14:55:39 -04:00
Pedro Pinto 297a925f22 Fixed a bug in cover where cover would not terminate properly. 2019-06-19 14:54:16 -04:00
John Colvin 11ca6326f3 refactor 'AlgoID.from_order_def' -> 'AlgoID.from_custom_def' 2019-06-19 13:19:09 -05:00
Kevin Darby c3c427742d Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-06-19 13:34:52 -04:00
Jake Norbut efa78ee5fc OrderBookVM now implements IRequestRowSelection and appropriately invokes it via all_rows_selected setter 2019-06-19 09:15:38 -05:00
Jake Norbut c52b98d17c DIYGrid now hooks up to request_selection event and passes that along to the underlying row presenter's SetSelectedItems 2019-06-19 09:13:55 -05:00
Jake Norbut 06506be398 Exposing a public SetSelectedItems that calls the protected ListBox equivalent 2019-06-19 09:11:34 -05:00
Jake Norbut d951942a95 Creating new interface to handle the select all checkboxbox in the mini ob the right way 2019-06-19 09:06:28 -05:00
Michael Harding 1f99cc68f8 Re: the 5:30 problem 2019-06-19 09:51:59 -04:00
Michael Harding d4f27a65f5 Harden system tests against 5:30PM 2019-06-19 09:37:15 -04:00
John Colvin 2b3901fdbe dispose LadderSettingsDialog viewmodel when window closes 2019-06-18 22:44:16 -05:00
John Colvin fa51d7f441 LadderSettingsView - abstract magic const out into StaticResource 2019-06-18 22:14:29 -05:00
John Colvin 2bc54b0dbb LadderSettingsView - 'Market Price/Size' tab Grid.IsSharedSizeScope=True 2019-06-18 19:52:38 -05:00
John Colvin 5b767482d7 LadderSettingsView - rearrange GroupBoxes in 'Left Panel' tab 2019-06-18 19:52:20 -05:00
John Colvin 8594708073 LadderVM.get_save_state - null check for order_properties_vm 2019-06-18 18:47:24 -05:00
John Colvin ac4cdc0e2d Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-18 18:09:08 -05:00
John Colvin c66dfdaf27 LadderSettingsView - add 'Auto Clear' value checkbox, minor style tweaks 2019-06-18 18:00:18 -05:00
Michael Harding 83c432d8e2 Merged in SP-34-loss_limit_admin (pull request #268)
SP-34: Add Loss Limit to Spark Admin

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 21:25:47 +00:00
Pedro Pinto dcd42367e1 Updated release notes. 2019-06-18 17:14:55 -04:00
Jake Norbut 1fd4212a4e Merged in mini_ob_perf (pull request #266)
mini-ob - delaying popup Child control creation until approparite button is actually clicked

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 21:12:13 +00:00
Pedro Pinto aa5b88e1a2 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-06-18 17:07:16 -04:00
Pedro Pinto 3f86968937 Rejected stop and stop limit orders until we can fix them. 2019-06-18 17:06:55 -04:00
Kevin Darby 0385376d04 Merged in fix_auth_tests (pull request #267)
fix auth tests

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 20:55:38 +00:00
Kevin Darby 914ec09b4f fix auth tests 2019-06-18 16:44:54 -04:00
Michael Harding 6a4930767b SP-34: Add Loss Limit to Spark Admin 2019-06-18 16:32:14 -04:00
Kevin Darby fd106559da Merged in key_codes (pull request #253)
RTM: Key codes
2019-06-18 20:16:31 +00:00
Pedro Pinto a1270fb928 Updated release notes. 2019-06-18 16:15:49 -04:00
John Colvin 1f8b0bf1e6 in profile settings dialog, revert restores the 'persisted' profile (not necessarily the same value from when the dialog was opened) 2019-06-18 15:03:55 -05:00
John Colvin 292acd64e6 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-18 14:44:26 -05:00
John Colvin 453f70259a AlgoID.from_algo_key was losing dynamic type name 2019-06-18 14:24:44 -05:00
John Colvin 18a80516d5 SPARK-2489 / SPARK-2499 eliminate 'window specific settings,' treat all props like regular settings 2019-06-18 14:24:43 -05:00
Kevin Darby 685e69adcc Merge branch 'key_codes' of bitbucket.org:bluetradesys/spark into span 2019-06-18 15:21:10 -04:00
Kevin Darby 1ebd0e8d62 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-18 15:08:13 -04:00
Kevin Darby c09ef31061 Added a warning message and facility to allow concurrent logins in a keycode 2019-06-18 15:07:08 -04:00
John Colvin 613ba8004d LadderView - make Min/MaxZoomFactor consts (were StaticResources) 2019-06-18 13:55:08 -05:00
John Colvin a1ffc24ed9 LadderVM - size_calculator uses shared settings subscription 2019-06-18 13:55:07 -05:00
John Colvin c40055dd2f SPARK-2493 Add multiple profiles for ladder settings 2019-06-18 13:55:07 -05:00
Jake Norbut 163b77ff84 mini-ob - delaying popup Child control creation until approparite button is actually clicked 2019-06-18 13:29:49 -05:00
Pedro Pinto 1b1129320a Merged in SP-22-expose-hidden-badge-facility-in-ap (pull request #265)
RTM. SP-22 expose hidden badge facility in ap

Approved-by: Taha Afzal <taha.afzal@bluetradesys.com>
2019-06-18 17:39:21 +00:00
Kevin Darby 589476eb02 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-18 13:34:06 -04:00
Taha Afzal fe2b167fd5 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-18 12:06:23 -04:00
Pedro Pinto fd0e1dfb0e Updated release notes. 2019-06-18 12:01:09 -04:00
Pedro Pinto e95eb4a749 Merged with master 2019-06-18 11:59:30 -04:00
Michael Harding 8fb13bd3c3 Merged in spreader_lean_qty (pull request #264)
Spreader lean qty
2019-06-18 14:17:21 +00:00
Jake Norbut 9cfa229b04 Merged in SPARK-2519 (pull request #262)
RTM - mini ob - select/unselect all checkbox in header

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 14:10:06 +00:00
Jake Norbut 72e7901145 Merged in SPARK-2509 (pull request #260)
RTM - SPARK-2509 - distinguish buy and sell better in fills window

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-18 14:09:25 +00:00
Michael Harding c2788e4d61 Harden wait_for_orders_service_ready 2019-06-18 09:58:54 -04:00
Michael Harding 0b5d2bd4dc Fix null security Id 2019-06-18 09:58:08 -04:00
Jake Norbut 98006a07e8 PR - refactoring - eliminating init_brush in favor of implicit BTSColor to frozen Brush operator 2019-06-18 08:04:15 -05:00
Jake Norbut 420273b861 Merge branch 'SPARK-2509' of bitbucket.org:bluetradesys/spark into SPARK-2509 2019-06-18 07:53:34 -05:00
Jake Norbut 09ed017bf2 BTSColors - no need to explicity freeze when we have an implicit operator that can do so, thx Eric 2019-06-18 07:52:55 -05:00
Taha Afzal 04fbcc49b1 Merge branch 'SPARK-2506_qho_ib_2' of bitbucket.org:bluetradesys/spark into SPARK-2506_qho_ib_2 2019-06-17 18:18:49 -04:00
Taha Afzal 32229e3236 removing the hard coded values now that it is hooked to the ui 2019-06-17 18:18:41 -04:00
Kevin Darby e9f61dc3c0 added get_excess_liquidity_report rpc for adminui 2019-06-17 18:15:05 -04:00
Jake Norbut f13949ed0f removing Minimum property on ShowQuantity template 2019-06-17 16:49:16 -05:00
Kevin Darby f329470165 added spreadsheet to help test span 2019-06-17 16:49:21 -04:00
Jake Norbut f2b7e692f8 ShowQuantity param now defaults to 1 and the associated TickUpDownUint has a min of 1 as well 2019-06-17 14:49:39 -05:00
Jake Norbut 2cff906c5f adding minimum maximum properties to TickUpDownUint; adding uint overload for within_range 2019-06-17 14:47:48 -05:00
Jake Norbut f711715350 don't allow parameter modification unless the checkbox is checked 2019-06-17 14:43:28 -05:00
Pedro Pinto 8116700737 Updated release notes. 2019-06-17 14:31:39 -04:00
Pedro Pinto f902f163de Merge branch 'master' into SPARK-2509 2019-06-17 14:30:33 -04:00
Pedro Pinto d06bef463b Merged SP-42-manual-fills-should-not-go-into-so into master 2019-06-17 14:07:02 -04:00
Pedro Pinto 38db7b28e0 Updated manual test. 2019-06-17 14:05:11 -04:00
Taha Afzal e76a72e3e6 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-17 13:37:48 -04:00
John Colvin 485991b046 LadderSettingsVM - assume algos in settings proto exist (avoid 'deleting' favs when create is called before first algo-TableUpdate arrives) 2019-06-17 12:32:57 -05:00
Michael Harding 63cd653ffd Merged master into spreader_lean_qty 2019-06-17 13:10:59 -04:00
Michael Harding cb0ec5f3b9 match sparkdata discovery 2019-06-17 13:08:59 -04:00
Taha Afzal 64e7921280 Lean Algo hides its badge once it has fired 2019-06-17 13:07:16 -04:00
Michael Harding e5d31d3c89 match sparkdata discovery, remove use_onixs: false 2019-06-17 12:58:21 -04:00
Jake Norbut 219da9b22a mini ob - select/unselect all checkbox in header 2019-06-17 11:50:43 -05:00
Pedro Pinto b3bff8ecc3 Updated release notes. 2019-06-17 11:02:01 -04:00
Pedro Pinto c1fb53ff06 Merge branch 'master' into SP-42-manual-fills-should-not-go-into-so 2019-06-17 11:01:11 -04:00
Michael Harding 220af155a9 Merged in Documentation (pull request #259)
Move system requirements to Docs
2019-06-17 14:58:07 +00:00
Pedro Pinto e7286e305d Updated release notes. 2019-06-17 10:57:33 -04:00
Pedro Pinto 770b857f90 Merge branch 'master' into Documentation 2019-06-17 10:56:35 -04:00
Jake Norbut 4b9e4b25c7 Merged in miniob_vnext (pull request #258)
Mini OrderBook client demo feedback
2019-06-17 14:53:50 +00:00
Pedro Pinto 0f87973ee2 Updated release notes. 2019-06-17 10:53:22 -04:00
Pedro Pinto b6790aa7f5 Merge branch 'master' into miniob_vnext 2019-06-17 10:44:57 -04:00
Kevin Darby d7d319f1e3 fixed span timing 2019-06-17 10:43:27 -04:00
Pedro Pinto 6adbfda6c4 Merged in SP-40-cover-should-be-more-decoupled-fro (pull request #261)
RTM. SP-40 cover should be more decoupled fro

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-17 14:42:51 +00:00
Pedro Pinto fef5b4edaa Updated release notes. 2019-06-17 10:42:05 -04:00
Pedro Pinto e8cccdb1c0 Changed manual fills to show in day rather than SOD. 2019-06-17 10:15:52 -04:00
Pedro Pinto 19dc7f8915 Fixed a bug in visibiliy control API and changed Join to hide itself 2019-06-17 09:59:03 -04:00
Pedro Pinto 69f61757c4 Added API support for ladder visibility control. 2019-06-17 09:47:02 -04:00
Taha Afzal cddadcb278 PositionService exposes new streaming calls pnl_data and pnl_account_data to get detailed and summary pnls 2019-06-16 22:02:16 -04:00
John Colvin 666765ffbb LadderSettingsVM implements IObservable<LadderSettings> 2019-06-16 20:43:57 -05:00
John Colvin 82a9b4521a DisposableViewModelBase - in subscribe(), use the _property_changed_demuxer via the property (which lazily inits the backing) 2019-06-16 20:33:35 -05:00
John Colvin 7c329ceebe DisposableViewModelBase - initialize '_observable_properties' lazily in property getter 2019-06-16 20:33:35 -05:00
Kevin Darby 419833d398 slight timing issue with span 2019-06-14 18:10:41 -04:00
Jake Norbut 0f88144fd6 adding new ShowQuantity Enabled + Quantity models 2019-06-14 14:55:15 -05:00
Jake Norbut abe35bddf0 adding ShowQuantity (and params) 2019-06-14 14:54:22 -05:00
Jake Norbut cd262cd66c Adding Show Quantity ParameterSection 2019-06-14 14:52:59 -05:00
Jake Norbut 0133cc8aa5 Adding ShowQuantity Defaults 2019-06-14 14:52:23 -05:00
Pedro Pinto 66d05c63af Made cover cancel behaviour parameterizable.
This implements the server side only, the UI does not expose this
configuration so effectively the behaviour right now is
hard-coded until we get around to adding the UI.
2019-06-14 14:25:04 -04:00
Kevin Darby 9883553fb5 MarginService 2019-06-14 11:21:09 -04:00
Pedro Pinto bc3ab563bf Changed Cover to detach its hedges when canceled. 2019-06-14 10:58:33 -04:00
Pedro Pinto ed23bc7e2b Enhanced the API to allow algos to implement custom cancel handling 2019-06-14 10:50:34 -04:00
Pedro Pinto 3dda3e7f75 Allow cover order to continue even if hedgding child is canceled. 2019-06-14 10:44:38 -04:00
Pedro Pinto ff461d42b8 Merge branch 'master' into prod 2019-06-14 10:13:40 -04:00
Jake Norbut d678d202c1 PR rename on_row_updated 2019-06-14 09:04:09 -05:00
Jake Norbut ad0b1756b1 PR rename on_row_unselected 2019-06-14 09:03:07 -05:00
Jake Norbut f34f718c72 PR rename on_row_selected 2019-06-14 09:02:24 -05:00
Jake Norbut 213fb1de59 PR refactoring cancel 2019-06-14 09:00:30 -05:00
Jake Norbut e8d799a0f6 PR refactoring detach 2019-06-14 09:00:17 -05:00
John Colvin bef46390be ContractPickerPopup - remove automatic Header binding, so callers may override it 2019-06-13 16:54:42 -05:00
Kevin Darby be5d037ca9 span works again 2019-06-13 17:52:47 -04:00
Jake Norbut 4edd804b3c forgot to remove this when I took the resource out 2019-06-13 16:15:22 -05:00
Jake Norbut 86733747f1 getting rid of the ListBoxItem template and just going with the converter 2019-06-13 15:58:40 -05:00
Jake Norbut 5202300fb1 ActualWidth for total visible width down in the HighPerformanceRow per John 2019-06-13 15:58:12 -05:00
Taha Afzal 21f5fd919d Changes in the API for Loss Limit and new parameter in the account to enforce that limit 2019-06-13 15:37:43 -04:00
Jake Norbut 7aae61decd Cleanup related to new RenderTextColumn func; deleting classes, etc 2019-06-13 14:14:45 -05:00
Jake Norbut 64f78e6077 RenderColumn now accepct a Func for background brush 2019-06-13 14:11:53 -05:00
Jake Norbut 318ea5909c adding new brushes to be references from xaml; misc helper function 2019-06-13 13:32:29 -05:00
Jake Norbut 83190b437a defining a new RowStyle for the fills grid that uses a converter based on side 2019-06-13 13:31:33 -05:00
Michael Harding 5a54b37af7 Move system requirements to Docs 2019-06-13 13:07:42 -04:00
Michael Harding 12dd3909bf Fix documentation deploy 2019-06-13 12:49:13 -04:00
John Colvin 5ddb0f92ea ObservantCommand impl was not updating 'CanExecute' 2019-06-13 00:26:05 -05:00
John Colvin 69f3f1f467 normalize line endings in Converters.xaml (why does it feel like i've done this before?) 2019-06-12 23:47:26 -05:00
John Colvin be42f9e583 general purpose ToolBar view 2019-06-12 23:46:50 -05:00
John Colvin 6d069f5174 added ListExtensions.WrapSkip() (returns an IList impl that skips the first n items in the source list) 2019-06-12 23:46:14 -05:00
John Colvin 7a31adcc1e added DictionaryHelper.key_comparer<TKey, TValue>() 2019-06-12 23:45:52 -05:00
John Colvin 512175966d KeyedStyleSelector._try_get_item_key returns false if key is found but null 2019-06-12 23:45:19 -05:00
John Colvin 67de7c5a33 clean up Spark.csproj 2019-06-12 23:44:52 -05:00
John Colvin 8add53a559 move/rename 'WindowSettingsDialogVM' -> 'ShellSettingsDialogVM' 2019-06-12 23:44:18 -05:00
John Colvin 3e7e572763 move/rename 'SimpleGridSettingsDialog' -> 'ShellSettingsDialog' 2019-06-12 23:43:43 -05:00
Kevin Darby 159e8fba23 beginnings of span, should't be long 2019-06-12 18:17:09 -04:00
Jake Norbut 0d020e9821 updating manual test 2019-06-12 14:48:36 -05:00
Jake Norbut fb1d70b08c Merge branch 'master' of bitbucket.org:bluetradesys/spark into miniob_vnext 2019-06-12 14:02:56 -05:00
Jake Norbut c2f6b01567 debugging comment 2019-06-12 10:54:24 -05:00
Jake Norbut 4a758c7554 misc comment cleanup, line endings or something 2019-06-12 10:54:02 -05:00
Jake Norbut 5c79a08f0e popup close event no longer messes with selected_rows 2019-06-12 10:53:15 -05:00
Jake Norbut 4b7adbe0c9 adding tooltip to update button 2019-06-12 09:59:48 -05:00
Jake Norbut 164c88fa60 popup debug comments 2019-06-12 09:56:03 -05:00
Jake Norbut b57d6cce85 select/unselect me only needs to manipulate row.is_selected now 2019-06-12 09:52:22 -05:00
Jake Norbut e47706eb1b guaranteeing on_updating is hooked up to only a single callback; this was a major pain to debug, thx john 2019-06-12 09:49:00 -05:00
Jake Norbut c2e91b03f6 Adding similar IsSelected setter to main OB just in case ... 2019-06-12 09:40:35 -05:00
Jake Norbut 114c9f3c27 Adding resurrected check column back to mini ob, bound to is_selected 2019-06-12 08:28:56 -05:00
Jake Norbut 40805be11e Bringing back the check column in case of SelectionHighlight shenanigans 2019-06-12 08:27:39 -05:00
Jake Norbut b21cd840c2 The first time I met the blues, I was losing my SelectionHighlight; too beat down to remove this Setter that was added over many hours of debugging 2019-06-12 08:23:55 -05:00
Jake Norbut 4854a06eda fix SelectionHighlight databinding expression in mini ob 2019-06-12 08:12:48 -05:00
Jake Norbut 44e7cfb4a5 setting is_selected and clearing selected_rows when update popup closes 2019-06-11 14:06:08 -05:00
Jake Norbut a7da930e69 synchronize selected_rows with _un/select_me() 2019-06-11 14:02:23 -05:00
Jake Norbut d64df27188 don't change is_selected state here b/c it leads to discrepancies when attaching/updating 2019-06-11 12:27:51 -05:00
Jake Norbut 93534eb801 changing row selection while in change breaks the main ob 2019-06-11 10:57:07 -05:00
Taha Afzal d1b2dbd688 Hiding the QHO badge 2019-06-11 08:59:52 -04:00
Taha Afzal bebe0c2478 Merge branch 'master' into SPARK-2506_qho_ib_2 2019-06-10 20:36:05 -04:00
Taha Afzal 61126fb3d5 QHO order qty's are contrained by the show qty now 2019-06-10 20:32:15 -04:00
John Colvin 0af5b78318 Merged in SPARK-2489-ladder-settings (pull request #257)
RTM SPARK-2489 ladder settings

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-10 21:31:24 +00:00
Pedro Pinto c652032ddf Updated release notes. 2019-06-10 17:27:46 -04:00
John Colvin 7bd868b753 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-10 15:23:58 -05:00
John Colvin 1bc4510862 LadderSettingsView - touch up 'Market Price And Size Display' tab 2019-06-10 15:12:38 -05:00
Kevin Darby e452413b92 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-10 16:04:43 -04:00
Taha Afzal b0886f6b3d First check in, needs to be retofitted to QHO now 2019-06-10 15:58:10 -04:00
Kevin Darby 62c7886807 used os::host_ips() 2019-06-10 15:57:53 -04:00
John Colvin b1fb964648 Merged in SPARK-2508-fills-symbol-column (pull request #256)
RTM SPARK-2508 fills symbol column
2019-06-10 19:45:37 +00:00
Pedro Pinto ee19f7c7da Updated release notes. 2019-06-10 15:44:00 -04:00
Jake Norbut a0bf7779f5 mini ob - changes from John; closer to working but not yet, likely related to mark_commited/revert not getting wired up/called 2019-06-10 14:35:06 -05:00
John Colvin cd41632b53 LadderSettingsView - touch up 'Order Quantities' tab 2019-06-10 14:27:54 -05:00
Kevin Darby 95a5791867 Jake was worried about using the same keycode for multiple servers. To combat this I look at ifconfig for
an ip to appear on the list that matches what is optionally encoded in the keycode
2019-06-10 14:51:07 -04:00
John Colvin ecd384501e align LadderSettingsView (content in settings dialog) with hamburger button 2019-06-10 12:22:05 -05:00
John Colvin 0483460ac9 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-10 12:00:08 -05:00
John Colvin 0bc24fa7b7 SPARK-2508 Fills - add 'Symbol' column 2019-06-10 11:48:09 -05:00
Kevin Darby af365177d0 PR feedback and a manual test 2019-06-10 12:46:37 -04:00
John Colvin ac60a1447e stack FillsView.Columns enum values vertically (easier to merge column changes) 2019-06-10 11:32:35 -05:00
Kevin Darby e8b5d4f8fd Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-10 12:00:06 -04:00
Taha Afzal 7fd8eeb1ff Merge branch 'master' into spreader_show_qty 2019-06-10 08:37:32 -04:00
Kevin Darby cb157dbce1 Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-07 18:15:26 -04:00
Jake Norbut 370b4bcde9 coording root row with selected params mon 2019-06-07 16:19:16 -05:00
Jake Norbut 6756b23104 miniob - attach and update select the row if they're not already selected 2019-06-07 16:17:42 -05:00
Jake Norbut c5f7ee0ed3 reassigning the miniob update button to the update_command property so select the row if it's not already selected 2019-06-07 16:16:27 -05:00
Jake Norbut 16eeddec7d OperableHierarchicalCollectionModel - fixing get_parent 2019-06-07 16:10:17 -05:00
John Colvin 445bbb10f5 LadderSettingsView - normalize spacing 2019-06-07 13:42:27 -05:00
John Colvin 70c006f797 SPARK-2489 / SPARK-2502 move 'Alternate Order Qty' into 'Order Quantities' tab 2019-06-07 13:42:27 -05:00
Jake Norbut 307d3d9466 eliminating new mini ob checkbox col and just operating based off of the standard diygrid selected_rows 2019-06-07 10:33:26 -05:00
John Colvin 3a5810d8ac LadderGridHost.OnMouseDown impl should return after handling middle click 2019-06-07 10:12:10 -05:00
Jake Norbut 9ba2ba1d75 refactoring modify_selected_orders to accept a IEnumerable>OrderBookRowVM>; creating a new command for minob that iterates over checked orders to modify 2019-06-07 09:25:25 -05:00
John Colvin 29c772cbb6 SPARK-2489 / SPARK-2501 rename 'Add Size' -> 'Add Quantity' 2019-06-06 23:41:45 -05:00
John Colvin 0b03180c4d SPARK-2489 / SPARK-2500 rename 'Size Presets' -> 'Order Quantities' 2019-06-06 23:39:02 -05:00
John Colvin 1518906b7f SPARK-2489 / SPARK-2498 remove 'Order Badge Style' option 2019-06-06 23:35:45 -05:00
John Colvin 31fe708907 touch up LadderSettingsView 2019-06-06 23:35:37 -05:00
John Colvin 4b2f39c536 LadderSettingsView - drop 'Show/Hide' from 'Advanced' hyperlinks 2019-06-06 22:42:55 -05:00
John Colvin 040960d33b SPARK-2489 / SPARK-2495 merge 'Volume Display' tab into the 'Ladder Columns' tab 2019-06-06 19:31:36 -05:00
Kevin Darby a28d5883f7 better to zero out the market data if not entitled so they don't trade off of it 2019-06-06 17:19:35 -04:00
Kevin Darby 8a1d49733e keycodes work, need some documentation 2019-06-06 16:40:32 -04:00
Kevin Darby 0da6b2cc3e Merge branch 'master' of bitbucket.org:bluetradesys/spark into key_codes 2019-06-06 16:40:05 -04:00
Jake Norbut 3f245cd886 SPARK-2497 - mini ob mass detach 2019-06-06 14:20:00 -05:00
Jake Norbut 570f85377a part of SPARK-2485 - miniob - update buttons not visible unless leaf under control of an algo 2019-06-06 14:00:06 -05:00
Kevin Darby 8afb35e564 don't compile crypto on win 2019-06-06 14:54:33 -04:00
Jake Norbut a38a6057e0 part of SPARK-2485 - mini ob only shows exchange orders + spreader rows 2019-06-06 13:32:48 -05:00
John Colvin dcd8984b33 SPARK-2489 / SPARK-2494 rename 'Column Settings' tab 'Ladder Columns' 2019-06-06 12:35:09 -05:00
Kevin Darby 6647b28a9f keycode generation, encrypt, decrypt works 2019-06-06 13:30:54 -04:00
Jake Norbut fb314f0eb1 SPARK-2491 - mass cancel 2019-06-05 14:01:47 -05:00
Kevin Darby 8a965d41ff keycodes. boost stil can't handle dates 2019-05-30 18:24:24 -04:00
Michael Harding 7d75d87e6b Merge branch 'master' into key_codes 2019-05-28 09:45:17 -04:00
Michael Harding f13a3325bc Add Crypto 2019-05-28 09:44:29 -04:00
Taha Afzal 5c22b43eb5 show qty added 2019-05-24 11:10:14 -04:00
352 changed files with 13246 additions and 3645 deletions

No files matched your search

+1
View File
@@ -32,3 +32,4 @@ trace_data/
/src/lib/orderman/perf_analysis/last_pretty_log.csv
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
.ionide/*
+1 -1
View File
@@ -104,7 +104,7 @@
"request": "launch",
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
"args": [
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+68
View File
@@ -4,6 +4,70 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.6.1
### User Visible Changes
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder, corrected display of volume tool tip
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
- Added an option for the Top Algo to delete itself if within N ticks from top of book
- Address a rare issue that could lead to an error report on UI exit
- Added margin risk limits
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
- Avoided switching the current account and order type on open ladders when limits are changed
### Internal Changes
- Ensure that admin logins are always allowed to login, regardless of keycode status.
## 2.5.0
### User Visible Changes
- Added loss limit risk check
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
- Allowed the mini order book defaults for column widths to be changed from the mini order book
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
## 2.4.0
### User Visible Settings
- In the ladder, changed the last trade column to accumulate successive trades at the same price
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
- Added ladder settings for the mini order book
- Added a safety check to guard against unintentional market crosses
- Improved the Queue Holder mode in the spreader:
- Added a Show Quantity, allowing the spreader to keep queue position of the trailing orders as Fills (of size Show Qty) happen
- Improved spreader performance when in Queue Holder mode
### Internal changes
- Suppressed order callbacks for completed/canceled orders. This should eliminate some bogus error logs by spreader orders.
- Addressed a potential concurrency issues with service initialization.
## 2.3.0
### User Visible Changes
- Introduced multiple settings profiles for the ladder
- This allows multiple standard configuration for the ladder, optionally with security specific variations
- Made Cover order more independent from its hedging chidren:
- Cover continues to operate even if an hedging child is canceled
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
- Changed manual fills to appear in day PnL rather than in the SOD records
- Add buy/sell coloring to fill rows in fills window
- Streamlined the mini order book:
- Display of the mini order book popup is quicker
- Only exchange orders are displayed
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
- A checkbox column has been added to make clearer which rows are selected
### API Changes
- Enhanced the API to allow algos to implement custom cancel handling
- Allowed algos to control their visibility in the ladder through the API
### Internal Changes
- Added system requirements to Spark documentation
- Implemented keycodes
- Temporarily rejected stop and stop limit orders, pending bug fixes
## 2.2.0
### User Visible Changes
- Made our orders easier to spot in MBO display
@@ -18,6 +82,9 @@ insert_anchor_links = "right"
- Fixed a number of problems with filtering on the Fills form
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
- Added security symbol column to fills window
- Re-organized ladder settings dialog to make it clearer and easier to use
## 2.1.4
### User Visible Changes
- Fixed handling of cancel button in security picker
@@ -351,6 +418,7 @@ insert_anchor_links = "right"
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
## 1.3.1 Through 1.3.4
### Speed Enhancements
@@ -0,0 +1,55 @@
+++
title = "System Requirements"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
+++
### Minimum Workstation
* New XPS Tower
* 8th Generation Intel® Core™ i7-8700 6-Core Processor (12M Cache, up to 4.6 GHz)
* 16GB, 2666MHz, DDR4 up to 64GB
* Windows 10 Professional 64-bit
* NVIDIA® GeForce® GTX 1070 with 8GB GDDR5 Graphics Memory
* 1 x 2.5" 256GB SATA Solid State Drive
### Recommended Workstation
* Similar or better than:
* Dell Precision Tower 5820
* Intel Xeon W-2145 3.7GHz, 4.5GHz Turbo, 8C, 11M Cache, HT, (140W) DDR4-2666
* Windows 10 Professional 64-bit
* NVIDIA® Quadro® P1000, 4GB, 4 mDP
* 16GB (4x4GB) 2133MHz DDR4 RDIMM ECC
* 2 x 2.5" 256GB SATA Solid State Drive running RAID1
### BTS Recommended Server Specifications
* Dell R640
* 2x Intel Xeon Gold 6148 2.40GHz 20-Core Processors. For better performance BTS recommends Intel® Xeon® Platinum 8268 2.9G, 24 Core CPU
* 192GB RAM 2666MHz DDR4
* 3x 480GB SSD RAID 5 HBA Minicard
* iDRAC 9 Enterprise
* Embedded Broadcom NIC
* Solarflare SFN8522-R2
* Redundant Power Supply
### BTS Minimum Server Specifications
* Dell R620
* Chassis with up to 8, 2.5” Hard Drives, up to 2 PCIe Slots (With Optional Riser)
* 2 x Intel® E5-2670 (16 total cores/ 32 total threads)
* 64GB total memory
* RAID 1 for H330/H730/H730P
* PERC H730 RAID Controller, 1GB NV Cache
* 3 x 300GB 10K RPM SATA 6Gbps 2.5in Hot-plug Hard Drive (RAID 5)
* iDRAC8 Enterprise, integrated Dell Remote Access Controller, Enterprise (varies by hosting company)
* Solarflare Ultra SFN7122F Dual-Port 10GbE (10 GB connectivity requirements, copper vs optical, varies by hosting company).
* Dual, Hot-plug, Redundant Power Supply (1+1), 750W
* Performance BIOS Setting
* ReadyRails Sliding Rails Without Cable Management Arm (varies by hosting company)
### Edge Tablet Requirements
* Similar or better than:
* Microsoft Surface Pro 4 Or Motion R12
* Intel i7
* 8 GB DDR4 RAM
-5
View File
@@ -1,7 +1,6 @@
#/bin/bash
#python -m mkdocs build -d site
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
./run_zola.sh build
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
@@ -14,7 +13,3 @@ sudo mv ./public/* /var/www/documentation/
EOF
rm -r public
cd ./content/bts_spark_sdk/server/
find . -not -name '_index.md' -delete
cd ../..
+6 -14
View File
@@ -7,12 +7,12 @@ 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 {
readonly string _user_name;
public string user_name => _user_name;
public readonly UserTuple user;
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
@@ -24,14 +24,14 @@ namespace mbts.spark {
IDisposable _dispose;
[ImportingConstructor]
AdminModel(AdminService service, [Import("username")]string user_name) {
_user_name = user_name;
AdminModel(AdminService service, [Import]UserTuple user) {
this.user = user;
_dispose = service.subscribei_admin_data().Select(update => {
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), _user_name));
_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
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
select bts_user;
IEnumerable<BTSAccountImmutable> active_accounts =
@@ -62,14 +62,6 @@ namespace mbts.spark {
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 UserTuple? get_current_user() {
foreach (var kv in _user_map) {
// just for good measure, there should never be > 1 element in _user_map, and it should always match _user_name
if (kv.Value == _user_name)
return new UserTuple(kv.Key, kv.Value);
}
return null;
}
public AccountTuple? get_default_account() {
foreach (var kv in _account_map) {
return new AccountTuple(kv.Key, kv.Value);
+2 -1
View File
@@ -3,6 +3,7 @@ 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;
@@ -22,7 +23,7 @@ namespace bts.ui {
IDisposable _dispose;
[ImportingConstructor]
public AboutDaemon(IBus bus, [Import("username")]string username) {
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
_user = username;
_bus = bus;
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
+28 -2
View File
@@ -10,6 +10,7 @@ using System.Reactive.Linq;
using System.Windows.Threading;
using System.Windows;
using System.Threading.Tasks;
using mbts.ui.shell;
namespace bts.spark
{
@@ -20,7 +21,7 @@ namespace bts.spark
IDisposable _dispose;
[ImportingConstructor]
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
_svc = svc;
_username = username;
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
@@ -82,7 +83,6 @@ namespace bts.spark
}
}
//public static async Task<Result<T>>
public static async Task<(T value, bool success)> safe_execute<T>(this ErrorReporter error_reporter, Func<Task<Result<T>>> execute, Func<T, bool> get_success, Func<T, string> get_error_msg, string desc) {
try {
var result = await execute();
@@ -101,5 +101,31 @@ namespace bts.spark
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
public static async Task<TResult> safe_execute<T, TResult>(
this ErrorReporter error_reporter,
Func<Task<Result<T>>> execute,
Func<T, bool> get_success,
Func<T, string> get_error_msg,
Func<T, bool, TResult> result_selector,
string desc) {
//
try {
var result = await execute();
if (!result.success) {
_report_error(result.error_message);
} else if (!get_success(result.value)) {
_report_error(get_error_msg(result.value));
} else {
return result_selector(result.value, true);
}
} catch (Exception ex) {
_report_error(ex.ToString());
}
return result_selector(default, false);
//
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
}
}
+3 -2
View File
@@ -15,6 +15,7 @@ using System.Collections.Concurrent;
using System.Threading;
using bts.api.user;
using bts.md;
using mbts.ui.shell;
namespace bts {
[Export(typeof(IInitializable))]
@@ -27,8 +28,8 @@ namespace bts {
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
[ImportingConstructor]
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) :
base(get_naggle(), MDSourceOptions.DeferInit) {
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) {
_bus = bus;
_token = token;
_ss = ss;
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows;
using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
[Export(typeof(IShellDaemon))]
@@ -48,14 +49,14 @@ namespace bts.shell {
void _show_order_ticket(SecurityMD smd) =>
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd,
Observable.Return(_model.factory.default_ladder_settings(smd.security)))
Observable.Return(LadderSettings.Default))
});
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
Observable.Return(_model.factory.default_ladder_settings(smd.security).calculator_buttons))
Observable.Return(LadderSettings.Default.calculator_buttons))
});
}
@@ -66,7 +67,7 @@ namespace bts.shell {
_show_order_window(new OrderModificationDialog {
DataContext = new ExistingOrderTicketVM(_model,
Observable.Return((md,ori.order)),
Observable.Return(_model.factory.default_ladder_settings(ori.sec.Value).calculator_buttons)
Observable.Return(LadderSettings.Default.calculator_buttons)
)
});
}
@@ -69,11 +69,11 @@ namespace bts {
cpv.Width = double.NaN;
cpv.Height = double.NaN;
vm.ok_action = () => {
vm.ok_action += () => {
window.Close();
message.complete(select_result(vm)).execute(_bus);
};
vm.cancel_action = () => window.Close();
vm.cancel_action += () => window.Close();
window.move_to_mouse();
window.Show();
}
@@ -3,6 +3,7 @@ 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;
@@ -23,7 +24,7 @@ namespace bts.perf {
Ping _pinger = new Ping();
[ImportingConstructor]
public ShellLatencyDaemon(ShellLatencyService service, [Import("username")] string user) {
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
_service = service;
_version = Configuration.get<bts.config.System>().version;
_username = user;
+2
View File
@@ -24,6 +24,7 @@ namespace mbts.ui {
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
@@ -147,6 +148,7 @@ namespace mbts.ui {
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey, Brushes.White, new Pen(Brushes.Black, 1).frozen());
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
@@ -48,6 +48,10 @@
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
</sys:String>
@@ -5,6 +5,7 @@ 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;
@@ -18,7 +19,7 @@ namespace bts.shell
class MarketShellModule : IShellModule
{
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -27,8 +27,8 @@ namespace mbts.spark {
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
// securities_connectable should NOT be connected already.
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.Create(securities_connectable, SecurityDataHelper.EqualityComparer.MDComparer), null) {
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
_bus = bus;
_orders_model = orders_model;
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
@@ -163,7 +163,6 @@ namespace mbts.spark {
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
securities_connectable.Connect(),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
);
@@ -7,6 +7,7 @@ using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -135,6 +136,7 @@ namespace mbts.spark {
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
// commands
_commands = new List<SparkCommand> {
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
@@ -144,19 +146,18 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.market_view_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -49,13 +49,8 @@ namespace mbts.spark {
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new TimeAndSalesColumn(
col_key
, (r) => _get_aggressor_brush(r)
, filter
, filter_template
)
{ Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
@@ -7,6 +7,7 @@ using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -17,7 +18,6 @@ using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
@@ -70,19 +70,18 @@ namespace mbts.spark {
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.value.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -59,6 +59,7 @@
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
ConditionalPriceTypeTemplate="{StaticResource SimpleValueTextTemplate}"
ShowQuantityTemplate="{StaticResource SimpleValueTextTemplate}"
>
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
<DataTemplate>
@@ -115,6 +116,16 @@
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
<usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
<DataTemplate>
<!-- Note: the motivation for this Template was to enforce a Minimum via the TickUpDownUInt -->
<!-- As of 2019/06/17, we believe such a Minimum/Maximum is not actually getting enforced and -->
<!-- furthermore is not the appropriate way to go about enforcing said value. It is near the end -->
<!-- of the cycle though so that is why we are leaving this Template in here for now until we -->
<!-- can come up with a better solution. -->
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}" />
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
@@ -240,7 +240,7 @@ namespace mbts.ui.spark {
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
var rv = new ContentControl();
rv.SetBinding(ContentControl.ContentProperty, new Binding());
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding() { Converter = cell_template_converter });
return rv;
}
}
@@ -306,17 +306,23 @@ namespace mbts.ui.spark {
public DataTemplate MarketDataTypeTemplate { get; set; }
public DataTemplate MarketDataCalculationTemplate { get; set; }
public DataTemplate ConditionalPriceTypeTemplate { get; set; }
public DataTemplate ShowQuantityTemplate { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
Type t = (Type)value;
if (t == typeof(double)) return DoubleTemplate;
else if (t == typeof(bool)) return BooleanTemplate;
else if (t == typeof(int)) return IntTemplate;
else if (t == typeof(uint)) return UIntTemplate;
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
else return null;
if (value is ICell cell) {
if (cell.row is ShowQuantityQuantityModel) {
return ShowQuantityTemplate;
}
Type t = cell.data_type;
if (t == typeof(double)) return DoubleTemplate;
else if (t == typeof(bool)) return BooleanTemplate;
else if (t == typeof(int)) return IntTemplate;
else if (t == typeof(uint)) return UIntTemplate;
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.synthetic;
using bts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -14,12 +15,14 @@ namespace mbts.spark {
readonly IBus _bus;
readonly SyntheticSecurityService _service;
readonly ErrorReporter _error;
readonly uint _user_id;
[ImportingConstructor]
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error) {
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
_bus = bus;
_service = service;
_error = error;
_user_id = user_id;
}
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
@@ -39,29 +42,40 @@ namespace mbts.spark {
return (result, success);
});
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
await lookup_strategy_async(security.Id);
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
lookup_strategy_async(security.Id);
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
if (result.success) {
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
if (metadata != null) {
return metadata.Strategy;
}
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) =>
_error.safe_execute(
() => _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } }),
mdc => mdc.Data.Count == 1 && mdc.Data[0].SecurityKey == id,
mdc => $"did not find strategy for security id '{id}.'",
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
"lookup strategy");
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
return null;
if (synthetic.Synthetic.UserId != _user_id) {
var msynth = synthetic.CloneToMutable();
msynth.Synthetic.UserId = _user_id;
synthetic = new SecurityImmutable(msynth);
}
return null;
return _error.safe_execute(
() => _service.create_synthetic_async(synthetic),
r => r.Error == CreateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success ? r.Key : (uint?)null,
"creating synthetic");
}
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
var result = await _service.create_synthetic_async(synthetic.Value);
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
}
public async Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) {
var result = await _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() });
return _error.handle_error(result, "Error saving execution strategy: {0}", r => r.Error, e => e == UpdateResult.Types.Error.Ok);
}
public Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) =>
_error.safe_execute(
() => _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() }),
r => r.Error == UpdateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success,
"saving strategy");
public async Task<bool> verify_name_is_unique(string name) {
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
@@ -108,6 +108,9 @@ namespace bts.shell {
NTicks = 2,
PriceType = ConditionalQuoting.Types.PriceType.Leg
};
public static NullableQuantity ShowQuantity => new NullableQuantity {
Quantity = 1
};
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
#endregion
@@ -75,19 +75,18 @@ namespace mbts.spark {
};
return ob_settings;
}), SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.synthetic_management_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.value.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -34,6 +34,8 @@ namespace mbts.spark {
public virtual Type data_type { get; } = typeof(T);
public virtual IRowModel row { get => _model; }
public T value {
get => _model.get_value(_leg_tup);
set {
@@ -64,6 +66,8 @@ namespace mbts.spark {
public Type data_type { get; } = typeof(QuantityMeasure);
public virtual IRowModel row { get => _model; }
public double ratio {
get => _get_qm().Ratio;
set {
@@ -0,0 +1,36 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark {
partial class SyntheticEditorVM {
class ShowQuantityEnabledModel : RowModelBase<bool> {
readonly ShowQuantityQuantityModel _quantity;
public ShowQuantityEnabledModel(ShowQuantityQuantityModel quantity) {
_quantity = quantity;
}
public override string display_name => string.Empty;
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.ShowQuantity != null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.QuoteParams.ShowQuantity == null) {
leg_tup.leg_params.QuoteParams.ShowQuantity = SyntheticDataHelper.Defaults.ShowQuantity;
_quantity.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.QuoteParams.ShowQuantity = null;
}
_quantity.set_enabled(leg_tup, value);
}
}
}
}
@@ -0,0 +1,43 @@
using bts.shell;
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
internal class ShowQuantityQuantityModel : RowModelBase<uint> {
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public override string display_name => "Quantity";
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup).Quantity = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) is NullableQuantity show_quantity ? show_quantity.Quantity : _value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.SecurityKey] = value;
if (_get_value(leg_tup) is NullableQuantity show_quantity) {
show_quantity.Quantity = value;
}
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) != null;
NullableQuantity _get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.ShowQuantity;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ShowQuantity.Quantity);
}
}
}
@@ -32,6 +32,7 @@ namespace mbts.spark {
internal interface ICell {
event Action value_changed;
IRowModel row { get; }
Type data_type { get; }
bool enabled { get; }
}
@@ -40,7 +41,7 @@ namespace mbts.spark {
FontWeight font_weight { get; }
}
abstract class RowModelBase<T> : ViewModelBase, IRowModel {
internal abstract class RowModelBase<T> : ViewModelBase, IRowModel {
readonly Dictionary<uint, ReplaySubject<bool>> _enabled_store = new Dictionary<uint, ReplaySubject<bool>>();
readonly Dictionary<uint, Subject<T>> _override_value_store = new Dictionary<uint, Subject<T>>();
@@ -98,6 +99,10 @@ namespace mbts.spark {
}
private static class Subsections {
public static ParameterSection ShowQuantity =>
new ParameterSection("Show Quantity", true,
"The quantity shown when the order is placed.");
public static ParameterSection Throttle =>
new ParameterSection("Throttle", true,
"Reduces re-quoting updates by defining a region on either side " +
@@ -76,8 +76,8 @@ namespace mbts.spark {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
}
}));
contract_picker_model.cancel_action = _close_new_leg_popup;
contract_picker_model.ok_action = () => {
contract_picker_model.cancel_action += _close_new_leg_popup;
contract_picker_model.ok_action += () => {
if (!leg_tuples.Any(l => l.leg.Security.Id == contract_picker_model.result.security.Id)) {
leg_tuples.Add(new LegSpreaderParamsTuple(
SyntheticDataHelper.Defaults.ContractLeg(contract_picker_model.result.security),
@@ -134,7 +134,7 @@ namespace mbts.spark {
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
uint? id = await model.create_synthetic(_synthetic);
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
var (smd, success) = await SparkUtils.get_security_md_async(bus, id.Value);
@@ -177,6 +177,8 @@ namespace mbts.spark {
} else {
_can_add_leg = backing(nameof(can_add_leg), false);
}
var show_quantity_quantity = new ShowQuantityQuantityModel();
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
@@ -211,6 +213,8 @@ namespace mbts.spark {
//new QuoteHiddenModel(),
new QuoteQueueHolderModel(),
new MarketDataTypeModel(),
new ShowQuantityEnabledModel(show_quantity_quantity),
show_quantity_quantity,
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
quote_throttle_outside_ticks,
@@ -1,14 +1,9 @@
using bts.api;
using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using ProtoBuf;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using System.Windows.Controls;
using mbts;
namespace bts.shell {
public class BrowserWindow : IShellWindow {
@@ -75,12 +70,15 @@ namespace bts.shell {
public string window_type => _window_type;
public IShellModule Owner {get; set; }
public ShellWindowUI GetContent() { return new ShellWindowUI(_browser); }
public void GetState(System.IO.Stream save_here) {
_isw.save(new BrowserWindowState() { uri = uri.ToString() }, save_here);
}
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
public void Dispose() {
if (_browser != null) {
_browser.Dispose();
@@ -102,5 +100,4 @@ namespace bts.shell {
}
#endregion
}
}
}
@@ -86,7 +86,7 @@ namespace mbts.spark {
} else if (t.definition != null) {
return t.definition.subscribe_is_definition_dirty();
} else return Observable.Return(false);
}).RefCount();
});
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
_vm.save_template(t).ignore();
@@ -116,7 +116,6 @@ namespace mbts.spark {
});
_commands.Add(undo_spark_cmd);
_resources.Add(can_undo_source.Subscribe(undo_cmd));
_resources.Add(can_undo_source.Connect());
// redo
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
@@ -133,7 +132,6 @@ namespace mbts.spark {
});
_commands.Add(redo_spark_cmd);
_resources.Add(can_redo_source.Subscribe(redo_cmd));
_resources.Add(can_redo_source.Connect());
}
// remove
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
@@ -1,8 +1,10 @@
using bts.api.orderman;
using bts.persist.algo;
using bts.utils;
using mbts.ui.utility;
using ProtoBuf;
using System;
using System.Linq;
namespace mbts.spark {
[ProtoContract]
@@ -14,6 +16,11 @@ namespace mbts.spark {
[ProtoMember(3)]
readonly uint? _persisted_key;
public static readonly AlgoID[] exchange_algo_keys = utilities.OrderDataHelper.outright_order_types
.Concat(new CustomOrderDefinition.CustomOneofCase[] { CustomOrderDefinition.CustomOneofCase.Spreader })
.Select(cooc => new AlgoID(cooc))
.ToArray();
public AlgoID(CustomOrderDefinition.CustomOneofCase factory_key) {
_factory_key = factory_key;
_dynamic_key = null;
@@ -32,33 +39,41 @@ namespace mbts.spark {
_persisted_key = persisted_key;
}
public static AlgoID from_order_def(OrderDefinitionImmutable order_def) {
switch (order_def.CustomDef.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
default: return new AlgoID(order_def.CustomDef.CustomCase);
public static AlgoID from_custom_def(CustomOrderDefinitionImmutable custom_def) {
switch (custom_def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(custom_def.Composite.AlgoTemplateKey);
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(custom_def.Dynamic.DynamicOrderType);
default: return new AlgoID(custom_def.CustomCase);
}
}
public static AlgoID from_algo_key(AlgoKey key) {
public static AlgoID from_algo_key(AlgoKey key, IMaskIndexInt32<string> dynamic_type_index) {
switch (key.mode) {
case AlgoKey.Mode.Factory:
if (key.factory.structure.structure.Count > 0)
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
break;
return AlgoStructureSerializer.to_algo_id(key.factory.structure, dynamic_type_index);
case AlgoKey.Mode.Persisted:
return new AlgoID(key.persisted.id_uint);
}
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
}
public static AlgoKey to_algo_key(AlgoID id, IMaskIndexInt32<string> dynamic_type_index) {
if (id._factory_key.HasValue)
return new AlgoKey(id._factory_key.Value);
if (id._persisted_key.HasValue)
return new AlgoKey(new AlgoPersistedKey(id._persisted_key.Value));
if (id._dynamic_key != null)
return new AlgoKey(new AlgoNameStructureKey(id._dynamic_key, AlgoStructureSerializer.structure_key(dynamic_type_index.to_int(id._dynamic_key))));
return default;
}
public bool Equals(AlgoID other) =>
_factory_key == other._factory_key &&
_dynamic_key == other._dynamic_key &&
_persisted_key == other._persisted_key;
public override bool Equals(object obj) =>
obj is AlgoID && Equals(obj);
obj is AlgoID other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
@@ -73,6 +73,9 @@ namespace mbts.spark {
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
public static AlgoStructureKey structure_key(int dynamic_type) =>
new AlgoStructureKey(new[] { dynamic_type | _dynamic_mask });
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
@@ -103,6 +106,13 @@ namespace mbts.spark {
return mdef;
}
public static AlgoID to_algo_id(AlgoStructureKey key, IMaskIndexInt32<string> dynamic_type_index) {
if (key.structure.Count == 0)
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
int i = key.structure[0];
return BitHelper.remove_flags(ref i, _dynamic_mask) ? new AlgoID(dynamic_type_index.from_int(i)) : new AlgoID((CustomOrderDefinition.CustomOneofCase)i);
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
if (structure == null || structure.Length == 0)
structure = new int[1];
@@ -1,5 +1,4 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.persist.algo;
@@ -110,38 +109,24 @@ namespace mbts.spark {
public void Dispose() =>
Disposable.dispose(ref _dispose);
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) {
if (_try_get_user(out uint user_id)) {
return await _create_definition(definition, user_id);
} else {
_report_no_user_id(ActionMetadata.CreateDefinition);
}
return null;
}
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
await _create_definition(definition, _admin_model.user.id);
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
new TemplateCreateRequest { UserId = user_id, NewTemplate = template.CloneToMutable() }),
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
if (success)
return _Utils.template_data(value.Record);
} else {
_report_no_user_id(ActionMetadata.CreateTemplate);
}
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
if (success)
return _Utils.template_data(value.Record);
return null;
}
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
if (success)
return _Utils.template_data(template);
} else {
_report_no_user_id(ActionMetadata.UpdateTemplate);
}
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
if (success)
return _Utils.template_data(template);
return null;
}
@@ -175,17 +160,6 @@ namespace mbts.spark {
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
// used for error handling/reporting
interface IActionMetadata {
string desc { get; }
@@ -1,14 +1,11 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman.algo;
using bts.persist.algo;
using bts.spark;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -27,7 +24,12 @@ namespace mbts.spark {
_admin_model = admin_model;
_error_reporter = error_reporter;
this.ui_configuration = ui_configuration;
definitions = service.subscribei_algo_definitions().Filter(r => r.Metadata.IsActive).SelectKeyValue(_definition_data, d => d.key).Table().Cache();
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
@@ -39,15 +41,11 @@ namespace mbts.spark {
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = user_id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
} else {
_report_no_user_id("save template");
}
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
return null;
}
@@ -105,16 +103,5 @@ namespace mbts.spark {
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
}
}
@@ -78,7 +78,7 @@ namespace mbts.spark {
_dispose = new System.Reactive.Disposables.CompositeDisposable(
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).RefCount().Subscribe(_ => _refresh_on_local_change.execute()),
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).Subscribe(_ => _refresh_on_local_change.execute()),
Disposable.wrap_collection(algo_map.Values)) {
() => {
_can_redo.Changed -= on_can_redo_changed;
@@ -54,7 +54,7 @@ namespace mbts.spark {
_model = model;
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).RefCount().OnDispatcher());
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).OnDispatcher());
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
@@ -77,7 +77,7 @@ namespace mbts.spark {
definitions_view.Refresh();
}),
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).RefCount().OnDispatcher()
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).OnDispatcher()
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
@@ -22,7 +22,6 @@ using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
using System.Windows.Media;
using static bts.api.orderman.Fill.Types;
@@ -61,6 +60,14 @@ namespace mbts.spark {
equality_comparer: SecurityDataHelper.EqualityComparer.IDComparer,
comparer: Comparer<SecurityImmutable>.Default
), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
// symbol
add_col(Columns.Symbol, DefaultColumnHeaders.Symbol, r => r.security.Symbol,
DiscreteValuesFilterVM.Create<FillTuple, string>(
co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
ft => ft.security?.Symbol ?? string.Empty,
nameof(OrderBookRowVM.symbol)),
DataGridFilterKnownTemplates.DiscreteValues,
DefaultColumnWidths.Symbol);
// order type
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
add_col_expl(Columns.OrderType,
@@ -121,8 +128,7 @@ namespace mbts.spark {
};
_rows_manager.on_settings(fs, s.sorting_criterion);
}),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -173,7 +179,7 @@ namespace mbts.spark {
} else return model.on_fill_tuples;
}
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
state.SelectManyInSeries(s => _get_observable(bus, model, s));
static IComparer<FillTuple> _get_column_comparer(Columns col) {
@@ -190,6 +196,7 @@ namespace mbts.spark {
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
case Columns.Side: return wrap_default(ft => ft.fill.Side);
case Columns.Size: return wrap(ft => ft.fill.LastShares);
case Columns.Symbol: return wrap(ft => ft.security?.Symbol ?? string.Empty);
case Columns.TimeInForce: return wrap_default(ft => ft.order?.time_in_force() ?? TimeInForce.Undefined);
case Columns.User: return wrap(ft => ft.order?.user() ?? string.Empty);
default: goto case Columns.FillTime;
@@ -6,6 +6,10 @@
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:u="clr-namespace:mbts.ui"
xmlns:api="clr-namespace:bts.api;assembly=spark_api"
xmlns:bts="clr-namespace:mbts.ui.controls"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
@@ -15,6 +19,10 @@
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
<cv:ValueMap x:Key="SideToBrushConverter" Default="{u:Brush Transparent}">
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{x:Static semi_transparent_bid_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{x:Static semi_transparent_ask_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
</cv:ValueMap>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
@@ -41,10 +49,10 @@
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
/>
</c:DIYGrid.CommandBindings>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView/>
</DataTemplate>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView Background="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
@@ -7,7 +7,23 @@ namespace mbts.ui.spark {
/// Interaction logic for FillsView.xaml
/// </summary>
partial class FillsView : UserControl {
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKind, FillKey, OrderKey, SecurityKey }
public enum Columns {
FillTime,
User,
Account,
Exchange,
Security,
Symbol,
TimeInForce,
OrderType,
Side,
Price,
Size,
FillKind,
FillKey,
OrderKey,
SecurityKey
}
public FillsView() =>
InitializeComponent();
@@ -6,6 +6,7 @@ using bts.spark;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -60,19 +61,18 @@ namespace mbts.spark {
_commands = new List<SparkCommand> {
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select( c => c.create())),
this.observe_title()
s.with(_vm.columns.Select(c => c.create()))
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -66,6 +66,10 @@ namespace bts.shell.fills {
[ProtoMember(14)]
[DIYGridColumnID(nameof(Columns.FillKind))]
public Fill.Types.FillKind[] fill_kind { get; set; }
[ProtoMember(15)]
[DIYGridColumnID(nameof(Columns.Symbol))]
public string[] symbol { get; set; }
}
static class FillsFilterStatesExtensions {
@@ -86,6 +90,7 @@ namespace bts.shell.fills {
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
// numeric
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
@@ -40,6 +40,7 @@ namespace bts.shell.fills {
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
@@ -62,6 +63,7 @@ namespace bts.shell.fills {
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return symbol;
yield return fill_time;
yield return user;
yield return account;
@@ -9,6 +9,7 @@ using bts.proto;
using mbts.ui.spark;
using System.Collections.Generic;
using System.ComponentModel;
using mbts.ui.settings;
namespace mbts.spark {
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
@@ -220,6 +221,23 @@ namespace mbts.spark {
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
}
[ProtoContract]
readonly struct LadderMiniOrderBookSettings : IEquatable<LadderMiniOrderBookSettings> {
// this is the only member now, but more may be added later (that is why a GridSettings member wasn't added directly to LadderSettings).
[ProtoMember(1)]
public readonly GridSettings value;
public LadderMiniOrderBookSettings(GridSettings value) =>
this.value = value;
public bool Equals(LadderMiniOrderBookSettings other) => value == null ? other.value == null : value.Equals(other.value);
public override bool Equals(object obj) => obj is LadderMiniOrderBookSettings other && Equals(other);
public override int GetHashCode() => value.GetHashCode();
public static bool operator ==(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => a.Equals(b);
public static bool operator !=(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => !a.Equals(b);
}
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
// 1 important reason at this time:
// From the settings dialog, default settings will only be persisted when they are NOT EQUAL to the current default settings.
@@ -228,6 +246,7 @@ namespace mbts.spark {
class LadderSettings : IEquatable<LadderSettings> {
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
@@ -255,6 +274,7 @@ namespace mbts.spark {
zoom_factor = _default_zoom_factor;
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
show_warn_on_cross = _default_show_warn_on_cross;
}
public static LadderSettings Default {
@@ -286,8 +306,8 @@ namespace mbts.spark {
volume_column = VolumeColumnSettings.Default,
calculator_buttons = new[] { 1, 5, 10, 25, 50, 100 },
scroll_bar_visible = false,
default_order_type = CustomOrderDefinition.CustomOneofCase.Limit,
default_time_in_force = TimeInForce.Day,
default_algo = new AlgoID(CustomOrderDefinition.CustomOneofCase.Limit),
animate_recenter = true,
badge_style = BadgeStyle.Normal,
bid_break_line = new LadderMarketBreakLine { color = 0, show = true },
@@ -319,6 +339,7 @@ namespace mbts.spark {
zoom_slider_visible = false,
zoom_factor = _default_zoom_factor,
net_change = NetChangeTerms.None,
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
};
rv.row_height = LadderRender.measure_row_height(rv);
return rv;
@@ -327,7 +348,15 @@ namespace mbts.spark {
public enum BadgeStyle { Normal, Alternate }
public enum NetChangeTerms { None, Dollars, Ticks }
// TODO: rename to PriceDisplayFormat (today is merge monday, since this isn't a functional change, it is not worth the potential merge headaches right now)
public enum NetChangeTerms {
[Description("Standard Ticks")]
None,
[Description("Net Change Dollars")]
Dollars,
[Description("Net Change Ticks")]
Ticks
}
[ProtoMember(1)]
public double row_height { get; set; }
@@ -354,6 +383,10 @@ namespace mbts.spark {
public bool scroll_bar_visible { get; set; }
// USED [ProtoMember(11)]
// USED [ProtoMember(12)]
[ProtoMember(51)]
public uint default_account { get; set; }
[ProtoMember(52)]
public AlgoID default_algo { get; set; }
[ProtoMember(13)]
public CustomOrderDefinition.CustomOneofCase default_order_type { get; set; }
[ProtoMember(14)]
@@ -364,6 +397,8 @@ namespace mbts.spark {
public bool animate_recenter { get; set; } = true;
[ProtoMember(17)]
public BadgeStyle badge_style { get; set; }
[ProtoMember(53)]
public bool auto_clear { get; set; }
[ProtoMember(18)]
public LadderMarketBreakLine bid_break_line { get; set; }
@@ -446,6 +481,47 @@ namespace mbts.spark {
get => _piq_column ?? (_piq_column = LadderSettings.Default.piq_column);
set => _piq_column = value;
}
[ProtoMember(54), DefaultValue(_default_show_warn_on_cross)]
public bool show_warn_on_cross { get; set; }
[ProtoMember(55)]
public LadderMiniOrderBookSettings mini_ob { get; set; }
public void sanitize() =>
sanitize(this);
// if there are new props, make sure they are not null, invalid, etc.
public static LadderSettings sanitize(LadderSettings s) {
if (s == null)
return null;
// these MarketBreakLines are new settings props, ensure they are not null
if (s.bid_break_line == null)
s.bid_break_line = Default.bid_break_line;
if (s.ask_break_line == null)
s.ask_break_line = Default.ask_break_line;
if (s.mid_break_line == null)
s.mid_break_line = Default.mid_break_line;
if (s.session_low_line == null)
s.session_low_line = Default.session_low_line;
if (s.session_high_line == null)
s.session_high_line = Default.session_high_line;
if (s.session_range_fill == null)
s.session_range_fill = Default.session_range_fill;
if (s.volume_column == null)
s.volume_column = Default.volume_column;
if (s.size_display == null)
s.size_display = MarketSizeDisplay.get_default();
if (s.size_display.normal == null)
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
if (s.mini_ob.value == null)
s.mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general);
#pragma warning disable CS0612 // Type or member is obsolete
if (s.mini_ob.value.sorting_criterion.Count == 0)
s.mini_ob = new LadderMiniOrderBookSettings(s.mini_ob.value.with(sorting_criterion: bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general.sorting_criterion));
#pragma warning restore CS0612 // Type or member is obsolete
s.row_height = LadderRender.measure_row_height(s);
return s;
}
public bool Equals(LadderSettings other) =>
other != null &&
@@ -459,11 +535,13 @@ namespace mbts.spark {
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_order_type == other.default_order_type &&
default_account == other.default_account &&
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) &&
@@ -495,7 +573,9 @@ namespace mbts.spark {
zoom_factor == other.zoom_factor &&
use_defaults == other.use_defaults &&
favorite_algos == other.favorite_algos &&
net_change == other.net_change;
net_change == other.net_change &&
show_warn_on_cross == other.show_warn_on_cross &&
mini_ob == other.mini_ob;
public override bool Equals(object obj) => Equals(obj as LadderSettings);
@@ -512,7 +592,6 @@ namespace mbts.spark {
EqualityComparer.safe_get_hash_code(piq_column),
EqualityComparer.safe_get_hash_code(calculator_buttons),
scroll_bar_visible.GetHashCode(),
default_order_type.GetHashCode(),
default_time_in_force.GetHashCode(),
size.GetHashCode(),
animate_recenter.GetHashCode(),
@@ -547,7 +626,8 @@ namespace mbts.spark {
zoom_factor.GetHashCode(),
use_defaults.GetHashCode(),
favorite_algos.GetHashCode(),
net_change.GetHashCode());
net_change.GetHashCode(),
show_warn_on_cross.GetHashCode());
public struct WindowSpecifics {
public readonly double order_entry_panel_width, zoom_factor;
@@ -4,13 +4,17 @@ using mbts;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -52,63 +56,217 @@ namespace bts.shell {
public async override Task<IShellWindow> load(LadderSaveState state) {
await Wait.until_true(() => _securities.ContainsKey(state.security_key), _ladder_conf.security_timeout);
if (_securities.TryGetValue(state.security_key, out SecurityMD security)) {
return create_ladder_window(security, state.settings);
return create_ladder_window(security, state.settings, state.settings_id);
}
return null;
}
static LadderSaveState _get_state(LadderVM vm) =>
new LadderSaveState { security_key = vm.security.Id, settings = vm.get_save_state() };
LadderSaveState _get_state(LadderVM vm, uint id) {
LadderSettings s = vm.get_save_state();
if (id != 0) {
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
// profile was deleted
id = 0;
} else {
var curr_profile = _fit_to_sec(result.value, vm.security.ContractCase);
if (!s.Equals(curr_profile)) {
// 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 };
}
int _ladder_count = 0;
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
IDisposable active_settings_dialog = null;
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
// on_shell_window_vm should behave like an event (do not set to null)
Action<ShellWindowVM> on_shell_window_vm = _ => { };
ShellWindowVM shell_window_vm = null;
IShellWindow shell_window = null;
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
var view = new Lazy<UIElement>(() => {
// resources
var model = new LadderModel(md, _orders_model, _mbo);
var settings_manager = new WindowSettingsManager<LadderSettings>(
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
us => sanitize_ladder_settings(us.ladder),
(us, v) => us.with(sanitize_ladder_settings(v)));
var init = state_settings ?? settings_manager.default_settings;
init.use_defaults |= state_settings == null;
var vm = LadderVM.create(_bus, model, settings_manager, init);
var profile_service = new SettingsProfileService<LadderSettings>(_settings_hub,
us => ImmutableDictionary<uint, SettingsProfile<LadderSettings>>.Empty.AddRange(us.ladder_profiles.ToDictionary(
lp => lp.id,
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, LadderSettings.sanitize(lp.value), lp.security_criteria))),
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
state_settings?.sanitize();
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
bool initialized = false, needs_resync = false;
LadderSettings last_value = fit_to_sec(state_settings);
LadderVM vm = null;
var settings_subject = new Subject<LadderSettings>();
var settings_source = Observable.Create<LadderSettings>(o => {
ensure_synced();
if (last_value != null)
o.OnNext(last_value);
return settings_subject.Subscribe(s => {
if (s != null)
o.OnNext(s);
});
});
// vm
vm = LadderVM.create(_bus, model, LadderSettings.sanitize(last_value ?? _settings_hub.current.ladder), settings_source);
vm.settings_invalidated += on_settings_invalidated;
vm.order_properties_vm.open_algo_popup_on_select = _ladder_conf.open_algo_popup_on_select;
vm.order_properties_vm.use_algo_param_grid = _ladder_conf.use_algo_param_grid;
vm.request_settings_dialog += open_settings_dialog;
var settings_subscription = _settings_hub.subscribe_default_settings().Subscribe(on_user_settings);
vm.OnDisposed += disposing => {
Disposable.dispose(ref settings_subscription);
Disposable.dispose(ref active_settings_dialog);
Disposable.dispose(ref settings_subject);
};
// view
var lv = new LadderView(vm);
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(vm.model.order_gateway.algo_model.ui_configuration))
lv.Resources[kv.Key] = kv.Value;
return lv;
// local methods
LadderSettings fit_to_sec(LadderSettings s) =>
_fit_to_sec(s, md.security.ContractCase);
void on_settings_invalidated() =>
needs_resync = true;
void ensure_synced() {
if (!needs_resync)
return;
var curr = vm.get_save_state();
if (settings_id == 0) {
last_value = curr;
} else if (!curr.Equals(last_value)) {
settings_id = 0;
last_value = curr;
}
needs_resync = false;
}
void on_user_settings(UserSettings us) {
if (initialized) {
ensure_synced();
if (settings_id == 0) {
// 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())) {
settings_id = 0;
} else {
settings_subject.OnNext(last_value = fit_to_sec(profile.value));
}
} else {
// profile was deleted; keep the current settings and switch to the 'Local' profile.
settings_id = 0;
}
} else if (_settings_hub.initialized) {
initialized = true;
if (settings_id != 0) {
if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
last_value = fit_to_sec(profile.value);
} else {
settings_id = 0;
}
}
if (settings_id == 0 && last_value == null) {
foreach (var lp in _settings_hub.current.ladder_profiles) {
// TODO: Use a kind of 'match priority' to resolve conflicts when the security is included in more than 1 profile's security criteria.
// In the meantime, simply use the first match.
if (lp.security_criteria.includes(md.security)) {
last_value = fit_to_sec(lp.value);
settings_id = lp.id;
break;
}
}
if (last_value == null)
last_value = fit_to_sec(_settings_hub.current.ladder);
}
settings_subject.OnNext(last_value);
}
}
void open_settings_dialog() {
// dialog vm
var dialog_vm = new ProfiledShellSettingsDialogVM<LadderSettings, LadderSettingsVM, LadderSettingsProfile>(
profile_service,
us => us.ladder ?? LadderSettings.Default,
s => {
var svm = new LadderSettingsVM(model.order_gateway, vm.security);
svm.load(s);
return svm;
},
fit_to_sec,
vm.get_save_state(),
UserSettings.FileExtensions.LADDER,
settings_id);
dialog_vm.applied += apply;
// dialog
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
dialog_vm.SubscribeDisposed(title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
dialog_vm.RequestClose += on_request_close;
dialog.Closed += on_closed;
Disposable.replace(ref active_settings_dialog, Disposable.Create(destroy_all));
dialog.move_to_mouse(-500, -500);
dialog.Show();
// local methods
void apply(SettingsProfile<LadderSettings> profile) {
settings_id = profile.id;
last_value = profile.value;
settings_subject.OnNext(profile.value);
}
// idempotent
void destroy_all() {
destroy_window();
destroy_vm();
}
// idempotent
void destroy_window() {
if (dialog == null) return;
try {
var temp = dialog;
dialog = null;
temp.Closed -= on_closed;
temp.Close();
} catch (Exception ex) {
on_error("closing window", ex);
}
}
// idempotent
void destroy_vm() {
if (dialog_vm == null) return;
try {
var temp = dialog_vm;
dialog_vm = null;
temp.applied -= apply;
temp.RequestClose -= on_request_close;
temp.Dispose();
} catch (Exception ex) {
on_error("disposing vm", ex);
}
}
void on_closed(object sender, EventArgs e) => destroy_all();
void on_request_close(object sender, EventArgs e) => destroy_all();
void on_error(string action_desc, Exception error) =>
Log.Ui.warn($"LadderSettingsDialog error {action_desc}: {error}.");
}
});
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{id:00}" : "";
return ShellWindow.create(
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
return shell_window = ShellWindow.create(
module, md.security.ToString(), _window_type, view,
(v,s) => save(_get_state(((LadderView)v).vm), s),
icon: Icons.Ladder,
thread_id: thread);
// if there are new props, make sure they are not null, invalid, etc.
LadderSettings sanitize_ladder_settings(LadderSettings s) {
// these MarketBreakLines are new settings props, ensure they are not null
if (s.bid_break_line == null)
s.bid_break_line = LadderSettings.Default.bid_break_line;
if (s.ask_break_line == null)
s.ask_break_line = LadderSettings.Default.ask_break_line;
if (s.mid_break_line == null)
s.mid_break_line = LadderSettings.Default.mid_break_line;
if (s.session_low_line == null)
s.session_low_line = LadderSettings.Default.session_low_line;
if (s.session_high_line == null)
s.session_high_line = LadderSettings.Default.session_high_line;
if (s.session_range_fill == null)
s.session_range_fill = LadderSettings.Default.session_range_fill;
if (s.volume_column == null)
s.volume_column = LadderSettings.Default.volume_column;
if (s.size_display == null)
s.size_display = MarketSizeDisplay.get_default();
if (s.size_display.normal == null)
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
s.row_height = LadderRender.measure_row_height(s);
return s;
}
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
icon: Icons.Ladder,
thread_id: thread,
vm => {
shell_window_vm = vm;
title_source.connect();
});
}
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
@@ -125,26 +283,19 @@ namespace bts.shell {
}
}
// TODO: whenever we re-implement the linked ladder, need to add some field to the LadderSaveState to indicate the owning window
//void show_linked(IObservable<SecurityMD> keystream) {
// var view = new LadderView();
// IDisposable sub = null;
// var window =
// ShellWindow.create(module, "loading", view,
// Disposable.Create(() => {
// (view.DataContext as LadderVM)?.Dispose();
// sub?.Dispose();
// }), s => {
// var vm = view.DataContext as LadderVM;
// if (vm != null) save(_get_state(vm), s);
// }, Icons.Ladder);
// sub =
// keystream
// .Select(smd => new LadderVM(_bus, new LadderModel(smd, _orders_model),
// Observable.Return(_orders_model.factory.default_ladder_settings(smd.security)))
// ).Aggregate((old, vm) => { old.Dispose(); return vm; })
// .Subscribe(vm => { view.DataContext = vm; window.Title = vm.security.ToString(); });
//}
LadderSettings _fit_to_sec(LadderSettings s, Security.ContractOneofCase sec_type) {
if (s == null) return null;
var algo_key = AlgoID.to_algo_key(s.default_algo, _orders_model.dynamic_helper.dynamic_type_index);
if (algo_key.mode == AlgoKey.Mode.Factory) {
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type, true);
if (fitted_algo.HasValue) {
// be sure to clone ('s' may be shared)
s = SparkUtils.clone_proto(s);
s.default_algo = AlgoID.from_custom_def(fitted_algo.Value);
}
}
return s;
}
readonly struct LadderConfig {
public readonly uint max_open_ladders;
@@ -167,5 +318,7 @@ namespace bts.shell {
public UInt32 security_key;
[ProtoMember(2)]
public mbts.spark.LadderSettings settings;
[ProtoMember(3)]
public uint settings_id;
}
}
}
@@ -1,5 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.shell.orderbook;
using mbts.persist;
using mbts.ui;
using mbts.ui.shell;
using System;
@@ -292,50 +294,29 @@ namespace mbts.spark {
price_filter.minimum.value = real_price;
price_filter.maximum.value = real_price;
var obvm = new OrderBookVM(null, _owner.bus, null, _owner.model.order_gateway, null, true);
var state = new bts.shell.orderbook.OrderBookStartupData(
new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_owner.security),
price = price_filter,
statuses = new[] { OrderStatus.Working }
statuses = new[] { OrderStatus.Working },
order_types = AlgoID.exchange_algo_keys
},
bts.shell.orderbook.OrderBookStartupData.default_mini_grid,
bts.shell.orderbook.OrderBookStartupData.default_mini_grid.with(_owner.current_settings.mini_ob.value),
use_defaults: false
);
// NOTE:
// we might need the ObservableAppend to InsertAt
// if these EditXXX ever support settings.
// The end result after this group of lines will be
// ----------------------------------------------
// | Cancel | Update | Qty | Price | Commit | Attach | Detach | ...
// ----------------------------------------------
//
var settings_man = new WindowSettingsManager<OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true);
_owner.settings.Subscribe(s => settings_man.instance_settings = settings_man.instance_settings.with(s.mini_ob.value));
obvm.columns.Insert(0, new OrderBookVM.AttachDetachColumn(typeof(MiniOrderBookView)));
obvm.columns.Insert(0, new OrderBookVM.CommitColumn());
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Price.ToString());
var price_index = obvm.columns.indexOf(price_col);
if (price_index > -1) {
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditablePrice.ToString());
var existing_editprice_index = obvm.columns.indexOf(existing_editprice_col);
if (existing_editprice_index == -1) {
// insert the edit price column at the beginning
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
}
}
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Size.ToString());
var qty_index = obvm.columns.indexOf(qty_col);
if (qty_index > -1) {
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditableQty.ToString());
var existing_editqty_index = obvm.columns.indexOf(existing_editqty_col);
if (existing_editqty_index == -1) {
// insert the edit quantity column at the beginning
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
}
}
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
obvm.columns.Insert(0, new OrderBookVM.UpdateColumn(typeof(MiniOrderBookView)));
obvm.columns.Insert(0, new OrderBookVM.CancelColumn(nameof(OrderBookRowVM.cancel_command)));
// the CheckColumn must be inserted at the front of the ObservableAppendCollection,
// and not mixed with the other columns in said ObservableAppendCollection's underlying ObservableCollection (the settings dialog uses the underlying collection).
// The user cannot adjust this column in the settings dialog, and it should always be the left-most column.
obvm.columns.insert_front(new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
obvm.load_from(state);
return obvm;
}
@@ -1,5 +1,8 @@
using bts.api;
using bts;
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
using mbts.ui;
@@ -8,10 +11,14 @@ using mbts.ui.controls.color_picker;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
@@ -19,19 +26,30 @@ using System.Windows.Media;
using TextUtils = mbts.ui.utility.text.TextUtils;
namespace mbts.spark {
class LadderSettingsVM : ViewModelBase, IWindowSettingsVM<LadderSettings>, ITemplateSelectorKey, IDisposable {
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
readonly Property<LadderSettings.BadgeStyle> _badge_style;
readonly Property<uint> _alternate_order_size;
readonly DelegateProperty<double> _row_height;
Func<double> _get_order_entry_panel_width, _get_zoom_factor;
readonly UserProperty<AccountTuple?> _default_account;
readonly UserProperty<AlgoDefinitionVM> _default_algo;
readonly DeferrableActionWrapper _update;
readonly ObservableKeyedCollection<AccountTuple?, uint> _accounts = new ObservableKeyedCollection<AccountTuple?, uint>(AccountTuple.nullable_key_fn);
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
readonly OrdersModel _model;
uint? _desired_account;
AlgoID? _desired_algo;
TimeInForce _default_time_in_force;
LadderSettings.NetChangeTerms _price_format;
double _order_entry_panel_width, _zoom_factor;
bool _auto_clear, _show_warn_on_cross;
LadderSettings _current;
LadderColumnSettingsVM
_orders = new OrdersColumnSettingsVM(),
_bid = new LadderColumnSettingsVM("Bid"),
_price = new LadderColumnSettingsVM("Price"),
_ask = new LadderColumnSettingsVM("Ask"),
_last = new LadderColumnSettingsVM("Last", true),
_piq = new LadderColumnSettingsVM("PIQ", true);
_last = new LadderColumnSettingsVM("Last", LadderSettingsView.LastColumnTemplateKey, true),
_piq = new LadderColumnSettingsVM("PIQ", can_toggle_show: true);
VolumeColumnVM _volume = new VolumeColumnVM();
LadderMarketBreakLineVM
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
@@ -59,25 +77,18 @@ namespace mbts.spark {
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
readonly Property<bool>
_show_in_net_change,
_show_in_net_change_dollars,
_show_in_net_change_ticks;
public LadderSettingsVM(OrdersModel model, bool is_synthetic, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
this.is_synthetic = is_synthetic;
favorite_algos_vm = new FavoriteAlgosVM(model);
public LadderSettingsVM(OrdersModel model, SecurityImmutable security) {
_model = model;
is_synthetic = security.ContractCase == Security.ContractOneofCase.Synthetic;
favorite_algos_vm = new FavoriteAlgosVM(_algo_definitions);
size_presets = new ObservableEditableCollection<IMutableValueWrapperVM<uint>>(_create_size_preset_vm);
size_presets.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
_badge_style = backing<LadderSettings.BadgeStyle>(nameof(badge_style));
_badge_style.Changed += _badge_style_Changed;
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
_get_order_entry_panel_width = get_left_panel_width;
_get_zoom_factor = get_zoom_factor;
_show_in_net_change = backing<bool>(nameof(show_in_net_change));
_show_in_net_change_dollars = backing<bool>(nameof(show_in_net_change_dollars));
_show_in_net_change_ticks = backing<bool>(nameof(show_in_net_change_ticks));
_default_account = user_backing<AccountTuple?>(nameof(default_account));
_default_algo = user_backing<AlgoDefinitionVM>(nameof(default_algo));
// be sure to initialize backings before this measure_row_height call
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
_orders.font_size_changed += on_font_size_changed;
@@ -86,10 +97,50 @@ namespace mbts.spark {
_ask.font_size_changed += on_font_size_changed;
_last.font_size_changed += on_font_size_changed;
_piq.font_size_changed += on_font_size_changed;
// accounts
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
CustomSort = Comparer<AccountTuple>.Default.to_nullable().to_non_generic()
};
// update
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
mini_ob.result_changed += _update.execute;
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
_dispose = new CompositeDisposable(
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
_try_select_desired_account();
}),
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, connect_favorite_algo),
_model.algo_model.definitions.OnDispatcher().Subscribe(update => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, algo_factory);
_try_select_desired_algo();
})
) { dispose_resources };
// local methods
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
void on_prop_changed(object sender, PropertyChangedEventArgs e) => _update.execute();
void on_favorite_algo_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => _update.execute();
IDisposable connect_favorite_algo(FavoriteAlgoItem item) {
item.PropertyChanged += on_prop_changed;
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
}
void dispose_resources() {
mini_ob.result_changed -= _update.execute;
mini_ob.Dispose();
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
favorite_algos_vm.Dispose();
foreach (var c in _observable_components)
c.PropertyChanged -= on_prop_changed;
}
AccountTuple? account_factory(KeyValuePair<uint, string> kv) => new AccountTuple(kv.Key, kv.Value);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, false);
}
public event Action result_changed;
private void _badge_style_Changed(LadderSettings.BadgeStyle oldval, LadderSettings.BadgeStyle newval) {
if (newval == LadderSettings.BadgeStyle.Alternate && _orders.font_size == 10) {
_orders.font_size = 7;
@@ -110,6 +161,27 @@ namespace mbts.spark {
}
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
// price format
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
// time in force
public TimeInForce default_time_in_force { get => _default_time_in_force; set => Set(ref _default_time_in_force, value); }
public IEnumerable<TimeInForce> time_in_force_options => EnumHelper.get_values_except(TimeInForce.Undefined);
// account
public AccountTuple? default_account { get => _default_account.Value; set => _default_account.Value = value; }
public ICollectionView<AccountTuple?> accounts_view { get; }
// algo
public AlgoDefinitionVM default_algo { get => _default_algo.Value; set => _default_algo.Value = value; }
public IReadOnlyObservableCollection<AlgoDefinitionVM> algo_definitions => _algo_definitions;
// left-panel (components)
public bool auto_clear { get => _auto_clear; set => Set(ref _auto_clear, value); }
public double order_entry_panel_width { get => _order_entry_panel_width; set => Set(ref _order_entry_panel_width, value); }
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
// safety checks
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
// favorites
public FavoriteAlgosVM favorite_algos_vm { get; }
public double row_height => _row_height.Value;
@@ -175,88 +247,133 @@ namespace mbts.spark {
}
}
public bool show_in_net_change {
get => _show_in_net_change.Value;
set {
// when unchecking, clear out dollars and ticks
if (!value) {
show_in_net_change_dollars = false;
show_in_net_change_ticks = false;
}
// when checking, by defalt check dollars
if (value) {
show_in_net_change_dollars = true;
}
_show_in_net_change.Value = value;
}
}
public bool show_in_net_change_dollars {
get => _show_in_net_change_dollars.Value;
set => _show_in_net_change_dollars.Value = value;
}
public bool show_in_net_change_ticks {
get => _show_in_net_change_ticks.Value;
set => _show_in_net_change_ticks.Value = value;
}
public string key => GridSettingsTemplateKeys.Ladder;
IEnumerable<INotifyPropertyChanged> _observable_components {
get {
// columns
yield return _orders;
yield return _bid;
yield return _price;
yield return _ask;
yield return _last;
yield return _piq;
yield return _volume;
yield return _volume.general;
// market break lines
yield return _bid_mkt_line;
yield return _ask_mkt_line;
yield return _mid_mkt_line;
yield return _session_low_mkt_line;
yield return _session_high_mkt_line;
yield return _session_range_fill;
// left panel
yield return _zoom_slider_visbility;
yield return _day_visibility;
yield return _high_visibility;
yield return _low_visibility;
yield return _open_visibility;
yield return _settle_visibility;
yield return _volume_visibility;
yield return _account_visibility;
yield return _tif_visibility;
yield return _order_type_visibility;
yield return _account_position_visibility;
yield return _day_position_visibility;
yield return _size_preset_visibility;
yield return _auto_clear_visibility;
yield return _clear_visibility;
yield return _cancel_buys_visibility;
yield return _cancel_sells_visibility;
yield return _cancel_all_visibility;
// other
yield return size_display;
yield return favorite_algos_vm;
yield return this;
}
}
public void load(LadderSettings settings) {
_current = settings;
badge_style = settings.badge_style;
alternate_order_size = settings.alternate_order_size;
favorite_algos_vm.load_from(settings.favorite_algos);
size_display.load_from(settings.size_display);
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
_orders.assign_from(_current.orders_column);
_bid.assign_from(_current.bid_column);
_ask.assign_from(_current.ask_column);
_price.assign_from(_current.price_column);
_volume.assign_from(_current.volume_column);
_last.assign_from(_current.last_column);
_piq.assign_from(_current.piq_column);
_bid_mkt_line.assign_from(_current.bid_break_line);
_ask_mkt_line.assign_from(_current.ask_break_line);
_mid_mkt_line.assign_from(_current.mid_break_line);
_session_low_mkt_line.assign_from(_current.session_low_line);
_session_high_mkt_line.assign_from(_current.session_high_line);
_session_range_fill.assign_from(_current.session_range_fill);
_zoom_slider_visbility.show = settings.zoom_slider_visible;
_day_visibility.show = settings.day_visible;
_high_visibility.show = settings.high_visible;
_low_visibility.show = settings.low_visible;
_open_visibility.show = settings.open_visible;
_settle_visibility.show = settings.settle_visible;
_volume_visibility.show = settings.volume_visible;
_account_visibility.show = settings.account_visible;
_tif_visibility.show = settings.tif_visible;
_order_type_visibility.show = settings.order_type_visible;
_account_position_visibility.show = settings.account_position_visible;
_day_position_visibility.show = settings.day_position_visible;
_size_preset_visibility.show = settings.size_presets_visible;
_clear_visibility.show = settings.clear_visible;
_auto_clear_visibility.show = settings.auto_clear_visible;
_cancel_buys_visibility.show = settings.cancel_buys_visible;
_cancel_sells_visibility.show = settings.cancel_sells_visible;
_cancel_all_visibility.show = settings.cancel_all_visible;
// do not read the order entry panel width here from settings
// because it could have changed from the UI
// there might be a better way of doing this, like how
// does the widths of the ladder grid column pull this off?
show_in_net_change = settings.net_change != LadderSettings.NetChangeTerms.None;
show_in_net_change_dollars = settings.net_change == LadderSettings.NetChangeTerms.Dollars;
show_in_net_change_ticks = settings.net_change == LadderSettings.NetChangeTerms.Ticks;
using (_update.defer_execution()) {
_current = settings;
badge_style = settings.badge_style;
alternate_order_size = settings.alternate_order_size;
favorite_algos_vm.load_from(settings.favorite_algos);
size_display.load_from(settings.size_display);
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
mini_ob.load_from(settings.mini_ob.value);
// columns
_orders.assign_from(_current.orders_column);
_bid.assign_from(_current.bid_column);
_ask.assign_from(_current.ask_column);
_price.assign_from(_current.price_column);
_volume.assign_from(_current.volume_column);
_last.assign_from(_current.last_column);
_piq.assign_from(_current.piq_column);
// market break lines
_bid_mkt_line.assign_from(_current.bid_break_line);
_ask_mkt_line.assign_from(_current.ask_break_line);
_mid_mkt_line.assign_from(_current.mid_break_line);
_session_low_mkt_line.assign_from(_current.session_low_line);
_session_high_mkt_line.assign_from(_current.session_high_line);
_session_range_fill.assign_from(_current.session_range_fill);
// left-panel (defaults)
default_time_in_force = settings.default_time_in_force;
// - account
_desired_account = settings.default_account;
_try_select_desired_account(true);
// - algo
_desired_algo = settings.default_algo;
_try_select_desired_algo(true);
// - components
auto_clear = settings.auto_clear;
order_entry_panel_width = settings.order_entry_panel_initial_width;
zoom_factor = settings.zoom_factor;
// left-panel (show/hide)
_zoom_slider_visbility.show = settings.zoom_slider_visible;
_day_visibility.show = settings.day_visible;
_high_visibility.show = settings.high_visible;
_low_visibility.show = settings.low_visible;
_open_visibility.show = settings.open_visible;
_settle_visibility.show = settings.settle_visible;
_volume_visibility.show = settings.volume_visible;
_account_visibility.show = settings.account_visible;
_tif_visibility.show = settings.tif_visible;
_order_type_visibility.show = settings.order_type_visible;
_account_position_visibility.show = settings.account_position_visible;
_day_position_visibility.show = settings.day_position_visible;
_size_preset_visibility.show = settings.size_presets_visible;
_clear_visibility.show = settings.clear_visible;
_auto_clear_visibility.show = settings.auto_clear_visible;
_cancel_buys_visibility.show = settings.cancel_buys_visible;
_cancel_sells_visibility.show = settings.cancel_sells_visible;
_cancel_all_visibility.show = settings.cancel_all_visible;
price_format = settings.net_change;
// safety checks
show_warn_on_cross = settings.show_warn_on_cross;
}
}
public LadderSettings get_result() {
var clone = SparkUtils.clone_proto(_current);
LadderSettings clone;
if (_current == null) {
clone = new LadderSettings {
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
};
} else {
clone = SparkUtils.clone_proto(_current);
#pragma warning disable CS0612 // Type or member is obsolete
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
mini_ob.create());
#pragma warning restore CS0612 // Type or member is obsolete
}
clone.badge_style = badge_style;
clone.alternate_order_size = alternate_order_size;
clone.favorite_algos = favorite_algos_vm.create();
clone.size_display = size_display.create();
clone.calculator_buttons = size_presets.Select(vm => (int)vm.value).ToArray();
// columns, row height (the latter determined by columns font size)
clone.orders_column = _orders.get_settings();
clone.bid_column = _bid.get_settings();
clone.price_column = _price.get_settings();
@@ -264,13 +381,28 @@ namespace mbts.spark {
clone.volume_column = _volume.get_settings();
clone.last_column = _last.get_settings();
clone.piq_column = _piq.get_settings();
clone.row_height = LadderRender.measure_row_height(clone);
// market break lines
clone.bid_break_line = _bid_mkt_line.get_settings();
clone.ask_break_line = _ask_mkt_line.get_settings();
clone.mid_break_line = _mid_mkt_line.get_settings();
clone.session_low_line = _session_low_mkt_line.get_settings();
clone.session_high_line = _session_high_mkt_line.get_settings();
clone.session_range_fill = _session_range_fill.get_settings();
clone.row_height = LadderRender.measure_row_height(clone);
// left-panel (defaults)
clone.default_time_in_force = default_time_in_force;
// - account
if (default_account.HasValue)
clone.default_account = default_account.Value.id;
// - algo
clone.default_algo = default_algo != null ?
AlgoID.from_algo_key(default_algo.key, _model.dynamic_helper.dynamic_type_index) :
(_desired_algo ?? LadderSettings.Default.default_algo);
// - components
clone.auto_clear = auto_clear;
clone.order_entry_panel_initial_width = _order_entry_panel_width;
clone.zoom_factor = zoom_factor;
// left-panel (components)
clone.day_visible = _day_visibility.show;
clone.high_visible = _high_visibility.show;
clone.low_visible = _low_visibility.show;
@@ -288,25 +420,23 @@ namespace mbts.spark {
clone.cancel_buys_visible = _cancel_buys_visibility.show;
clone.cancel_sells_visible = _cancel_sells_visibility.show;
clone.cancel_all_visible = _cancel_all_visibility.show;
clone.order_entry_panel_initial_width = _get_order_entry_panel_width();
clone.zoom_slider_visible = _zoom_slider_visbility.show;
clone.zoom_factor = _get_zoom_factor();
clone.net_change = _create_net_change_terms(show_in_net_change_dollars, show_in_net_change_ticks);
clone.net_change = price_format;
// safety checks
clone.show_warn_on_cross = show_warn_on_cross;
return clone;
LadderSettings.NetChangeTerms _create_net_change_terms(bool dollars, bool ticks) {
if (dollars) {
return LadderSettings.NetChangeTerms.Dollars;
}
if (ticks) {
return LadderSettings.NetChangeTerms.Ticks;
}
return LadderSettings.NetChangeTerms.None;
}
}
public void Dispose() => favorite_algos_vm.Dispose();
public IDisposable defer_updates() => _update.defer_execution();
IDisposable IObservable<LadderSettings>.Subscribe(IObserver<LadderSettings> observer) {
result_changed += notify;
return Disposable.Create(() => result_changed -= notify);
void notify() => observer.OnNext(get_result());
}
LadderSettings IValueFactory<LadderSettings>.create() => get_result();
void IValueFactory<LadderSettings>.load_from(LadderSettings value) => load(value);
IMutableValueWrapperVM<uint> _create_size_preset_vm() => _create_size_preset_vm(0);
IMutableValueWrapperVM<uint> _create_size_preset_vm(uint init) {
@@ -314,6 +444,20 @@ namespace mbts.spark {
vm.PropertyChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
return vm;
}
void _try_select_desired_account(bool bypass_setter = false) {
if (_desired_account.HasValue && (!_default_account.setter_called || bypass_setter) && _accounts.TryGetValue(_desired_account.Value, out var acc_tup)) {
_desired_account = null;
_default_account.bypass_setter(acc_tup);
}
}
void _try_select_desired_algo(bool bypass_setter = false) {
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value, _model.dynamic_helper.dynamic_type_index), out var vm)) {
_desired_algo = null;
_default_algo.bypass_setter(vm);
}
}
public class LadderColumnSettingsVM : ViewModelBase, ITemplateSelectorKey {
readonly Property<double> _font_size;
@@ -323,11 +467,13 @@ namespace mbts.spark {
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<string> _font_display_name;
readonly Property<bool> _show;
readonly bool _can_toggle_visibility;
readonly string _template_key;
uint _width;
public LadderColumnSettingsVM(string name, bool can_toggle_visibility = false) {
public LadderColumnSettingsVM(string name, string template_key = null, bool can_toggle_show = false) {
this.name = name;
_can_toggle_visibility = can_toggle_visibility;
_template_key = template_key;
this.can_toggle_show = can_toggle_show;
foreground_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
background_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
_show = backing(nameof(show), true);
@@ -338,26 +484,35 @@ namespace mbts.spark {
_foreground = backing(nameof(foreground), Colors.Black);
_background = backing(nameof(background), Colors.White);
_font_display_name = backing(nameof(font_display_name),
() => TextUtils.display_name(font_family, font_weight, font_size), nameof(font_family), nameof(font_weight), nameof(font_size));
() => TextUtils.display_name(font_family, font_weight, font_size), new[] { nameof(font_family), nameof(font_weight), nameof(font_size) });
_font_size.Changed += (oldval, newval) => font_size_changed?.Invoke(oldval, newval);
toggle_show_details_cmd = new DelegateCommand(() => {
show_details = !show_details;
RaisePropertyChanged(nameof(show_details));
});
}
public event Action<double, double> font_size_changed;
public string name { get; }
public Typeface typeface { get => _typeface.Value; set => _typeface.Value = value; }
public FontFamily font_family => _font_family.Value;
public FontWeight font_weight => _font_weight.Value;
public string font_display_name => _font_display_name.Value;
public double font_size { get => _font_size.Value; set => _font_size.Value = value; }
public uint width { get; set; }
public uint width { get => _width; set => Set(ref _width, value); }
public bool show { get => _show.Value; set => _show.Value = value; }
public bool can_toggle_show { get; }
public bool show_details { get; private set; }
public Color foreground { get => _foreground.Value; set => _foreground.Value = value; }
public Color background { get => _background.Value; set => _background.Value = value; }
public ObservableCollection<ColorPaletteVM> foreground_palettes { get; }
public ObservableCollection<ColorPaletteVM> background_palettes { get; }
public event Action<double, double> font_size_changed;
public virtual string sample_text => "999-56789.01234";
string ITemplateSelectorKey.key => _can_toggle_visibility ? ui.spark.LadderSettingsView.ColumnVisibilityToggleTemplateKey : null;
public ICommand toggle_show_details_cmd { get; }
string ITemplateSelectorKey.key => _template_key;
public void assign_from(LadderColumnSettings settings) {
typeface = new Typeface(
@@ -370,7 +525,6 @@ namespace mbts.spark {
show = settings.show;
foreground = settings.foreground.rgb_color();
background = settings.background.rgb_color();
RaisePropertyChanged(nameof(width), nameof(show));
}
public LadderColumnSettings get_settings() {
@@ -395,7 +549,7 @@ namespace mbts.spark {
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
public Brush badge_background { get; } = Brushes.White;
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnSampleTemplateKey;
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnTemplateKey;
}
public class LadderMarketBreakLineVM : ViewModelBase {
@@ -463,7 +617,7 @@ namespace mbts.spark {
}
}
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", true);
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", LadderSettingsView.VolumeColumnTemplateKey, true);
public VolumeAtPriceAnimationVM buy { get; }
public VolumeAtPriceAnimationVM sell { get; }
public VolumeAtPriceAnimationVM passive { get; }
@@ -566,17 +720,15 @@ namespace mbts.spark {
}
public class FavoriteAlgosVM : DisposableViewModelBase, IValueFactoryVM<LadderFavoriteAlgosSettings> {
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
int _column_count = 2;
public FavoriteAlgosVM(OrdersModel model) {
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
_algo_definitions = algo_definitions;
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
_dispose = model.algo_model.definitions.OnDispatcher().Subscribe(update => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, d => AlgoDefinitionVM.create(model, d, false));
});
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -596,11 +748,17 @@ namespace mbts.spark {
}
public LadderFavoriteAlgosSettings create() =>
new LadderFavoriteAlgosSettings(_raw_items.Where(fai => fai.algo_definition != null).Select(fai => fai.algo_definition.key).ToArray(), column_count);
new LadderFavoriteAlgosSettings(_raw_items
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray(),
column_count);
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic(),
Filter = obj => obj is AlgoDefinitionVM vm && vm.key.is_persisted
});
}
@@ -611,7 +769,7 @@ namespace mbts.spark {
protected FavoriteAlgoItem() { }
public FavoriteAlgoItem(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
public FavoriteAlgoItem(IObservableCollection<AlgoDefinitionVM> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
selector = new KeyedItemSelector<AlgoDefinitionVM, AlgoKey>(this, nameof(algo_definition), algo_definitions, d => d.key);
this.algo_definitions_view = algo_definitions_view;
}
@@ -624,4 +782,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -1,13 +1,11 @@
using bts.api;
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -36,32 +34,28 @@ namespace mbts.spark {
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
double _position, _day_position;
bool _show_account_position, _show_day_position;
IWindowSettingsManager<LadderSettings> _settings_manager;
ulong _misc_time = 0;
ulong _last_trade_time = 0;
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
public static LadderVM create(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) {
var non_shared_wsm = settings_manager.as_non_shared();
return new LadderVM(bus, model, settings_manager, non_shared_wsm, init, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))));
}
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
public static LadderVM create_readonly(IBus bus, LadderModel model) {
var default_settings = LadderSettings.Default;
return create_readonly(bus, model, new WindowSettingsManager<LadderSettings>.DisconnectedImpl(default_settings), default_settings);
}
public static LadderVM create_readonly(IBus bus, LadderModel model, IObservable<LadderSettings> settings) =>
create_readonly(bus, model, LadderSettings.Default, settings);
public static LadderVM create_readonly(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) =>
new LadderVM(bus, model, settings_manager, settings_manager.as_non_shared(), init);
public static LadderVM create_readonly(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings);
LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, IWindowSettingsManagerNonShared<LadderSettings> non_shared_wsm, LadderSettings init, OrderPropertiesEditorVM order_properties_vm = null) {
LadderVM(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings, OrderPropertiesEditorVM order_properties_vm = null) {
_bus = bus;
_model = model;
current_settings = init;
this.settings = settings;
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
_settings_manager = non_shared_wsm;
this.order_properties_vm = order_properties_vm;
_high = backing(nameof(high), double.NaN);
_low = backing(nameof(low), double.NaN);
@@ -81,12 +75,14 @@ namespace mbts.spark {
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
// 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), false);
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
_status = backing(nameof(status_message), "");
_status_brush = backing(nameof(status_background), _transparent_status_brush);
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
var settings_observer = new CompositeObserver<LadderSettings> {
/// first things first: assign our <see cref="current_settings"/>
(s => current_settings = s),
// brush props
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
@@ -113,6 +109,19 @@ namespace mbts.spark {
if (Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) | Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position))) {
RaisePropertyChanged(nameof(position_display_text));
}
size_calculator.buttons = s.calculator_buttons;
_auto_clear.set(s.auto_clear);
// column widths
_orders_column_width.set(s.orders_column.width);
_bid_column_width.set(s.bid_column.width);
_price_column_width.set(s.price_column.width);
_ask_column_width.set(s.ask_column.width);
_last_column_width.set(s.last_column.width);
_piq_column_width.set(s.piq_column.width);
_volume_column_width.set(s.volume_column.general.width);
// left panel/zoom
_left_panel_width.set(s.order_entry_panel_initial_width);
_zoom_factor.set(s.zoom_factor);
}
};
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
@@ -128,30 +137,13 @@ namespace mbts.spark {
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
show_book_command = new DelegateCommand(_show_book);
show_ticket_command = new DelegateCommand(_show_ticket);
settings_command = new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<LadderSettings, LadderSettingsVM>(
settings_manager, s => s.ladder, UserSettings.FileExtensions.LADDER,
s => {
var svm = new LadderSettingsVM(model.order_gateway, security.ContractCase == Security.ContractOneofCase.Synthetic, () => left_panel_width, () => zoom_factor);
svm.load(s);
return svm;
}, s => s.with_window_specifics(get_save_state().get_window_specifics()));
var dialog = new ui.spark.SimpleGridSettingsDialog {
DataContext = vm,
Title = $"Ladder Settings - '{security}'"
};
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}, -500, -500);
settings_command = new DelegateCommand(() => request_settings_dialog?.Invoke());
orders = new ObservableCollection<LadderOrderVM>();
_dispose = Disposable.Create(
settings_manager.subscribe_current().OnDispatcher().Subscribe(settings_observer),
settings_command,
non_shared_wsm,
settings.OnDispatcher().Subscribe(settings_observer),
get_order_properties_editor_hooks(),
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
// we need to hook this up to model.orders which is filtered for just the security
model.orders.OnDispatcher().Subscribe(working => {
@@ -176,7 +168,7 @@ namespace mbts.spark {
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
} else {
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, settings.Select(s => s.badge_style).OnDispatcher());
_slot_price_vs_orders[slot_price_target] = slot_vm;
}
@@ -243,14 +235,13 @@ namespace mbts.spark {
}
if ((Set(ref _position, total, nameof(position)) | Set(ref _day_position, day, nameof(day_position))) && (show_day_position || show_account_position))
RaisePropertyChanged(nameof(position_display_text));
}),
position_src.Connect());
}));
}
}
public IBus bus => _bus;
public event Action on_tick;
public event Action on_tick, request_settings_dialog, settings_invalidated;
public MDConsumerHandle md => _model.md;
// we return only entries who have piqs and whose piqs are not uint.Max
@@ -285,6 +276,7 @@ namespace mbts.spark {
remove => _on_disposed_impl -= value;
}
public SecurityImmutable security => _model.security;
public LadderSettings current_settings { get; private set; }
public double high => _high.Value;
public double low => _low.Value;
public double change => _change.Value;
@@ -294,7 +286,7 @@ namespace mbts.spark {
public double day_position => _day_position;
public ulong volume => _volume.Value;
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
public CalculatorLogic size_calculator { get; }
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
/// Important: <see cref="order_properties_vm"/> can be null.
public OrderPropertiesEditorVM order_properties_vm { get; }
public ObservableCollection<LadderOrderVM> orders { get; }
@@ -302,11 +294,11 @@ namespace mbts.spark {
public ICommand send_order_command { get; }
public ICommand show_book_command { get; }
public ICommand show_ticket_command { get; }
public ICommand settings_command { get; }
public Brush bid_background => _bid_background.view;
public Brush bid_foreground => _bid_foreground.view;
public Brush ask_background => _ask_background.view;
public Brush ask_foreground => _ask_foreground.view;
public DialogCommand settings_command { get; }
public bool show_day => _show_day.view;
public bool show_high => _show_high.view;
public bool show_low => _show_low.view;
@@ -346,8 +338,10 @@ namespace mbts.spark {
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
internal IObservable<LadderSettings> settings { get; }
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
var order = op.order;
double p = order.price();
@@ -366,10 +360,26 @@ namespace mbts.spark {
}
public LadderSettings get_save_state() {
var state = _settings_manager.current_settings();
state = state.with_window_specifics(new LadderSettings.WindowSpecifics(left_panel_width, orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, zoom_factor, piq_column_width));
state.use_defaults = _settings_manager.instance_settings == null;
return state;
var rv = current_settings.with_window_specifics(
new LadderSettings.WindowSpecifics(
left_panel_width,
orders_column_width,
bid_column_width,
price_column_width,
ask_column_width,
last_column_width,
volume_column_width,
zoom_factor,
piq_column_width));
rv.auto_clear = auto_clear;
if (order_properties_vm != null) {
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
if (order_properties_vm.selected_account.HasValue)
rv.default_account = order_properties_vm.selected_account.Value.id;
if (order_properties_vm.selected_algo_definition != null)
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key, _model.order_gateway.dynamic_helper.dynamic_type_index);
}
return rv;
}
public void key_down(KeyEventArgs e) {
@@ -384,20 +394,24 @@ namespace mbts.spark {
}
}
public void send_order((double price, Side? side, MouseButton buton) args) {
if (args.buton == MouseButton.Middle) return;
var size_from_alternate = args.buton == MouseButton.Right;
if (order_properties_vm == null)
public void on_mini_orderbook_closed(bts.shell.orderbook.OrderBookStartupData ob_state) {
current_settings = SparkUtils.clone_proto(current_settings);
current_settings.mini_ob = new LadderMiniOrderBookSettings(ob_state.orderbook_grid.general);
settings_invalidated?.Invoke();
}
public void send_order((double price, Side? side, MouseButton button) args) {
if (order_properties_vm == null || args.button == MouseButton.Middle)
return;
var s = _settings_manager.current_settings();
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
var size_from_alternate = args.button == MouseButton.Right;
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
return;
}
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
}
else {
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
send_order_async(args.side.Value, size, args.price).ignore();
}
}
@@ -0,0 +1,58 @@
using bts.api;
using bts.api.orderman;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
namespace mbts.spark {
class LadderVMContainer : ViewModelBase, IDisposable {
readonly IBus _bus;
readonly OrdersModel _model;
LadderVM _value;
SecurityMD? _security_md;
HashSet<uint> _selected_order_tree = new HashSet<uint>();
public LadderVMContainer(IBus bus, OrdersModel model) {
_bus = bus;
_model = model;
}
public SecurityMD? security_md {
get => _security_md;
set {
if (_security_md != value) {
_security_md = value;
_reset_ladder_vm();
RaisePropertyChanged(nameof(security_md), nameof(LadderVMContainer.value));
}
}
}
public LadderVM value => _value;
public void Dispose() {
if (_value != null) {
_value.Dispose();
_value = null;
RaisePropertyChanged(nameof(value));
}
}
public void set_orders(IEnumerable<uint> orders) {
_selected_order_tree = new HashSet<uint>(orders);
_reset_ladder_vm();
}
void _reset_ladder_vm() {
if (_value == null && !_security_md.HasValue)
return;
Disposable.replace(ref _value,
_security_md.HasValue ?
LadderVM.create_readonly(_bus,
new LadderModel(security_md.Value, _model, _model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
RaisePropertyChanged(nameof(value));
}
}
}
@@ -67,7 +67,8 @@ namespace mbts.spark {
NativePriceSize _last_trade;
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
int _old_tooltip_row = int.MaxValue;
double _old_last_trade_price = 0.0;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
bool _tooltips_are_hooked_up = false;
ToolTip tooltip_view = new ToolTip();
IDisposable _parameterized_order_popup;
@@ -337,13 +338,28 @@ namespace mbts.spark {
if (e.ChangedButton == MouseButton.Middle) {
if (bs.HasValue) {
_send_parameterized_order(point, bs.Value);
return;
} else if (column.column_type == ColumnType.Price) {
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
show_mbo_popup(point).ignore();
} else {
_scroll_to_center();
}
return;
}
} else if (_vm != null) {
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
// if warning enabled && (buy above opposite inside market || ask below opposite inside market)
// when 1-sided market, _get_top_price will return NaN, and so the price comparison is false
// meaning show no warning == correct
if (_vm.show_warn_on_cross_from_settings &&
(size > 0) &&
((bs == Side.Buy && price >= _get_top_price(_asks)) || (bs == Side.Sell && price <= _get_top_price(_bids)))) {
string prompt = $"Are you sure you want to CROSS the market to {bs.ToString().ToUpper()} {size} @ {security.spec_or_default().format(price)} ?";
if (!BTSMessageBox.Confirm(prompt, "Cross Market Alert", MessageBoxImage.Warning)) {
return;
}
}
}
var pbs = (price, bs, e.ChangedButton);
@@ -678,12 +694,14 @@ namespace mbts.spark {
// At any rate, we need this part hiding the tooltip so that we don't leave random tooltips around
// and we have to reset to old row so that when we go back to what could be our preivous row, the tooltip gets drawn again
//System.Diagnostics.Debug.WriteLine(" Tooltip - Volume is 0");
_old_tooltip_row = int.MaxValue;
_hide_tool_tip();
//_old_tooltip_row = Int32.MaxValue;
}
}
} else {
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
_old_tooltip_row = int.MaxValue;
_hide_tool_tip();
}
}
@@ -846,21 +864,43 @@ namespace mbts.spark {
void _refresh_volume_at_price() {
if (!_settings.volume_column.general.show)
return;
// if the last trade has a new size then we need to explicitly
// mark the volume container as dirty because there is a chance
// we have arrived here off a size change and not a volume change
// or a random change, like applying new settings
bool last_trade_size_changed = _old_last_trade.HasValue ? _last_trade.size != _old_last_trade.Value.size : true;
if (last_trade_size_changed && _settings.volume_column.show_ltq) {
_volume_container.mark_dirty();
}
var vapls = _volume_container.read_new_value();
if (vapls != null) {
Log.assert_d(_price_list != null);
var ltp = _last_trade.price;
var last_index = LadderRender.index_of(_price_list, ltp, _price_comparer);
string direction_arrow = string.Empty;
if (MathUtils.epsilon_equals(ltp, _old_last_trade_price)) {
// do nothing if in here b/c direction_arrow already string.empty
} else if (ltp > _old_last_trade_price) {
direction_arrow = "▲";
} else {
direction_arrow = "▼";
}
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _last_trade, direction_arrow);
_old_last_trade_price = ltp;
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
_old_last_trade = _last_trade;
}
string _get_last_arrow_string() {
// if _old_last_trade doesn't have a value, this is the first time the ladder has been launched
// and so the direction should be drawn without an arrow since there is no basis for comparison
if (!_old_last_trade.HasValue) {
_direction_arrow = string.Empty;
// if the Lasts are different then we need to reformat the direction arrow in some fashion
} else if (_old_last_trade != _last_trade) {
var ltp = _last_trade.price;
if (MathUtils.epsilon_equals(ltp, _old_last_trade.Value.price)) {
// update on the same price, so size must be different
_direction_arrow = string.Empty;
} else if (ltp > _old_last_trade.Value.price) {
_direction_arrow = "▲";
} else {
_direction_arrow = "▼";
}
}
// regardless of if we've arrived here via a new Last (and new direction arrow)
// or we're reusing the old one, this format bit is the same
return $"{_direction_arrow} {_last_trade.size} ";
}
}
@@ -9,9 +9,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace mbts.spark {
@@ -139,6 +137,7 @@ namespace mbts.spark {
// properly Dispose of it later when the popup is closed.
DataContext = obvm,
Child = view,
// StaysOpen true for debugging
StaysOpen = false,
PlacementTarget = popup_placement_target,
Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom,
@@ -155,7 +154,10 @@ namespace mbts.spark {
popup.SetBinding(System.Windows.Controls.Primitives.Popup.IsOpenProperty,
nameof(OrderBookVM.is_row_collection_populated)
);
popup.Closed += (sender, event_args) => (popup.DataContext as IDisposable)?.Dispose();
popup.Closed += (sender, event_args) => {
(_parent.DataContext as LadderVM)?.on_mini_orderbook_closed(obvm.create());
(popup.DataContext as IDisposable)?.Dispose();
};
popup.MouseUp += (sender, event_args) => {
if (event_args.ChangedButton == MouseButton.Middle) {
popup.IsOpen = false;
@@ -196,8 +196,7 @@ namespace mbts.spark {
ContractSpecificationImmutable spec,
VolumeColumnSettings settings,
int last_index,
NativePriceSize last_trade,
string direction_arrow
string last_size_and_direction
) {
if (!data.Any()) {
_clear_visual(dv);
@@ -216,16 +215,16 @@ namespace mbts.spark {
Brush total_brush = null;
if (settings.total.bars.result.HasValue) {
total_brush = new SolidColorBrush(settings.total.bars.result.Value);
} else if (components.Count == 0) {
} else if (components.Count == 0 && !settings.show_ltq) {
_clear_visual(dv);
return;
}
// inflate the max so that the graph never fills the available space
double max = data.Max(v => v.Volume) * 1.1;
Brush text_brush = settings.general.foreground.rgb_brush();
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
/// local methods
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
Brush text_brush = settings.general.foreground.rgb_brush();
double y = row_height * r;
var last_trade_here = r == last_index;
@@ -259,7 +258,8 @@ namespace mbts.spark {
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
numbers_text.Add($"{direction_arrow} {last_trade.size} ");
numbers_text.Add(last_size_and_direction);
text_brush = BTSCoreColors.white;
}
if (numbers_text.Count > 0) {
string str = string.Join("/", numbers_text);
File diff suppressed because it is too large. Load diff
@@ -10,10 +10,15 @@ namespace mbts.ui.spark {
/// </summary>
partial class LadderSettingsView : UserControl {
public const string
OrdersColumnSampleTemplateKey = nameof(OrdersColumnSampleTemplateKey),
ColumnVisibilityToggleTemplateKey = nameof(ColumnVisibilityToggleTemplateKey),
OrdersColumnTemplateKey = nameof(OrdersColumnTemplateKey),
VolumeColumnTemplateKey = nameof(VolumeColumnTemplateKey),
LastColumnTemplateKey = nameof(LastColumnTemplateKey),
FavoriteAlgoAddButtonTemplateKey = nameof(FavoriteAlgoAddButtonTemplateKey);
public static readonly IMultiValueConverter LadderColumnToggleShowToolTipConverter =
ValueConverter.create<bool, bool, string, string>((can_toggle_show, show, col_name) =>
can_toggle_show ? $"{(show ? "Hide" : "Show")} {col_name}" : $"{col_name} cannot be {(show ? "hidden" : "shown")}.");
public LadderSettingsView() =>
InitializeComponent();
@@ -152,20 +152,21 @@ namespace mbts.spark {
// When middle click over bid or ask columns:
// Non-parameterizalbe order: change cursor to No indicating sending an order is not possible
// Parameterizable order: if qty is null/0, flash and No cursor
var pos = e.GetPosition(_host);
var column_type = _host._column_for_point(pos).column_type;
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
if (e.MiddleButton == MouseButtonState.Pressed &&
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
if (!selected_algo_can_expand) {
_host.Cursor = Cursors.No;
}
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
_host._vm.size_calculator.flash_error();
_host.Cursor = Cursors.No;
if (_host._vm != null) {
var pos = e.GetPosition(_host);
var column_type = _host._column_for_point(pos).column_type;
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
if (e.MiddleButton == MouseButtonState.Pressed &&
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
if (!selected_algo_can_expand) {
_host.Cursor = Cursors.No;
}
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
_host._vm.size_calculator.flash_error();
_host.Cursor = Cursors.No;
}
}
}
_is_mouse_down = true;
InvalidateVisual();
}
@@ -288,8 +288,11 @@
/>
</Grid.LayoutTransform>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
MinWidth="60" MaxWidth="150"/>
<ColumnDefinition
Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
MinWidth="{x:Static spark:LadderView.MinLeftPanelWidth}"
MaxWidth="{x:Static spark:LadderView.MaxLeftPanelWidth}"
/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
@@ -309,11 +312,14 @@
</Style>
</StackPanel.Resources>
<StackPanel x:Name="_slider_stack_panel" Margin="5,5,5,0">
<Slider x:Name="_ui_scaler" Value="{Binding zoom_factor}"
Minimum="{StaticResource MinZoomFactor}"
Maximum="{StaticResource MaxZoomFactor}"
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Slider
x:Name="_ui_scaler"
Value="{Binding zoom_factor}"
Minimum="{x:Static spark:LadderView.MinZoomFactor}"
Maximum="{x:Static spark:LadderView.MaxZoomFactor}"
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
<Grid Margin="2,4,2,0">
<Grid.RowDefinitions>
@@ -11,6 +11,12 @@ using System.Windows.Input;
namespace mbts.spark {
partial class LadderView : UserControl {
public const double
MinLeftPanelWidth = 60,
MaxLeftPanelWidth = 150,
MinZoomFactor = 0.5,
MaxZoomFactor = 1.5;
public static IMultiValueConverter PositionToolTipConverter =
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
const string day_pos_text = "day", total_pos_text = "total";
@@ -40,8 +46,6 @@ namespace mbts.spark {
return sb.ToString();
});
readonly double _max_zoom, _min_zoom;
internal LadderView(LadderVM vm) {
InitializeComponent();
_column_headers_presenter.Columns = new GridViewColumnCollection();
@@ -50,19 +54,6 @@ namespace mbts.spark {
this.vm = vm;
DataContext = vm;
DataContextChanged += datacontext_changed;
if (TryFindResource("MaxZoomFactor") is double found_max) {
_max_zoom = found_max;
} else {
// is there a better way of not specifying the constant twice ?
// once in the resource dictionary here and again in the xaml, seems like there has to be a better way
_max_zoom = 1.5;
}
// ditto
if (TryFindResource("MinZoomFactor") is double found_min) {
_min_zoom = found_min;
} else {
_min_zoom = 0.5;
}
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
this.PreviewMouseDown += LadderView_PreviewMouseDown;
@@ -78,7 +69,7 @@ namespace mbts.spark {
var delta = (e.Delta > 0) ? 0.1 : -0.1;
var proposed_scaler_value = _ui_scaler.Value + delta;
// Here we enforce the same contraints that are on the gui slider regarding min and max
if (_min_zoom < proposed_scaler_value && proposed_scaler_value < _max_zoom) {
if (MinZoomFactor < proposed_scaler_value && proposed_scaler_value < MaxZoomFactor) {
_ui_scaler.Value = proposed_scaler_value;
}
}
@@ -63,8 +63,10 @@
IsEditMode="False"
IsLazy="True"
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
SelectedSecurity="{Binding security}"
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}"/>
@@ -145,6 +145,7 @@
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ShowDialogSkirt="True"
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
@@ -14,7 +14,6 @@
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
mc:Ignorable="d"
MaxHeight="240"
MinWidth="875"
d:DesignHeight="600" d:DesignWidth="1080"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}">
@@ -99,7 +98,16 @@
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
<!-- We added this IsSelected after seeing some strangeness when bringing up the Mini OB,
then clicking the attach button, then performing a click to modify a param in the popup, the grid
would lose the appearance of its selected row. I think this was actually a result of a bad path
in the SelectionHighlight expression below, but that leaves me questioning how if it was bad (and
it was, I confirmed in Snoop) did the Grid successfully show a highlighted row in the first place.
At any rate, I have a strong suspicion this Setter is no longer necessary, but I am too exhausted
and scared to remove it at this point.
-->
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
</Style>
<DataTemplate x:Key="{x:Static sp:MiniOrderBookView.RowTemplateKey}">
<sp:MiniOrderBookRowView/>
@@ -121,10 +129,9 @@
Rows="{Binding rows}"
RowStyle="{StaticResource {x:Static sp:MiniOrderBookView.RowStyleKey}}"
RowTemplate="{StaticResource {x:Static sp:MiniOrderBookView.RowTemplateKey}}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
RowsHorizontalScrollBarVisibility="Collapsed"
RowHeight="20"
SelectionMode="Single"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
@@ -165,7 +165,7 @@ namespace mbts.spark {
DataGridFilterKnownTemplates.EnumValues,
_wrap_comparer(r => r.time_in_force)));
// (algo items)
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key, model.dynamic_helper.dynamic_type_index)).OnDispatcher();
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
// order type
@@ -176,7 +176,7 @@ namespace mbts.spark {
_wrap_comparer(r => r.order_type),
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var order_type_filter_observer, out var order_type_items_view)));
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
order_type_items_view.CustomSort = algo_items_comp;
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
@@ -188,7 +188,7 @@ namespace mbts.spark {
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
o => new AlgoDataVM(o.root_algo),
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var root_algo_type_observer, out var root_algo_type_items_view),
DefaultColumnHeaders.RootAlgoType));
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
root_algo_type_items_view.CustomSort = algo_items_comp;
@@ -724,6 +724,33 @@ namespace mbts.spark {
}
}
public class CheckColumn : FrameworkElementColumnBase<Columns> {
readonly BindingBase _checked_binding;
public CheckColumn(string checked_property) : base(Columns.Check) {
id = key.ToString();
Width = 30;
_checked_binding = new Binding(checked_property);
var header_cb = new CheckBox();
header_cb.ToolTip = "Select / Unselect All";
header_cb.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(OrderBookVM.all_rows_selected)));
this.Header = header_cb;
}
public override FrameworkElement view_factory() {
var grid = new Grid() {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
var cb = new CheckBox();
cb.Name = "check_col";
cb.SetBinding(CheckBox.IsCheckedProperty, _checked_binding);
grid.Children.Add(cb);
return grid;
}
}
public class CancelColumn : FrameworkElementColumnBase<Columns> {
readonly BindingBase _command_binding;
@@ -812,7 +839,10 @@ namespace mbts.spark {
VerticalAlignment = VerticalAlignment.Center,
};
attach_grid.Children.Add(attach_button);
attach_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
attach_button.Click += (s, e) => {
popup.Child = create_popup_child();
popup.IsOpen = !popup.IsOpen;
};
var orderbook_vm_datacontext_binding = new Binding("DataContext");
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
@@ -829,84 +859,9 @@ namespace mbts.spark {
var row_vm_attach_command_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.attach_command)));
row_vm_attach_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
attach_button.SetBinding(Button.CommandProperty, row_vm_attach_command_binding);
attach_button.SetBinding(Button.CommandProperty, row_vm_attach_command_binding);
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = AlgoEditorGridView.create_attach_view();
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.attach_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
#region Dialog Skirt
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var send_attach_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.send_attached_orders_cmd))) {
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
send_attach_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, send_attach_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "attach_border",
Child = grid,
};
#endregion
popup.Child = border;
// set to true for debugging
popup.StaysOpen = false;
popup.PlacementTarget = attach_button;
popup.Placement = PlacementMode.Bottom;
@@ -914,7 +869,6 @@ namespace mbts.spark {
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
});
#endregion
#region Detach Button
@@ -950,6 +904,83 @@ namespace mbts.spark {
the_whole_thing.Children.Add(popup);
return the_whole_thing;
UIElement create_popup_child() {
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = AlgoEditorGridView.create_attach_view();
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.attach_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var send_attach_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.send_attached_orders_cmd))) {
// set to true for debugging
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
send_attach_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, send_attach_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}
};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "attach_border",
Child = grid,
};
return border;
}
}
}
@@ -970,17 +1001,21 @@ namespace mbts.spark {
public override FrameworkElement view_factory() {
var popup = new System.Windows.Controls.Primitives.Popup();
#region Attach Button
#region Update Button
// create the attach button and assign the menu to it
// create the update button and assign the menu to it
var update_button = new Button() {
Name = "update_button",
Content = "···",
Margin = new Thickness(2),
VerticalAlignment = VerticalAlignment.Center,
Focusable = false
Focusable = false,
ToolTip = "Update"
};
update_button.Click += (s, e) => {
popup.Child = create_popup_child();
popup.IsOpen = !popup.IsOpen;
};
update_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
var orderbook_vm_datacontext_binding = new Binding("DataContext");
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
@@ -995,89 +1030,14 @@ namespace mbts.spark {
row_vm_update_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
update_button.SetBinding(Button.CommandProperty, row_vm_update_command_binding);
// update button is only visible for orders under the control of an algo
var update_button_visibility_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.root_algo_order_type))) {
Converter = ValueConverter.create<CustomOrderDefinition.CustomOneofCase, Visibility>(ord_type => utilities.OrderDataHelper.outright_order_types.Contains(ord_type) ? Visibility.Hidden : Visibility.Visible)
};
update_button_visibility_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
update_button.SetBinding(Button.VisibilityProperty, update_button_visibility_binding);
#endregion
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = new AlgoEditorGridView {
Margin = new Thickness(6),
CanUserChangeAlgoRoot = false,
};
;
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.order_mod_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
#region Dialog Skirt
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var modify_selected_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.modify_selected_orders_cmd))) {
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
modify_selected_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, modify_selected_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}
};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "update_border",
Child = grid,
};
#endregion
popup.Child = border;
popup.StaysOpen = false;
popup.PlacementTarget = update_button;
popup.Placement = PlacementMode.Bottom;
@@ -1086,8 +1046,9 @@ namespace mbts.spark {
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
});
popup.Closed += (s, e) => {
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm)
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm) {
ob_vm.is_attaching_orders = false;
}
};
var the_whole_thing = new Grid();
@@ -1095,6 +1056,83 @@ namespace mbts.spark {
the_whole_thing.Children.Add(popup);
return the_whole_thing;
UIElement create_popup_child() {
var order_params = new AlgoEditorGridView {
Margin = new Thickness(6),
CanUserChangeAlgoRoot = false,
};
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.order_mod_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var modify_selected_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.modify_selected_orders_cmd))) {
// set to true for debugging
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
modify_selected_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, modify_selected_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}
};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "update_border",
Child = grid,
};
return border;
}
}
}
@@ -1139,7 +1177,6 @@ namespace mbts.spark {
public class CommitColumn : FrameworkElementColumnBase<Columns> {
public CommitColumn() : base(Columns.Commit) {
id = key.ToString();
Header = "";
Width = 20;
}
@@ -27,14 +27,16 @@ namespace mbts.spark {
// for mini-ob
uint _prospective_quantity;
double _prospective_price;
Action<bool> _on_selection_changed;
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded) {
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
_owner = owner;
this.order_tuple = order_tuple;
is_visible = !is_child;
_is_expanded = is_expanded;
prospective_quantity = this.quantity;
prospective_price = this.price;
_on_selection_changed = on_selection_changed;
}
//// This takes care of the case where the OB has all rows visible
@@ -118,7 +120,11 @@ namespace mbts.spark {
public int depth_level { get => _depth_level; set => Set(ref _depth_level, value); }
public bool is_selected {
get => _is_selected;
set => Set(ref _is_selected, value);
set {
if (Set(ref _is_selected, value)) {
_on_selection_changed(value);
}
}
}
public uint prospective_quantity {
get => _prospective_quantity;
@@ -190,14 +196,18 @@ namespace mbts.spark {
public ICommand attach_command => _attach_command ?? (
_attach_command = new DelegateCommand(() => {
// the _select me will trigger the OBVM.attach_vm to get primed
_select_me();
if (!is_selected) {
_select_me();
}
_owner.begin_attach_algos();
})
);
public ICommand update_command => _update_command ?? (
_update_command = new DelegateCommand(() => {
_select_me();
if (!is_selected) {
_select_me();
}
})
);
@@ -233,12 +243,10 @@ namespace mbts.spark {
private void _unselect_me() {
this.is_selected = false;
_owner.current_row = null;
}
private void _select_me() {
this.is_selected = true;
_owner.current_row = this;
}
// These fns present a way for the orderbook to avoid noisy, intermittent collection changes during "Expand/Collapse All" ops,
@@ -290,6 +298,10 @@ namespace mbts.spark {
// root display name
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
RaisePropertyChanged(nameof(root_algo_display_name));
// root order type
if (old_order_tuple.root_algo.value.CustomCase != order_tuple.root_algo.value.CustomCase) {
RaisePropertyChanged(nameof(root_algo_order_type));
}
// status
if ((Columns.Status & dirty) != 0)
RaisePropertyChanged(nameof(status));
@@ -18,8 +18,8 @@ using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@@ -31,12 +31,14 @@ using System.Windows.Input;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace mbts.spark {
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting {
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting, IRequestRowSelection {
const double
_min_right_panel_width = 200,
_min_order_params_height = 300,
_min_ladder_height = 100,
_min_fills_height = 100;
static readonly TimeSpan _selection_change_throttle = TimeSpan.FromSeconds(0.1);
readonly IBus _bus;
readonly OrdersModel _model;
readonly Property<OrderBookRowVM> _current_row;
@@ -45,7 +47,8 @@ namespace mbts.spark {
readonly OrderBookColumnCollection _columns;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
readonly SelectedOrderParamsMon _selected_params_mon;
readonly OrderParamsAggregator _selected_params_mon;
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly RightPanelModel _right_panel = new RightPanelModel();
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
double
@@ -56,16 +59,17 @@ namespace mbts.spark {
bool
_show_right_panel = OrderBookStartupData.Default.show_right_panel,
_is_attaching_orders,
_is_row_collection_populated;
_is_row_collection_populated,
_all_rows_selected;
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
HashSet<uint>
_attachable_selected_orders = new HashSet<uint>(),
_detachable_selected_orders = new HashSet<uint>();
FilterData _cached_filter_data;
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
public OrderBookVM(
IObservable<string> owner_title,
IBus bus,
INotificationService notification_service,
OrdersModel model,
@@ -74,7 +78,8 @@ namespace mbts.spark {
_bus = bus;
_model = model;
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle);
ladder_vm = new LadderVMContainer(bus, model);
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
order_mod_vm.algo_vm.algo_root = null;
@@ -98,7 +103,17 @@ namespace mbts.spark {
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
o => new OrderBookRowVM(this, o, rows_are_initially_expanded) {
o => new OrderBookRowVM(this, o, rows_are_initially_expanded, (row_is_selected) => {
// all are selected and one is checked off
if (all_rows_selected && !row_is_selected) {
_all_rows_selected = false;
RaisePropertyChanged(nameof(this.all_rows_selected));
// not all selected and now the last one gets turned on
} else if (!all_rows_selected && _row_collection.All(r => r.is_selected)) {
_all_rows_selected = true;
RaisePropertyChanged(nameof(this.all_rows_selected));
}
}) {
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
},
@@ -125,23 +140,22 @@ namespace mbts.spark {
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
const string title = "Order Book 'Mini' Fills Grid";
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_fills_settings_manager,
us => us.fills_grid,
UserSettings.FileExtensions.GRID,
s => fills_vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(fills_vm.columns.Select(c => c.create())),
Observable.Return(title)
s.with(fills_vm.columns.Select(c => c.create()))
),
show_save_as_defaults: false);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = title, DataContext = vm };
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
grid_settings_manager = new OrderBookGridSettingsManager(owner_title, bus, notification_service, model, rows, columns);
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
@@ -164,6 +178,11 @@ namespace mbts.spark {
if (ch.newval && fills_height < _min_fills_height)
fills_height = _min_fills_height;
}),
_selected_security_mon.SubscribeThrottled(_selection_change_throttle).to_security_md(bus).OnDispatcher().Subscribe(smd => ladder_vm.security_md = smd),
_subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
var hierarchical_order_keys = new HashSet<uint>(selected_hierarchical_rows.Select(r => r.order_key));
ladder_vm.set_orders(hierarchical_order_keys);
}),
_selected_params_mon.subscribe_all_active().Subscribe(on_all_selected_orders_active_changed),
_selected_params_mon.subscribe_security_plurality().Subscribe(p => {
security_plurality = p;
@@ -205,7 +224,6 @@ namespace mbts.spark {
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,
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_params_mon.on_security_md_map),
attach_vm,
_show_fills.observe_current(true)
.Subscribe(is_visible => {
@@ -313,8 +331,8 @@ namespace mbts.spark {
IDisposable connect_order_mod_vm() =>
Disposable.Create(
_selected_params_mon.subscribe_algos().Subscribe(order_mod_vm.algo_vm.load_many),
order_mod_vm.subscribe_is_dirty().Subscribe(_ => {
_selected_params_mon.subscribe_algos().OnDispatcher().Subscribe(order_mod_vm.algo_vm.load_many),
order_mod_vm.subscribe_is_dirty().OnDispatcher().Subscribe(_ => {
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
revert_selected_orders_cmd.RaiseCanExecuteChanged();
}),
@@ -322,10 +340,10 @@ namespace mbts.spark {
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_params_mon.subscribe_security_md().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().Subscribe(newval => vm.selected_account = newval));
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
}
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
@@ -334,6 +352,7 @@ namespace mbts.spark {
public event Action<OrderRecordImmutable?> selected_order_changed;
public event Action begin_attaching, end_attaching;
public event Action<(IEnumerable, bool)> request_selection;
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
public ObservableCollection<DIYGrid.ColumnBase> core_columns => _columns;
@@ -419,6 +438,14 @@ namespace mbts.spark {
public bool can_attach_selected_orders => _can_attach_selected_orders.Value;
public bool can_detach_selected_orders => _can_detach_selected_orders.Value;
public Plurality security_plurality { get; private set; }
public bool all_rows_selected {
get => _all_rows_selected;
set {
Set(ref _all_rows_selected, value);
request_selection?.Invoke((_row_collection, _all_rows_selected));
}
}
public LadderVMContainer ladder_vm { get; }
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
@@ -426,23 +453,60 @@ namespace mbts.spark {
public void row_selection_changed(SelectionChangedEventArgs e) {
try {
using (_selected_params_mon.begin_udpate()) {
using (_selected_params_mon.defer_updates())
using (_selected_security_mon.defer_updates()) {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
row_vm.is_selected = false;
_selected_params_mon.on_row_unselected(row_vm);
_selected_security_mon.remove(row_vm.security);
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
_attachable_selected_orders.Remove(row_vm.order_key);
_detachable_selected_orders.Remove(row_vm.order_key);
}
}
if (e.AddedItems != null) {
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
var root = _get_root(row_vm.order_tuple);
row_vm.is_selected = true;
_selected_params_mon.on_row_selected(row_vm);
_selected_security_mon.add(row_vm.security);
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
row_vm.updated += on_updated;
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
}
_selected_params_mon.Add(root);
if (row_vm.is_attachable)
_attachable_selected_orders.Add(row_vm.order_key);
if (row_vm.is_detachable)
_detachable_selected_orders.Add(row_vm.order_key);
void on_updated((OrderSecurity oldval, OrderSecurity newval) change) {
if (!row_vm.is_selected) {
// self destruct
if (_on_updated_single_callback_gatekeeper.TryGetValue(row_vm.order_key, out var unsubscribe_updated_disposable)) {
unsubscribe_updated_disposable.Dispose();
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
}
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
_selected_security_mon.remove(row_vm.security);
return;
}
var new_root = _get_root(change.newval);
if (!new_root.Equals(root)) {
using (_selected_params_mon.defer_updates()) {
// unselect and select in one smooth motion via on_row_updated
// because we thought there may be gremlins afoot when doing
// on_row_unselected and then on_row_selected
_selected_params_mon.Remove(root);
_selected_params_mon.Add(new_root);
root = new_root;
}
}
if (change.oldval.security?.Id != change.newval.security?.Id) {
using (_selected_security_mon.defer_updates()) {
_selected_security_mon.remove(change.oldval.security);
_selected_security_mon.add(change.newval.security);
}
}
}
}
}
RowSelectionHelper.apply_changes(selected_rows, e);
@@ -520,11 +584,11 @@ namespace mbts.spark {
if (!new_sort.Equals(old_sort)) {
/// Both filter and sort have changed, use <see cref="OperableHierarchicalCollection{TRow, T, TKey}.defer_refresh"/> to avoid syncing twice.
using (_row_collection.defer_refresh()) {
_row_collection.filter = new_filter.build_predicate();
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
_row_collection.comparer = new_sort.comparer;
}
} else {
_row_collection.filter = new_filter.build_predicate();
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
}
} else if (!new_sort.Equals(old_sort))
_row_collection.comparer = new_sort.comparer;
@@ -544,35 +608,28 @@ namespace mbts.spark {
public void begin_attach_algos() => is_attaching_orders = true;
public bool try_get_parent(OrderBookRowVM row, out OrderSecurity parent) =>
_cached_orders.TryGetValue(row.parent_order_key, out parent);
public OrderRecordImmutable get_root(OrderRecordImmutable r) {
while (r.ParentKey != 0 && _cached_orders.TryGetValue(r.ParentKey, out var parent_osam)) {
r = parent_osam.order;
internal void detach_orders(OrderBookRowVM row) {
if (row.is_selected) {
_detach_orders_async(selected_rows).ignore();
} else {
_detach_orders_async(new[] { row }).ignore();
}
return r;
}
internal void detach_orders(OrderBookRowVM order) =>
_detach_orders_async(new[] { order }).ignore();
public OrderSecurity get_ordersecurityalgometa(OrderRecordImmutable order) {
_cached_orders.TryGetValue(order.Key, out var osam);
return osam;
}
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) => _model.factory.get_metadata(order_type);
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
internal void show_order_modify(OrderBookRowVM row) =>
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
internal void cancel_order(OrderBookRowVM row) =>
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
internal void cancel_order(OrderBookRowVM row) {
if (row.is_selected) {
_bus.post(SparkTags.CancelOrders, selected_rows.Select(r => r.order_key).ToList());
} else {
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
}
}
internal void modify_order_quantity(OrderBookRowVM row, uint size) =>
_bus.post(SparkTags.UpdateOrder, (row.order_key, row.price, size));
@@ -591,11 +648,17 @@ namespace mbts.spark {
}
}
OrderSecurity _get_root(OrderSecurity os) {
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
os = pos;
return os;
}
void _on_filter_changed() {
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
if (!fd.Equals(_cached_filter_data)) {
_cached_filter_data = fd;
_row_collection.filter = fd.build_predicate();
_row_collection.filter = fd.build_predicate(_model.dynamic_helper.dynamic_type_index);
}
}
@@ -611,6 +674,7 @@ namespace mbts.spark {
}
}
// Note: modifications are performed on the selected orders' ROOT orders.
async Task _modify_selected_orders_async() {
double? price = DoubleHelper.nan_nullable(order_mod_vm.price);
uint? size = order_mod_vm.size;
@@ -623,17 +687,17 @@ namespace mbts.spark {
// their values will be cleared (in the param grid gui), and the modifications will not be applied.
// FYI this behavior is implemented to support 'partial edits.' (e.g., changing the 'price' for multiple orders that do not have the same 'size').
// ----
var update_params = selected_rows.Select(update_params_factory).ToList();
var update_params = selected_rows.Select(r => update_params_factory(_get_root(r.order_tuple))).ToList();
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurity order_tuple) {
var mod_algo = order_tuple.order.Def.CustomDef.CloneToMutable();
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
} else {
mod_algo_vm.assign_to(mod_algo);
}
var mod_algoi = new CustomOrderDefinitionImmutable(mod_algo);
return (row.order, OrderDataHelper.EqualsHelpers.equals(row.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
return (order_tuple.order, OrderDataHelper.EqualsHelpers.equals(order_tuple.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
}
} else await Task.WhenAll(selected_rows.Select(r => _model.update_order(r.order, price)));
}
@@ -676,52 +740,6 @@ namespace mbts.spark {
}
}
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
Observable.Create<ISet<uint>>(o => {
bool waiting_for_mouse_up = false;
var sub_container = new DisposableContainer();
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
var selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
if (waiting_for_mouse_up)
return;
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
foreach (OrderBookRowVM row in e.NewItems)
selected_keys = selected_keys.Add(row.order_key);
break;
case NotifyCollectionChangedAction.Remove:
foreach (OrderBookRowVM row in e.OldItems)
selected_keys = selected_keys.Remove(row.order_key);
break;
case NotifyCollectionChangedAction.Reset:
selected_keys = ImmutableHashSet<uint>.Empty;
break;
default: return;
}
if (Mouse.LeftButton == MouseButtonState.Pressed) {
waiting_for_mouse_up = true;
wait_for_mouse_up();
async void wait_for_mouse_up() {
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
waiting_for_mouse_up = false;
}
} else {
o.OnNext(selected_keys);
}
}
});
public class OrderParamsVM : DisposableViewModelBase {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly OrderBookVM _owner;
@@ -732,8 +750,6 @@ namespace mbts.spark {
AlgoParamPermissions
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
HashSet<uint> _selected_order_tree = new HashSet<uint>();
LadderVM _ladder_vm;
SecurityMD? _security_md;
public OrderParamsVM(OrderBookVM owner)
@@ -784,13 +800,7 @@ 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),
owner._subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
_selected_order_tree = new HashSet<uint>(_owner.selected_hierarchical_rows.Select(r => r.order_key));
_reset_ladder_vm();
})) {
() => Disposable.dispose(ref _ladder_vm)
};
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
// payup methods
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
void payup_ask(int ticks) => payup(bbo.ask, ticks);
@@ -818,8 +828,7 @@ namespace mbts.spark {
get => _security_md;
set {
if (Set(ref _security_md, value)) {
_reset_ladder_vm();
RaisePropertyChanged(nameof(security), nameof(spec), nameof(ladder_vm));
RaisePropertyChanged(nameof(security), nameof(spec));
bbo.security_md = value;
_payup_bid_cmd.RaiseCanExecuteChanged();
_payup_ask_cmd.RaiseCanExecuteChanged();
@@ -843,7 +852,6 @@ namespace mbts.spark {
}
public AlgoDefinitionVM algo_vm { get; }
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
public LadderVM ladder_vm => _ladder_vm;
public BBOVM bbo { get; }
public ICommand send_order { get; }
public ICommand payup_bid_cmd => _payup_bid_cmd;
@@ -878,18 +886,6 @@ namespace mbts.spark {
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
void _reset_ladder_vm() {
if (_ladder_vm == null && !_security_md.HasValue)
return;
Disposable.replace(ref _ladder_vm,
_security_md.HasValue ?
LadderVM.create_readonly(
_owner._bus,
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
null);
RaisePropertyChanged(nameof(ladder_vm));
}
void _assign_param_permissions<T>(IAlgoParamVM<T> param, ref AlgoParamPermissions permissions_backing, AlgoParamPermissions permissions) {
if (permissions_backing != permissions) {
permissions_backing = permissions;
@@ -908,8 +904,8 @@ namespace mbts.spark {
this.column_filters = column_filters;
}
public Func<OrderSecurity, bool> build_predicate() {
var pred = column_filters.build_predicate();
public Func<OrderSecurity, bool> build_predicate(IMaskIndexInt32<string> dynamic_type_index) {
var pred = column_filters.build_predicate(dynamic_type_index);
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
if (pred == null) {
pred = tree_disp_pred;
@@ -936,132 +932,6 @@ namespace mbts.spark {
EqualityComparer.aggregate_hash((int)algo_tree_display_mode, column_filters?.GetHashCode() ?? 0);
}
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class SelectedOrderParamsMon {
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
ask => ask,
EqualityComparer<AlgoSecurityKey>.Default,
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
readonly DynamicOrderHelper _dyn_helper;
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
public SelectedOrderParamsMon(DynamicOrderHelper dyn_helper) {
_dyn_helper = dyn_helper;
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
event Action _security_md_map_changed;
public SecurityImmutable? security => _security_mon.common_value;
public SecurityMD? security_md => security is SecurityImmutable sec && _security_md_map.TryGetValue(sec.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityMD?> subscribe_security_md() =>
Observable.Create<SecurityMD?>(o => {
SecurityMD? last_value = security_md;
o.OnNext(last_value);
_security_mon.common_value_changed += on_common_sec_changed;
_security_md_map_changed += refresh;
return Disposable.Create(() => {
_security_mon.common_value_changed -= on_common_sec_changed;
_security_md_map_changed -= refresh;
});
void on_common_sec_changed(SecurityImmutable? _) => refresh();
void refresh() {
var curr = security_md;
if (curr != last_value)
o.OnNext(last_value = curr);
}
});
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable begin_udpate() =>
_group_mon.defer_updates();
public void on_row_selected(OrderBookRowVM row) {
_group_mon.add(row.order_tuple);
row.updated += _on_row_updated;
}
public void on_row_unselected(OrderBookRowVM row) {
_group_mon.remove(row.order_tuple);
row.updated -= _on_row_updated;
}
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
_security_md_map = security_md_map;
_security_md_map_changed?.Invoke();
}
void _on_row_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
using (begin_udpate()) {
_group_mon.remove(ch.oldval);
_group_mon.add(ch.newval);
}
}
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
SecurityMD? _get_sec_md(OrderSecurity osam) =>
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
struct AlgoSecurityKey {
public readonly CustomOrderDefinitionImmutable algo;
public readonly SecurityImmutable? security;
public readonly AlgoStructureKey key;
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
this.algo = algo;
this.security = security;
this.key = key;
}
public bool Equals(AlgoSecurityKey other) =>
security == other.security &&
key == other.key;
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
public override int GetHashCode() => key.GetHashCode();
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
}
}
class RightPanelModel {
OrderBookRightPanelSettings.ViewMode _mode;
OrderBookRightPanelSettings.Components _all_selected, _hidden = OrderBookRightPanelSettings.Components.None;
@@ -1124,11 +994,13 @@ namespace mbts.spark {
var join = config.Configuration.get<bts.config.JoinAttachUIOptions>();
var lean = config.Configuration.get<bts.config.LeanAttachUIOptions>();
var top = config.Configuration.get<bts.config.TopAttachUIOptions>();
var offset = config.Configuration.get<bts.config.OffsetAttachUIOptions>();
_algo_type_conf = new Dictionary<CustomOrderDefinition.CustomOneofCase, (bool, bool)> {
{ CustomOrderDefinition.CustomOneofCase.Cover, (cover.show_price, cover.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Join, (join.show_price, join.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Lean, (lean.show_price, lean.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) }
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Offset, (offset.show_price, offset.show_quantity) }
};
}
@@ -7,8 +7,8 @@ using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -17,10 +17,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.orderbook.OrderBookGridSettings>;
namespace mbts.spark {
internal class OrderBookWindow : SparkWindow<OrderBookStartupData>, ISharedWindow {
@@ -65,7 +63,7 @@ namespace mbts.spark {
}
public void thread_init() {
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model, _settings_manager);
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
@@ -161,7 +159,7 @@ namespace mbts.spark {
toggle_expand_all_command.group = CommandGroups.Auxiliary;
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
var gs_vm = new ShellSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
_settings_manager,
us => us.order_book_grid,
UserSettings.FileExtensions.GRID,
@@ -171,14 +169,11 @@ namespace mbts.spark {
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.general = s.general.with(_vm.core_columns.Select(c => c.create()));
return _vm.grid_settings_manager.create_vm(s, this.observe_title());
return _vm.grid_settings_manager.create_vm(s);
}
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { DataContext = gs_vm };
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(gs_vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
});
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
@@ -1,154 +1,155 @@
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public UIntMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public UIntMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.utility;
using ProtoBuf;
using System;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
//[ProtoMember(8)]
//public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(23)]
public AlgoID[] order_types;
[ProtoMember(24)]
public AlgoID[] root_algo_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public UIntMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public UIntMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs) {
var pb = new ColumnPredicateBuilder<OrderSecurity>();
// security
pb.add_security_criteria(fs.security_criteria, o => o.security);
// regex
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
// time
pb.add_time_filter(fs.time_created, o => o.order.created());
pb.add_time_filter(fs.last_change, o => o.order.last_change());
// discrete values
pb.add_discrete_values(fs.users, o => o.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.account());
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.status());
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.quantity());
pb.add_numeric(fs.price, o => o.order.price());
pb.add_numeric(fs.qty_filled, o => o.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.last_price());
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
[ProtoMember(25)]
public UIntMinMaxState piq;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
piq.safe_equals(other.piq);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols),
piq?.GetHashCode() ?? 0);
}
static class FilterStatesExtensions {
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs, IMaskIndexInt32<string> dynamic_type_index) {
var pb = new ColumnPredicateBuilder<OrderSecurity>();
// security
pb.add_security_criteria(fs.security_criteria, o => o.security);
// regex
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
// time
pb.add_time_filter(fs.time_created, o => o.order.created());
pb.add_time_filter(fs.last_change, o => o.order.last_change());
// discrete values
pb.add_discrete_values(fs.users, o => o.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.account());
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.Def.CustomDef));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key, dynamic_type_index));
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.status());
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.quantity());
pb.add_numeric(fs.price, o => o.order.price());
pb.add_numeric(fs.qty_filled, o => o.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.last_price());
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
@@ -43,7 +43,9 @@ namespace bts.shell.orderbook {
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations);
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
algo_tree_display_mode = algo_tree_display_mode
};
public bool Equals(OrderBookGridSettings other) =>
other != null &&
@@ -14,16 +14,12 @@ using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace bts.shell.orderbook {
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
AlgoTreeDisplayMode _algo_tree_display_mode;
public OrderBookGridSettingsManager(
IObservable<string> owner_title,
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
static OrderBookGridSettingsManager() {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
// TODO: Remove the immutable font-weight flags when cell animation is ready
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
@@ -31,10 +27,20 @@ namespace bts.shell.orderbook {
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
}
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
hard_column_flags = col_settings_flags;
}
public OrderBookGridSettingsManager(
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
owner_title, bus, model, rows, columns,
bus, model, rows, columns,
() => new OrderRecordTuplePredicate(),
() => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
@@ -53,7 +59,7 @@ namespace bts.shell.orderbook {
p => p.create_notification_predicate(notification_service.data_cache),
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
col_settings_flags);
hard_column_flags);
_core_man_as_observer = core_manager;
}
@@ -83,8 +89,8 @@ namespace bts.shell.orderbook {
return gs;
}
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs, IObservable<string> owner_title) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general, owner_title)) {
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
algo_tree_display_mode = gs.algo_tree_display_mode
};
@@ -137,12 +137,13 @@ namespace bts.shell.orderbook {
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criterion);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion)) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
static class DefaultColumns {
// standard
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
@@ -167,6 +168,13 @@ namespace bts.shell.orderbook {
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
// editable
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Size, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
@@ -174,7 +182,7 @@ namespace bts.shell.orderbook {
}
}
public static IEnumerable<GridColumnSettings> all {
public static IEnumerable<GridColumnSettings> standard {
get {
yield return security;
yield return symbol;
@@ -203,13 +211,26 @@ namespace bts.shell.orderbook {
}
}
public static IEnumerable<GridColumnSettings> editable {
get {
yield return cancel;
yield return update;
yield return edit_quantity;
yield return edit_price;
yield return commit;
yield return attach_detach;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in all) {
foreach (var col_setting in editable)
yield return col_setting;
foreach (var col_setting in standard) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
@@ -17,7 +17,8 @@ namespace bts.shell.orderbook {
this.core_settings_vm = core_settings_vm;
public OrderBookSettingsVM(
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
IBus bus,
OrdersModel orders_model,
Func<OrderRecordTuplePredicate> predicate_data_factory,
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
@@ -25,7 +26,7 @@ namespace bts.shell.orderbook {
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
owner_title, bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
}
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
@@ -108,4 +108,9 @@
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
</c:DIYGrid.InputBindings>
<c:DIYGrid.RowStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
</Style>
</c:DIYGrid.RowStyle>
</c:DIYGrid>
@@ -50,7 +50,8 @@ namespace mbts.spark {
Update = 1 << 27,
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30
Piq = 1 << 30,
Check = 1 << 31
}
public OrderBookGridView() {
@@ -554,7 +554,7 @@
BorderThickness="0,1,0,0"
ResizeBehavior="PreviousAndNext"
/>
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding order_mod_vm.ladder_vm}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding ladder_vm.value}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
<GridSplitter
x:Name="ladder_splitter"
Grid.Column="1"
@@ -117,12 +117,12 @@ namespace bts.shell {
var temp_sound_triggers = _sound_trigger_refs;
foreach (var kv in temp_sound_triggers) {
if (kv.Key.trigger(value)) {
sounds.AddRange(kv.Value.keys);
sounds.AddRange(kv.Value.Keys);
}
}
var temp_toast_triggers = _toast_trigger_refs;
foreach (var krc in temp_toast_triggers) {
if (krc.key.trigger(value) is IToastNotification tn && tn.pass)
foreach (var kv in temp_toast_triggers) {
if (kv.Key.trigger(value) is IToastNotification tn && tn.pass)
toast.Add(tn);
}
if (sounds.Count > 0 || toast.Count > 0)
+19 -12
View File
@@ -168,17 +168,14 @@ namespace mbts.spark.utilities {
// this will be the tag 50 on CME orders
// current user might be okay, but we might also want to get it
// from the store that serves the risk admin gui
ManualOrderIndicator = ManualOrderIndicator.Yes
ManualOrderIndicator = ManualOrderIndicator.Yes,
User = _admin_model.user.name,
UserId = _admin_model.user.id
};
if (account.HasValue) {
rv.Account = account.Value.name;
rv.AccountId = account.Value.id;
}
var user = _admin_model.get_current_user();
if (user.HasValue) {
rv.User = user.Value.name;
rv.UserId = user.Value.id;
}
return rv;
}
@@ -516,7 +513,7 @@ namespace mbts.spark.utilities {
}
}
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type) {
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type, bool fit_all_to_spreader = false) {
var mdef = defi.CloneToMutable();
switch (security_type) {
case Security.ContractOneofCase.Synthetic:
@@ -542,7 +539,8 @@ namespace mbts.spark.utilities {
case CustomOrderDefinition.CustomOneofCase.Stop:
case CustomOrderDefinition.CustomOneofCase.StopLimit:
case CustomOrderDefinition.CustomOneofCase.Market:
def = null;
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg:
def = fit_all_to_spreader ? new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() } : null;
break;
default:
var desc_data = _algo_type_case_descriptor_data[def.CustomCase];
@@ -604,7 +602,8 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset
},
// The following spreader types are not yet ready for production use:
// CustomOrderDefinition.CustomOneofCase.Dynamic,
@@ -621,7 +620,15 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset
},
outright_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
CustomOrderDefinition.CustomOneofCase.Limit,
CustomOrderDefinition.CustomOneofCase.Stop,
CustomOrderDefinition.CustomOneofCase.StopLimit,
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
};
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
@@ -710,7 +717,7 @@ namespace mbts.spark.utilities {
!b.HasChild);
public static bool top_equals(TopOrderDefinitionImmutable a, TopOrderDefinitionImmutable b) =>
a.WatchLevelTickOffset == b.WatchLevelTickOffset &&
a.TicksOff == b.TicksOff &&
(a.HasTopThreshold ?
b.HasTopThreshold && quantity_measure_equals(a.TopThreshold, b.TopThreshold) :
!b.HasTopThreshold) &&
@@ -901,7 +908,7 @@ namespace mbts.spark.utilities {
add_component(_get_hash(_get_tick_child(tick)));
break;
case CustomOrderDefinition.CustomOneofCase.Top:
add_component(def.Top.WatchLevelTickOffset.GetHashCode());
add_component(def.Top.TicksOff.GetHashCode());
add_component(def.Top.HasTopThreshold ? def.Top.TopThreshold.GetHashCode() : 0);
add_component(_get_top_child(def.Top).GetHashCode());
break;
@@ -0,0 +1,105 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class OrderParamsAggregator {
readonly TimeSpan _throttle;
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
ask => ask,
EqualityComparer<AlgoSecurityKey>.Default,
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
readonly DynamicOrderHelper _dyn_helper;
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle) {
_dyn_helper = dyn_helper;
_throttle = throttle;
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
}
public SecurityImmutable? security => _security_mon.common_value;
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? size => _size_mon.common_value_nullable();
public AccountTuple? account => _account_mon.common_value_nullable();
public bool all_active => _all_active_mon.common_value;
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
public IDisposable defer_updates() =>
_group_mon.defer_updates();
event Action<TableUpdate<OrderSecurity, uint>> _updated;
public void Update(TableUpdate<OrderSecurity, uint> table) {
_updated?.Invoke(table);
}
public void Add(OrderSecurity order_tuple) =>
_group_mon.add(order_tuple);
public void Remove(OrderSecurity order_tuple) =>
_group_mon.remove(order_tuple);
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
struct AlgoSecurityKey {
public readonly CustomOrderDefinitionImmutable algo;
public readonly SecurityImmutable? security;
public readonly AlgoStructureKey key;
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
this.algo = algo;
this.security = security;
this.key = key;
}
public bool Equals(AlgoSecurityKey other) =>
security == other.security &&
key == other.key;
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
public override int GetHashCode() => key.GetHashCode();
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
}
}
}
@@ -26,27 +26,26 @@ using System.Windows.Input;
namespace mbts.spark {
class InlineOrderProperties {
public readonly InlineUserProperty<AccountTuple> account;
public readonly InlineUserProperty<uint> account;
public readonly InlineUserProperty<TimeInForce> time_in_force;
public readonly InlineUserProperty<CustomOrderDefinition> order_type_def;
public readonly InlineUserProperty<AlgoID> algo;
public InlineOrderProperties(OrdersModel model, LadderSettings settings) : this(
new InlineUserProperty<AccountTuple>(),
new InlineUserProperty<uint>(settings.default_account, settings.default_account != 0),
new InlineUserProperty<TimeInForce>(settings.default_time_in_force, true),
new InlineUserProperty<CustomOrderDefinition>(model.factory.custom_order(settings.default_order_type), true)) { }
new InlineUserProperty<AlgoID>(settings.default_algo, true)) { }
public InlineOrderProperties(OrdersModel model, OrderDefinitionImmutable def) : this(
new InlineUserProperty<AccountTuple>(),
new InlineUserProperty<TimeInForce>(def.TimeInForce, true),
new InlineUserProperty<CustomOrderDefinition>(def.CustomDef.CloneToMutable(), true)) { }
new InlineUserProperty<uint>(def.AccountId, def.AccountId != 0),
new InlineUserProperty<TimeInForce>(def.TimeInForce, true)) { }
public InlineOrderProperties(
InlineUserProperty<AccountTuple> account=null,
InlineUserProperty<uint> account=null,
InlineUserProperty<TimeInForce> time_in_force=null,
InlineUserProperty<CustomOrderDefinition> order_type_def=null) {
this.account = account ?? new InlineUserProperty<AccountTuple>();
InlineUserProperty<AlgoID> algo = null) {
this.account = account ?? new InlineUserProperty<uint>();
this.time_in_force = time_in_force ?? new InlineUserProperty<TimeInForce>();
this.order_type_def = order_type_def ?? new InlineUserProperty<CustomOrderDefinition>();
this.algo = algo ?? new InlineUserProperty<AlgoID>();
}
}
@@ -102,14 +101,28 @@ namespace mbts.spark {
_favorite_algo_definitions_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionVM.is_favorite));
_favorite_algo_definitions_view.IsLiveFiltering = true;
favorite_algo_definitions_view.Filter = d => d.is_favorite;
//_accounts
var inline_account_src = order_properties.AggregateLatest(
model.factory.subscribe_accounts(),
(last, change, cache) => convert_inline_account(change.New.account, cache),
(last, props, change) => convert_inline_account(props.account, change.New, false),
() => new InlineUserProperty<AccountTuple>(),
new InlineOrderProperties(),
DictionaryHelper.empty<uint, string>());
// Note that with the .Where on the account_tuples, we will ignore any
// updates for which all accounts have been removed.
// This was a safer change than changing SelectorProperty's IEnumberable.OnNext
// so that it would not reset the selected item on server reboots; i.e. this
// change only affects this IObservable.
_accounts = backing(
nameof(selected_account),
model.factory.subscribe_account_tuples(),
order_properties.Select(ops => ops.account),
model.factory.subscribe_account_tuples().Where(items => items.Any()),
inline_account_src,
a => new AccountTuple?(a),
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
EqualityComparer<AccountTuple?>.Default,
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
// time in force
_time_in_force = backing(
nameof(selected_time_in_force),
// remember, both observables must fire once to start the CombineLatest sequence
@@ -164,8 +177,12 @@ namespace mbts.spark {
}, out IObserver<bool> revert_algo_params_observer);
var algo_params_dirty_mon = new ReactiveThresholdMonitor<AlgoParamRowVM>(r => r.param.subscribe_is_dirty());
var nullable_security_md_source = security_md_source.Select(smd => new SecurityMD?(smd)).RefCountReplay(1);
var inline_algo_relay = new SelectionModelInlinePropRelay<AlgoDefinitionVM, AlgoDefinitionVM, AlgoID>(_selected_algo_definition, vm => AlgoID.from_algo_key(vm.key, _model.dynamic_helper.dynamic_type_index));
//
_dispose = new CompositeDisposable(
order_properties.OnDispatcher().Subscribe(props => {
inline_algo_relay.on_inline_prop(props.algo);
}),
algo_params_dirty_mon.Subscribe(set_default_algo_params_observer, revert_algo_params_observer),
ObservableAutoWrapper.connect_item_hooks(_algo_param_rows, algo_params_dirty_mon.join),
_selected_algo_definition.subscribe_changed(_selected_algo_definition_Changed),
@@ -226,6 +243,7 @@ namespace mbts.spark {
Disposable.dispose(ref _algo_param_rows_hook);
}
};
// local methods
async Task save_template_async(AlgoDefinitionTemplateVM template) {
using (_preserve_selection_by_index())
await AlgoPersistenceHelper.persist_template(model, algo_definitions, template);
@@ -250,6 +268,19 @@ namespace mbts.spark {
break;
}
}
//
// Regarding the 3rd parameter, force_override_from_inline_property:
// When using an InlineUserProperty, in the context of assigning a default vs selected item,
// there is somewhat of a conflict between the IObserver<InlineUserProperty<TItem>> and the
// IObserver<IEnumerable<TItem>> OnNext methods. This 3rd paramter provides yet another override
// to say if the force_override should come from the inline property or if the force_override should
// be disbled, by way of the immediate return in ViewModelBase._try_auto_select when force_override
// is false. Our intent is then accomplished because we are no longer attempting to set a default/
// selected item when the account_tuples potentially changes
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map, bool force_override_from_inline_property = true) =>
account_id.has_value && account_map.TryGetValue(account_id.value, out string name) ?
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), force_override_from_inline_property ? account_id.force_override : false) :
new InlineUserProperty<AccountTuple>();
}
IDisposable _template_collection_sync;
@@ -5,13 +5,16 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
readonly Property<uint?> _ticks_off;
readonly Property<QuantityMeasureImmutable?> _top_threshold;
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_ticks_off = add_nullable_property<uint>(nameof(ticks_off), TopDef.TicksOffFieldNumber);
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
}
public uint? ticks_off { get => _ticks_off.Value; set => _ticks_off.set(value); }
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
}
}
+2 -62
View File
@@ -42,6 +42,7 @@
<PackageReference Include="System.Reactive" Version="3.1.1" />
<PackageReference Include="protobuf-net" Version="2.3.2" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="WPFTextBoxAutoComplete" Version="1.0.5" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml" Generator="MSBuild:Compile" />
@@ -52,12 +53,6 @@
<Resource Include="**\*.png" />
<ConfigDefaults Include="../../lib/config/defaults/*.conf" />
</ItemGroup>
<ItemGroup>
<None Remove="Sounds\game_sound_correct.wav" />
<None Remove="Sounds\whistle.wav" />
<None Remove="view\Controls\TickUpDown\TickUpDownNullableUIntCommit.xaml" />
<None Remove="view\Shell\Workspaces\WorkspacesView.xaml" />
</ItemGroup>
<ItemGroup>
<Resource Remove="logo.ico" />
</ItemGroup>
@@ -68,60 +63,5 @@
<EmbeddedResource Include="Sounds\game_sound_correct.wav" />
<EmbeddedResource Include="Sounds\whistle.wav" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controls\SecurityPicker\" />
<Folder Include="view\Extensions\ItemsControl\ObservableItemsSource\" />
</ItemGroup>
<ItemGroup>
<Compile Update="view\Controls\TickUpDown\TickUpDownNullableUIntCommit.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="view\Shell\Workspaces\WorkspacesDialogView.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorGridView\AlgoEditorGridView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderSettingsView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\LadderView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\Ladder\View\MiniLadderView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\MiniOrderBook\MiniOrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\MiniOrderBook\OrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookGridView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookToolBar.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderBookView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Modules\Trading\OrderBook\View\OrderParamsPanel.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
</ItemGroup>
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
</Target>
<Target Name="diagnostic" AfterTargets="PreBuildEvent" />
</Project>
@@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using mbts.ui.shell;
using System;
using System.Windows.Threading;
namespace mbts.ui.tests
{
class TestShellWindow : IShellWindow
{
namespace mbts.ui.tests {
class TestShellWindow : IShellWindow {
#region IShellWindow boilerplate
public string Title { get; set; }
public event Action TitleChanged = () => { };
@@ -20,6 +15,7 @@ namespace mbts.ui.tests
#endregion
public Dispatcher Dispatcher { get; private set; }
public ShellWindowUI GetContent() {
this.Dispatcher = Dispatcher.CurrentDispatcher;
var content = new System.Windows.Forms.Button() { Text = "Click Me!", Width = 100, Height = 100 };
@@ -27,10 +23,12 @@ namespace mbts.ui.tests
}
public bool IsDisposed { get; set; }
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
public void Dispose() { IsDisposed = true; }
}
class TestSlowWindow : TestShellWindow, ISlowWindow {
public void thread_init() { }
}
}
}
+1
View File
@@ -14,6 +14,7 @@
<ResourceDictionary Source="/view/Resources/Buttons.xaml" />
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
<ResourceDictionary Source="/view/Resources/MenuItems.xaml" />
<ResourceDictionary Source="/view/Controls/ToolBar/ToolBar.xaml" />
<ResourceDictionary Source="/view/Controls/EditableTextBlock/EditableTextBlock.xaml"/>
<ResourceDictionary Source="/view/Controls/DragDropItemsControl/DragDropItemsControl.xaml" />
<ResourceDictionary Source="/view/Controls/DragDropItemsControl/DraggableContentControl.xaml" />
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.collections {
class ItemSelector<T> : ViewModelBase {
@@ -128,7 +129,6 @@ namespace mbts.collections {
readonly Property<T> _value;
readonly Func<T, TKey> _key_selector;
readonly string _prop_name;
TKey? _desired_key;
public KeyedItemSelector(IRaiseNotifyPropertyChanged owner, string prop_name, IObservableCollection<T> items, Func<T, TKey> key) {
_owner = owner;
@@ -143,33 +143,42 @@ namespace mbts.collections {
get => _value.Value;
set {
_value.set(value);
if (_desired_key.HasValue) {
if (desired_key.HasValue) {
items.CollectionChanged -= _on_items_CollectionChanged;
_desired_key = null;
desired_key = null;
}
_owner.raise_notify_property_changed(_prop_name);
}
}
public TKey? desired_key { get; private set; }
public bool try_select(TKey key) {
if (items.TryGetFirst(i => _key_selector(i).Equals(key), out T result)) {
value = result;
return true;
} else {
if (!_desired_key.HasValue) {
if (!desired_key.HasValue) {
items.CollectionChanged += _on_items_CollectionChanged;
}
_desired_key = key;
desired_key = key;
return false;
}
}
public IObservable<T> subscribe_value() =>
Observable.Create<T>(o => {
o.OnNext(value);
_value.Changed += on_changed;
return Disposable.Create(() => _value.Changed -= on_changed);
void on_changed(T oldval, T newval) => o.OnNext(newval);
});
void _on_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
foreach (T i in e.NewItems)
if (_key_selector(i).Equals(_desired_key.Value)) {
if (_key_selector(i).Equals(desired_key.Value)) {
value = i;
return;
}
@@ -128,6 +128,15 @@ namespace mbts.collections {
}
}
public void SyncWithDictionary(IReadOnlyDictionary<TKey, T> dictionary) {
foreach (var k in _map.Keys.Except(dictionary.Keys).ToList())
Remove(k);
foreach (var kv in dictionary) {
if (!ContainsKey(kv.Key))
Add(kv.Value);
}
}
public void SyncWithDictionary<TModel>(IReadOnlyDictionary<TKey, TModel> dictionary, Func<TModel, T> factory) {
foreach (var k in _map.Keys.Except(dictionary.Keys).ToList())
Remove(k);
@@ -206,6 +215,11 @@ namespace mbts.collections {
}
static class ObservableKeyedCollectionExtensions {
public static void AddRange<T, TKey>(this ObservableKeyedCollection<T, TKey> collection, IEnumerable<T> values) {
foreach (var v in values)
collection.Add(v);
}
public static IReadOnlyObservableKeyedCollection<TResult, TKey> WrapSelector<T, TKey, TResult>(this IReadOnlyObservableKeyedCollection<T, TKey> source, Func<T, TResult> selector) =>
new SelectorWrapper<T, TKey, TResult>(source, selector);
@@ -1,17 +1,17 @@
using System;
using bts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace mbts.collections {
interface IKeyRefCount<T> {
T key { get; }
int count { get; }
}
class RefCountMap<TKey> : IDictionary<TKey, int>, IReadOnlyDictionary<TKey, int> {
static readonly Func<KeyRefCount, int> _count_selector = krc => krc.count;
class RefCountMap<TKey> : IReadOnlyCollection<IKeyRefCount<TKey>>, ICollection {
readonly IEqualityComparer<TKey> _key_comparer;
readonly Dictionary<TKey, KeyRefCount> _dictionary;
readonly IReadOnlyCollection<int> _ro_values;
readonly ICollection<int> _values;
public RefCountMap()
: this(EqualityComparer<TKey>.Default) { }
@@ -22,31 +22,54 @@ namespace mbts.collections {
RefCountMap(Dictionary<TKey, KeyRefCount> dictionary, IEqualityComparer<TKey> key_comparer) {
_dictionary = dictionary;
_key_comparer = key_comparer;
CollectionHelper.wrap_selector(dictionary.Values, _count_selector, out _ro_values, out _values);
}
public int this[TKey key] {
get => _dictionary[key].count;
set {
if (!_dictionary.TryGetValue(key, out var krc))
_dictionary[key] = krc = new KeyRefCount(key);
krc.count = value;
}
}
public int Count => _dictionary.Count;
public object SyncRoot => null;
public IReadOnlyCollection<TKey> Keys => _dictionary.Keys;
public IReadOnlyCollection<int> Values => _ro_values;
public bool IsReadOnly => false;
public bool IsSynchronized => false;
public int this[TKey key] => _dictionary[key].count;
public IReadOnlyCollection<TKey> keys => _dictionary.Keys;
public bool contains_key(TKey key) => _dictionary.ContainsKey(key);
public object SyncRoot => null;
// explicit
ICollection<TKey> IDictionary<TKey, int>.Keys => _dictionary.Keys;
ICollection<int> IDictionary<TKey, int>.Values => _values;
IEnumerable<TKey> IReadOnlyDictionary<TKey, int>.Keys => _dictionary.Keys;
IEnumerable<int> IReadOnlyDictionary<TKey, int>.Values => _values;
public void CopyTo(Array array, int index) =>
((ICollection)_dictionary).CopyTo(array, index);
public void Add(TKey key, int value) =>
_dictionary.Add(key, new KeyRefCount(key) { count = value });
public IEnumerator<IKeyRefCount<TKey>> GetEnumerator() =>
_dictionary.Values.GetEnumerator();
public void Add(KeyValuePair<TKey, int> item) =>
_dictionary.Add(item.Key, new KeyRefCount(item.Key) { count = item.Value });
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
public void Clear() => _dictionary.Clear();
public void increment(TKey key) =>
increment(key, 1);
public RefCountMap<TKey> clone() =>
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
public void increment(TKey key, int count) {
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
else krc.count += count;
public bool Contains(KeyValuePair<TKey, int> item) =>
_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value;
public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
public void CopyTo(KeyValuePair<TKey, int>[] array, int arrayIndex) {
foreach (var kv in _dictionary)
array[arrayIndex++] = new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
}
public void CopyTo(Array array, int index) {
foreach (var kv in _dictionary)
array.SetValue(new KeyValuePair<TKey, int>(kv.Key, kv.Value.count), index++);
}
public bool decrement(TKey key) =>
@@ -66,38 +89,41 @@ namespace mbts.collections {
return false;
}
public bool remove(TKey key) =>
_dictionary.Remove(key);
public IEnumerator<KeyValuePair<TKey, int>> GetEnumerator() {
foreach (var kv in _dictionary)
yield return new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
}
public void clear() => _dictionary.Clear();
public void increment(TKey key) =>
increment(key, 1);
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
if (try_lookup(key, out key_ref_count)) {
remove(key);
public void increment(TKey key, int count) {
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
else krc.count += count;
}
public bool Remove(TKey key) => _dictionary.Remove(key);
public bool Remove(KeyValuePair<TKey, int> item) {
if (_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value) {
_dictionary.Remove(item.Key);
return true;
} else return false;
}
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
key_ref_count = krc;
public bool TryGetValue(TKey key, out int value) {
if (_dictionary.TryGetValue(key, out var krc)) {
value = krc.count;
return true;
}
key_ref_count = null;
value = 0;
return false;
}
// explicit
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void absorb(RefCountMap<TKey> other) {
foreach (var kv in other._dictionary) {
if (!_dictionary.TryGetValue(kv.Key, out KeyRefCount curr))
_dictionary.Add(kv.Key, kv.Value);
}
}
public RefCountMap<TKey> clone() =>
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
class KeyRefCount : IKeyRefCount<TKey> {
class KeyRefCount {
public KeyRefCount(TKey key) =>
this.key = key;
@@ -198,27 +224,7 @@ namespace mbts.collections {
_dictionary.Clear();
}
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
using (_execute_op()) {
if (try_lookup(key, out key_ref_count)) {
remove(key);
return true;
} else return false;
}
}
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
using (_execute_op()) {
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
key_ref_count = krc;
return true;
}
key_ref_count = null;
return false;
}
}
class KeyRefCount : IKeyRefCount<TKey> {
class KeyRefCount {
public KeyRefCount(TKey key) =>
this.key = key;
@@ -45,8 +45,10 @@ namespace mbts.ui {
var cmd = new DelegateCommand(() => execute(param), () => can_execute);
observer = new AnonymousObserver<T>(p => {
param = p;
if (can_execute != get_can_execute(param))
if (can_execute != get_can_execute(param)) {
can_execute = !can_execute;
cmd.RaiseCanExecuteChanged();
}
});
return cmd;
}
@@ -6,19 +6,15 @@ namespace mbts.ui {
// Value = "{mbts:BTSColor X.y}"
// Some colos are also available as static resources in colors.xaml
public static class BTSColors {
// 84 = 255 * 0.33
const byte _one_third_opacity_strength = 84;
static Brush _header_brush, _header_text_brush, _header_alternate_brush, _header_error_text_brush;
static BTSColors() {
_init_brush(header, out _header_brush);
_init_brush(header_text, out _header_text_brush);
_init_brush(header_alternate, out _header_alternate_brush);
_init_brush(header_error_text, out _header_error_text_brush);
}
// freezing brushes makes them thread-safe
static void _init_brush(BTSColor color, out Brush brush) {
brush = color;
brush.Freeze();
_header_brush = header;
_header_text_brush = header_text;
_header_alternate_brush = header_alternate;
_header_error_text_brush = header_error_text;
}
[DisplayInfo(name = "Default Header")]
@@ -72,7 +68,7 @@ namespace mbts.ui {
}
public static class MarketData {
static readonly Brush _bid_brush, _ask_brush;
static readonly Brush _bid_brush, _ask_brush, _semi_transparent_bid_brush, _semi_transparent_ask_brush;
static MarketData() {
// null BTSColors are implicitly gray media colors/brushes (this behavior is problematic and should be changed),
@@ -81,6 +77,8 @@ namespace mbts.ui {
ask = BTSCoreColors.red_darker;
_bid_brush = bid;
_ask_brush = ask;
_semi_transparent_bid_brush = new BTSColor(_one_third_opacity_strength, BTSColors.MarketData.bid);
_semi_transparent_ask_brush = new BTSColor(_one_third_opacity_strength, BTSColors.MarketData.ask);
}
// Base Colors
[DisplayInfo(name = "Market Data")]
@@ -97,6 +95,9 @@ namespace mbts.ui {
public static Brush bid_brush => _bid_brush;
public static Brush ask_brush => _ask_brush;
public static Brush semi_transparent_bid_brush => _semi_transparent_bid_brush;
public static Brush semi_transparent_ask_brush => _semi_transparent_ask_brush;
[DisplayInfo(name = "Settlement")]
public static BTSColor settlement_background { get { return Grid.default_column_background; } }
@@ -165,14 +166,14 @@ namespace mbts.ui {
static Orders() {
_init_brush(_sent, out sent_brush);
_init_brush(filled_dark, out filled_dark_brush);
_init_brush(working_cancel, out working_cancel_brush);
_init_brush(working_order_background, out working_order_background_brush);
_init_brush(completed_order_background, out completed_order_background_brush);
_init_brush(error_order_background, out error_order_background_brush);
_init_brush(canceled_order_background, out canceled_order_background_brush);
_init_brush(modified, out modified_brush);
sent_brush = _sent;
filled_dark_brush = filled_dark;
working_cancel_brush = working_cancel;
working_order_background_brush = working_order_background;
completed_order_background_brush = completed_order_background;
error_order_background_brush = error_order_background;
canceled_order_background_brush = canceled_order_background;
modified_brush = modified;
}
// Base Colors
@@ -7,8 +7,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
@@ -240,84 +238,7 @@ namespace System.Collections.Specialized {
}
namespace System.Reactive.Linq {
public static class ObservableExtensions
{
/// <summary>
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
/// </summary>
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
/// <returns>An "Observable Heartbeat"</returns>
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
IObservable<bool> alive = source.Select(_ => true);
return dead.Merge(alive).DistinctUntilChanged();
}
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
// with structs it was even more annoying because they can't be null
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
source.CombineLatest(second, (v1, v2) => (v1, v2));
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
PairWithPrevious(source, default);
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
/// <summary>
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
/// each inner sequence is terminated when the next one begins.
/// <para/>In other words, observers are notified by the inner observerables in &quot;series,&quot; rather than &quot;parallel.&quot;
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="select_next"></param>
/// <param name="source_sub"></param>
/// <returns></returns>
public static IConnectableObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
new ConnectableSeriesSource<TSource>(source).create_connectable(select_next);
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
Observable.Create<T>(o => {
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
Observable.Create<T>(o => {
if (can_get_value())
o.OnNext(get_value());
return source.Subscribe(o);
});
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
Observable.Create<T>(o => {
callback();
add_handler(callback);
return Disposable.Create(() => remove_handler(callback));
void callback() => o.OnNext(selector());
});
static class SparkObservableExtensions {
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
@@ -326,47 +247,5 @@ namespace System.Reactive.Linq {
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
}
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) =>
source.Subscribe(new CompositeObserver<T>(observers));
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
if (buffer_size == 0) return source.Publish().RefCount();
int num_subscribers = 0;
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
IDisposable source_sub = null;
return Observable.Create<T>(o => {
if (Interlocked.Increment(ref num_subscribers) == 1)
source_sub = source.Subscribe(replay.OnNext);
IDisposable sub = replay.Subscribe(o);
return Disposable.Create(() => {
sub.Dispose();
if (Interlocked.Decrement(ref num_subscribers) == 0)
source_sub.Dispose();
});
});
}
/// <summary>
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
/// </summary>
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
/// <summary>
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="subscribe_value"></param>
/// <returns></returns>
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
IDisposable value_sub = null;
return new Disposables.CompositeDisposable(source.Subscribe(val => Disposable.replace(ref value_sub, subscribe_value(val)))) {
() => Disposable.dispose(ref value_sub)
};
}
}
}
}
@@ -27,6 +27,8 @@ namespace mbts.ui {
// module to ask to restore the window after app restart.
IShellModule Owner { get; }
void on_shell_window_vm(ShellWindowVM shell_window_vm);
// Return the UI controls which will be displayed
ShellWindowUI GetContent();
@@ -113,5 +115,28 @@ namespace mbts.ui {
return src.Subscribe(o);
});
}
/// <summary>
/// Returns an IObservable sequence of <see cref="ShellWindowVM.Title"/>.
/// </summary>
public static IObservable<string> literal_title_source(IShellWindow shell_window, ShellWindowVM shell_window_vm) {
return Observable.Create<string>(o => {
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
shell_window.TitleChanged += notify;
notify();
connect();
return Disposable.Create(disconnect);
void connect() {
shell_window_vm.Model.TitleChanged += notify;
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
}
void disconnect() {
shell_window_vm.Model.TitleChanged -= notify;
shell_window_vm.TitleOverrideChanged -= on_title_override_changed;
}
void on_title_override_changed(string value) => notify();
void notify() => o.OnNext(shell_window_vm.Title);
});
}
}
}
@@ -0,0 +1,70 @@
using bts.api.core;
using mbts.spark;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.persist {
/// <summary>
/// This service offers methods to save named settings, and observe the resultant named-settings <see cref="IObservable{T}"/> <see cref="TableUpdate{T, TKey}"/>.
/// </summary>
/// <typeparam name="T">The type of settings.</typeparam>
interface ISettingsProfileService<T> where T : class, IEquatable<T> {
uint add_or_update(SettingsProfile<T> value);
IReadOnlyList<uint> add_or_update(IEnumerable<SettingsProfile<T>> value);
void remove(uint id);
IObservable<TableUpdate<SettingsProfile<T>, uint>> subscribe_all();
}
class SettingsProfileService<T> : ISettingsProfileService<T> where T : class, IEquatable<T> {
readonly UserSettingsHub _hub;
readonly Func<UserSettings, ImmutableDictionary<uint, SettingsProfile<T>>> _get_profiles;
readonly Func<UserSettings, IReadOnlyDictionary<uint, SettingsProfile<T>>, UserSettings> _set_profiles;
public SettingsProfileService(UserSettingsHub hub, Func<UserSettings, ImmutableDictionary<uint, SettingsProfile<T>>> get_profiles, Func<UserSettings, IReadOnlyDictionary<uint, SettingsProfile<T>>, UserSettings> set_profiles) {
_hub = hub;
_get_profiles = get_profiles;
_set_profiles = set_profiles;
}
// Note: this operation is atomic, but does not protect against overwrites.
// This is not cause for concern, since settings are saved via user intervention (not programatically),
// it is pracically impossible for the user to click 'Save' fast enough to cause an incidental overwrite.
public uint add_or_update(SettingsProfile<T> value) {
var curr = _hub.current;
var mdata = _get_profiles(curr);
if (value.id == 0)
value = value.with(curr.next_ladder_profile_id);
_hub.save_default(_set_profiles(curr, mdata.SetItem(value.id, value))).ignore();
return value.id;
}
public IReadOnlyList<uint> add_or_update(IEnumerable<SettingsProfile<T>> values) {
var curr = _hub.current;
var builder = _get_profiles(curr).ToBuilder();
var ids = new List<uint>();
uint new_id = curr.next_ladder_profile_id;
foreach (var value in values) {
if (value.id == 0) {
builder.Add(new_id, value.with(new_id));
new_id++;
} else {
builder[value.id] = value;
}
}
_hub.save_default(_set_profiles(curr, builder.ToDictionary())).ignore();
return ids;
}
public void remove(uint id) {
var curr = _hub.current;
_hub.save_default(_set_profiles(curr, _get_profiles(curr).Remove(id))).ignore();
}
public IObservable<TableUpdate<SettingsProfile<T>, uint>> subscribe_all() =>
_hub.subscribe_default_settings().Select(_get_profiles).Table(SettingsProfile<T>.key_fn);
}
}
@@ -0,0 +1,79 @@
using ProtoBuf;
using System;
namespace mbts.spark {
interface ISettingsProfile<T> {
uint id { get; }
string name { get; }
T value { get; }
SecurityCriteria security_criteria { get; }
}
struct SettingsProfile<T> : IEquatable<SettingsProfile<T>> where T : class, IEquatable<T> {
public static readonly Func<SettingsProfile<T>, uint> key_fn = ns => ns.id;
public readonly uint id;
public readonly string name;
public readonly T value;
public readonly SecurityCriteria security_criteria;
public SettingsProfile(uint id, string name, T value, SecurityCriteria security_criteria = null) {
this.id = id;
this.name = name;
this.value = value;
this.security_criteria = security_criteria;
}
public SettingsProfile<T> with(uint? id = null, string name = null, T value = null, SecurityCriteria security_criteria = null) =>
new SettingsProfile<T>(id ?? this.id, name ?? this.name, value ?? this.value, security_criteria ?? this.security_criteria);
public bool Equals(SettingsProfile<T> other) =>
id == other.id &&
name == other.name &&
value.safe_equals(other.value) &&
security_criteria.safe_equals(other.security_criteria);
public override bool Equals(object obj) =>
obj is SettingsProfile<T> other && Equals(other);
public override int GetHashCode() =>
id.GetHashCode();
}
[ProtoContract]
class LadderSettingsProfile : ISettingsProfile<LadderSettings>, IEquatable<LadderSettingsProfile> {
public LadderSettingsProfile() { }
public LadderSettingsProfile(uint id, string name, LadderSettings value, SecurityCriteria security_criteria = null) {
this.id = id;
this.name = name;
this.value = value;
this.security_criteria = security_criteria;
}
[ProtoMember(1)]
public uint id { get; }
[ProtoMember(2)]
public string name { get; }
[ProtoMember(3)]
public LadderSettings value { get; }
[ProtoMember(4)]
public SecurityCriteria security_criteria { get; }
public LadderSettingsProfile with(uint? id = null, LadderSettings value = null) =>
new LadderSettingsProfile(id ?? this.id, name, value ?? this.value, security_criteria);
public bool Equals(LadderSettingsProfile other) =>
other != null &&
id == other.id &&
name == other.name &&
value.safe_equals(other.value) &&
security_criteria.safe_equals(other.security_criteria);
public override bool Equals(object obj) => Equals(obj as LadderSettingsProfile);
public override int GetHashCode() => id.GetHashCode();
}
}
@@ -2,11 +2,14 @@
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
interface IDefaultableSettings {
bool use_defaults { get; set; }
}
/// <summary>
/// The settings Mothership. All window - and any other future user based - settings should have a corresponding default member here.
/// </summary>
@@ -25,6 +28,18 @@ namespace mbts.spark {
[ProtoMember(1)]
LadderSettings _ladder;
[ProtoMember(11)]
readonly LadderSettingsProfile[] _ladder_profiles;
/// When Profiles Are Deleted, ladders that were using that profile shall copy the profile's settings to a local profile.
/// Therefor:
/// Deleted profile ids must never be reused.
/// If they were reused, consider an unloaded workspace with ladders that were using the deleted profile.
/// If such a workspace was reloaded after a new profile was saved with the deleted one's id, the ladders using that deleted profile would suddenly be using the new profile.
/// To Prevent IDs From Being Recycled:
/// The last profile id is stored in the protobuf (<see cref="_last_ladder_profile_id"/>).
/// Anytime a profile is added, it shall use <see cref="next_ladder_profile_id"/>.
[ProtoMember(12)]
uint _last_ladder_profile_id;
// removing NotificationSettings these from proto-serialization, since they are disabled by default for now.
// This way, when they are enabled, there won't be any backwards compatability issues to worry about, as no clients will have persisted them yet.
NotificationSettings _notification;
@@ -44,7 +59,12 @@ namespace mbts.spark {
public UserSettings() { }
public UserSettings(LadderSettingsProfile[] ladder_profiles) =>
_ladder_profiles = ladder_profiles;
public LadderSettings ladder => _ladder ?? (_ladder = LadderSettings.Default);
public IReadOnlyList<LadderSettingsProfile> ladder_profiles => _ladder_profiles ?? Array.Empty<LadderSettingsProfile>();
public uint next_ladder_profile_id => _last_ladder_profile_id + 1;
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
@@ -53,15 +73,16 @@ namespace mbts.spark {
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
public bts.shell.orderbook.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.orderbook.OrderBookStartupData.Default.grid_settings_wrapper());
public UserSettings with(LadderSettings ladder = null,
NotificationSettings notification = null,
GridSettings provided_market_view_gridsettings = null,
public UserSettings with(LadderSettings ladder = null,
LadderSettingsProfile[] ladder_profiles = null,
NotificationSettings notification = null,
GridSettings provided_market_view_gridsettings = null,
GridSettings time_and_sales = null,
GridSettings provided_fills_gridsettings = null,
GridSettings provided_synthetic_management_gridsettings = null,
GridSettings provided_pnl_gridsettings = null,
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null) =>
new UserSettings {
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null) {
var us = new UserSettings(ladder_profiles ?? _ladder_profiles) {
_ladder = ladder ?? _ladder,
_notification = notification ?? _notification,
_market_view_grid = provided_market_view_gridsettings ?? _market_view_grid,
@@ -69,8 +90,11 @@ namespace mbts.spark {
_fills_grid = provided_fills_gridsettings ?? _fills_grid,
_synthetic_management_grid = provided_synthetic_management_gridsettings ?? _synthetic_management_grid,
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
_order_book_grid = order_book_grid ?? _order_book_grid
_order_book_grid = order_book_grid ?? _order_book_grid,
};
us._last_ladder_profile_id = Math.Max(_last_ladder_profile_id, us.ladder_profiles.Select(p => p.id).MaxOrDefault());
return us;
}
public static class FileExtensions {
public const string
@@ -1,7 +1,10 @@
using bts;
using bts.utils;
using mbts.spark;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
@@ -28,15 +31,42 @@ namespace mbts.persist {
UserSettings s;
try {
s = await _persist.get<UserSettings>(NAME_SPACE, DEFAULT_KEY);
if (s != null) {
s.ladder.sanitize();
// ensure valid named ladder set (w/ distinct, non-zero ids)
var ladder_profile_map = new Dictionary<uint, LadderSettingsProfile>();
var unidentified_lps = new List<LadderSettingsProfile>();
foreach (var lp in s.ladder_profiles) {
lp.value.sanitize();
if (lp.id == 0) {
unidentified_lps.Add(lp);
} else ladder_profile_map[lp.id] = lp;
}
uint id = s.next_ladder_profile_id;
foreach (var lp in unidentified_lps) {
ladder_profile_map[id] = lp.with(id);
id++;
}
var sorted_lps = ladder_profile_map.Values.ToArray();
Array.Sort(sorted_lps, Comparer<uint>.Default.wrap<uint, LadderSettingsProfile>(lp => lp.id));
if (!sorted_lps.SequenceEqual(s.ladder_profiles))
await _persist.set(NAME_SPACE, DEFAULT_KEY, s = s.with(ladder_profiles: sorted_lps));
}
} catch(Exception ex) {
Log.Persist.warn($"Error loading {typeof(UserSettings)} from namespace '{NAME_SPACE}', will restore factory defaults and continue as usual: {ex}");
await save_default(s = UserSettings.factory_defaults);
}
if (s != null)
_set_and_notify(s);
lock (_default_settings_lock) {
initialized = true;
if (s != null)
_default_settings_subject.OnNext(_default_settings = s);
}
}
}
// indicates whether or not the settings have been loaded from the server (this happens once, on startup - see the constructor for details)
public bool initialized { get; private set; }
/// Subscribing via _persist.subscribe may be cleaner in theory, however using a local event allows observers to be notified immediately.
/// Assuming there aren't any rogue agents saving UserSettings by themselves, there should never be any discrepancies.
public IObservable<UserSettings> subscribe_default_settings() => _default_settings_subject.AsObservable();
@@ -49,20 +79,19 @@ namespace mbts.persist {
}
public async Task save_default(UserSettings value) {
_set_and_notify(value);
// notify observers before persisting so the changes are reflected immediately in the ui
lock (_default_settings_lock) {
_default_settings_subject.OnNext(_default_settings = value);
}
await _persist_async();
}
async Task _persist_async() {
try {
await _persist.set(NAME_SPACE, DEFAULT_KEY, value);
await _persist.set(NAME_SPACE, DEFAULT_KEY, _default_settings);
} catch(Exception ex) {
Log.Persist.warn($"Error persisting {typeof(UserSettings)} in namespace '{NAME_SPACE}': {ex}");
}
}
void _set_and_notify(UserSettings value) {
lock (_default_settings_lock) {
_default_settings = value;
// notify observers before persisting so the changes are reflected immediately in the ui
_default_settings_subject.OnNext(value);
}
}
}
}
@@ -40,11 +40,11 @@ namespace bts.utils {
public event Action<T> common_value_changed;
public event Action<Plurality> plurality_changed;
public IReadOnlyCollection<T> distinct_values => _values.keys;
public T common_value => _values.keys.SafeSingleOrDefault(_default_value);
public IReadOnlyCollection<T> distinct_values => _values.Keys;
public T common_value => _values.Keys.SafeSingleOrDefault(_default_value);
public Plurality plurality {
get {
switch (_values.keys.Count) {
switch (_values.Count) {
case 0: return Plurality.None;
case 1: return Plurality.One;
default: return Plurality.Many;
@@ -115,7 +115,7 @@ namespace bts.utils {
_deferred_adds = new RefCountMap<T>(value_comparer);
_deferred_removes = new RefCountMap<T>(value_comparer);
_value_groups = new Dictionary<TKey, RefCountMap<T>>(key_comparer);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.keys);
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.Keys);
}
public event Action<KeyValuePair<TKey, IEnumerable<T>>?> common_value_group_changed;
@@ -177,27 +177,27 @@ namespace bts.utils {
++_deferral_count;
return Disposable.Create(() => {
if (--_deferral_count == 0 && (_deferred_adds.Count > 0 || _deferred_removes.Count > 0)) {
foreach(var krc in _deferred_removes) {
var k = _key(krc.key);
foreach(var kv in _deferred_removes) {
var k = _key(kv.Key);
if (_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
rcm.decrement(krc.key, krc.count);
rcm.decrement(kv.Key, kv.Value);
if (rcm.Count == 0)
_value_groups.Remove(k);
}
}
foreach (var krc in _deferred_adds) {
var k = _key(krc.key);
foreach (var kv in _deferred_adds) {
var k = _key(kv.Key);
if (!_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
_value_groups.Add(k, rcm = new RefCountMap<T>(_value_comparer));
}
rcm.increment(krc.key, krc.count);
rcm.increment(kv.Key, kv.Value);
}
if (!EqualityComparer.nullable_equals(last_fired_common_key, common_value_group?.Key)) {
common_value_group_changed?.Invoke(common_value_group);
} else if (last_fired_common_key is TKey k) {
common_values_changed?.Invoke((
_deferred_removes.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k)),
_deferred_adds.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k))));
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
}
_deferred_adds = new RefCountMap<T>(_value_comparer);
_deferred_removes = new RefCountMap<T>(_value_comparer);
+2 -1
View File
@@ -61,7 +61,8 @@ namespace bts.utils {
static class MutableFactoryStore {
[ThreadStatic]
static readonly Dictionary<Type, Func<IMessage>> _factories = new Dictionary<Type, Func<IMessage>>();
static Dictionary<Type, Func<IMessage>> _factories_backing;
static Dictionary<Type, Func<IMessage>> _factories => _factories_backing ?? (_factories_backing = new Dictionary<Type, Func<IMessage>>());
public static Func<IMessage> get(Type type) =>
_factories.GetOrAdd(type,
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive.Linq;
namespace mbts.ui.utility {
@@ -22,6 +23,35 @@ namespace mbts.ui.utility {
: base(items, item => item, select_default, comparer) { }
}
class SelectionModelInlinePropRelay<TItem, TValue, TKey> where TKey : IEquatable<TKey> {
readonly SelectionModel<TItem, TValue> _model;
readonly Func<TItem, TKey> _item_key;
IDisposable _select_inline_prop;
public SelectionModelInlinePropRelay(SelectionModel<TItem, TValue> model, Func<TItem, TKey> item_key) {
_model = model;
_item_key = item_key;
}
public void on_inline_prop(InlineUserProperty<TKey> inline_prop) {
Disposable.dispose(ref _select_inline_prop);
if (inline_prop?.has_value ?? false) {
if (!_try_convert_inline_prop() && _model.items is INotifyCollectionChanged ncc) {
_select_inline_prop = ncc.Subscribe((s, e) => {
if (_try_convert_inline_prop())
Disposable.dispose(ref _select_inline_prop);
});
}
bool _try_convert_inline_prop() {
if (_model.items.TryGetFirst(i => _item_key(i).Equals(inline_prop.value), out var item)) {
_model.on_inline_property(new InlineUserProperty<TItem>(item, inline_prop.force_override));
return true;
} else return false;
}
}
}
}
class SelectionModel<TItem, TValue> {
readonly Func<TValue> _select_default;
readonly IEqualityComparer<TValue> _value_comparer;
@@ -62,7 +62,7 @@ namespace mbts.utils {
_threshold = value;
}
}
public IReadOnlyCollection<T> filtered_items => _filtered_items.keys;
public IReadOnlyCollection<T> filtered_items => _filtered_items.Keys;
public IDisposable join(T item) =>
_subscribe_item == null ? throw new InvalidOperationException("No 'subscribe-item' delegate was specified in the constructor.") : join(item, _subscribe_item(item));
@@ -592,13 +592,14 @@ namespace mbts.ui.shell {
public abstract class DisposableViewModelBase : ViewModelBaseWithValidation, IPropertyChangedDemuxer, IDisposable, IWillLetYouKnowWhenIDispose
{
protected IDisposable _dispose = null;
private List<IDisposable> _observable_properties;
List<IDisposable> _observable_properties_backing;
List<IDisposable> _observable_properties => _observable_properties_backing ?? (_observable_properties_backing = new List<IDisposable>());
PropertyChangedDemuxer _property_changed_demuxer_backing;
IPropertyChangedDemuxer _property_changed_demuxer =>
_property_changed_demuxer_backing ?? (_property_changed_demuxer_backing = new PropertyChangedDemuxer(this));
IObservable<T> IPropertyChangedDemuxer.subscribe<T>(string property) => (_property_changed_demuxer_backing ?? (_property_changed_demuxer_backing = new PropertyChangedDemuxer(this))).subscribe<T>(property);
IObservable<T> IPropertyChangedDemuxer.subscribe<T>(string property) => _property_changed_demuxer.subscribe<T>(property);
IObservable<object> IObservableDemuxer<string, PropertyChangedEventArgs, object>.subscribe(string property) => _property_changed_demuxer.subscribe(property);
void IObserver<PropertyChangedEventArgs>.OnNext(PropertyChangedEventArgs value) => _property_changed_demuxer.OnNext(value);
void IObserver<PropertyChangedEventArgs>.OnError(Exception error) => _property_changed_demuxer.OnError(error);
@@ -607,7 +608,6 @@ namespace mbts.ui.shell {
internal IPropertyChangedDemuxer as_demuxer() => this;
protected Property<T> backing<T>(string name, IObservable<T> source, T default_value = default(T)) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
var prop = new ObservablePropertySink<T>(this, name, default_value);
_observable_properties.Add(source.Subscribe(prop));
return prop;
@@ -618,7 +618,6 @@ namespace mbts.ui.shell {
/// <para/>Note: UI settings in particular may find this useful, as brushes and other Freezable types are not inherently thread safe.
/// </summary>
protected Property<T> backing<T, TData>(string name, IObservable<TData> source, Func<TData, T> select, T default_value = default) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
var prop = new ObservablePropertySink<T>(this, name, default_value);
_observable_properties.Add(source.OnDispatcher().Subscribe(d => prop.set(select(d))));
return prop;
@@ -685,7 +684,6 @@ namespace mbts.ui.shell {
}
protected void attach<T, V>(SelectorProperty<T, V> selector_property, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
_observable_properties.Add(items.OnDispatcher().Subscribe(selector_property));
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(selector_property));
}
@@ -722,7 +720,7 @@ namespace mbts.ui.shell {
protected virtual void Dispose(bool disposing) { }
public void Dispose() {
Disposable.dispose(ref _dispose);
Disposable.dispose(_observable_properties);
Disposable.dispose(_observable_properties_backing);
Disposable.dispose(ref _property_changed_demuxer_backing);
Dispose(true);
OnDisposed?.Invoke(true);
@@ -822,6 +820,11 @@ namespace mbts.ui.shell {
void _try_auto_select() {
if (_is_user_selected_item) {
// if we have a user-selected item and the _desired_item for which we might want to set as the selected item
// isn't force_override, then no change
if (!_desired_item.force_override) {
return;
}
if (desired_item_valid() && _desired_item.force_override && _try_match_item(_desired_item.value, out TValue match)) {
_set_selected_item(match, SelectionReason.SettingsMandatedSelection);
return;
@@ -3,6 +3,7 @@
namespace bts {
struct AccountTuple : IEquatable<AccountTuple>, IComparable<AccountTuple>, IComparable {
public static readonly Func<AccountTuple, uint> key_fn = a => a.id;
public static readonly Func<AccountTuple?, uint> nullable_key_fn = a => a.HasValue ? a.Value.id : 0u;
public readonly uint id;
public readonly string name;
+7
View File
@@ -36,6 +36,13 @@
<SolidColorBrush x:Key="TextBox.Static.Background.Brush" Color="{StaticResource TextBox.Static.Background.Color}"/>
<SolidColorBrush x:Key="TextBox.MouseOver.Border.Brush" Color="{StaticResource TextBox.MouseOver.Border.Color}"/>
<!--Menu-->
<Color x:Key="MenuItem.MouseOver.Background.Color">#3D26A0DA</Color>
<Color x:Key="MenuItem.MouseOver.Border.Color">#FF26A0DA</Color>
<Color x:Key="Menu.Background.Color">#FFF5F5F5</Color>
<Color x:Key="Menu.DarkBackground.Color">#FFF1F1F1</Color>
<Color x:Key="Menu.Border.Color">#FFE2E3E3</Color>
<!--CheckBox-->
<Color x:Key="CheckBox.Static.ForegroundColor">#FF212121</Color>
<Color x:Key="CheckBox.Static.BackgroundColor">#FFFFFFFF</Color>
@@ -3,11 +3,6 @@
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:sys="System;assembly=mscorlib"
BorderBrush="Black"
Padding="3,0,0,0"
>
@@ -21,10 +16,10 @@
<ControlTemplate TargetType="ToggleButton">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="19"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="ellipse" Width="19" Height="19" Stroke="{TemplateBinding BorderBrush}"/>
<Ellipse x:Name="ellipse" Width="20" Height="20" Stroke="{TemplateBinding BorderBrush}"/>
<Path
x:Name="arrow"
Data="M1,1.5L4.5,5 8,1.5"
Loaded 100 of 352 files, more files were not shown because too many files have changed in this diff. Show more