Compare commits

...
241 Commits
Author SHA1 Message Date
Pedro Pinto 188a226165 Updated release notes 2018-10-24 18:46:36 +00:00
Pedro Pinto 9aba824be0 Merge branch 'grid-settings' into prod 2018-10-24 18:19:33 +00:00
Pedro Pinto a958be45dd Added command to facilitate discovery of legged orders 2018-10-24 18:17:54 +00:00
John Colvin 7fd0375283 SPARK-1807 Synthetic Add Leg OOM Exception 2018-10-24 12:49:35 -05:00
Pedro Pinto dc2879e2ff Merged 2018-10-24 13:46:41 -04:00
Eric Harding 7fa75b8090 Force implied merge to use the same precision as direct to avoid ladder overlap 2018-10-24 12:48:20 -04:00
John Colvin 5e58aaf199 Synthetic Editor - disable 'Market Data Calculation' param-row 2018-10-24 11:16:07 -05:00
Taha Afzal 54231e2d74 spreader test for implieds 2018-10-24 10:56:51 -04:00
Eric Harding 0042835842 Combine 2 sim config keys into one. 2018-10-24 10:17:56 -04:00
Kevin Darby a53d48a6e3 FirstOrDefault is incompatible with immutable.... 2018-10-23 17:37:31 -04:00
Kevin Darby d3ffc9eed5 Merged in bts_user_override (pull request #86)
allow bts user to see all fills/orders/p&l, etc per Pedro

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2018-10-23 21:14:49 +00:00
Kevin Darby dcfa809b9e allow bts user to see all fills/orders/p&l, etc per Pedro 2018-10-23 17:10:21 -04:00
Jake Norbut fa31c2ef11 MView - Better (Implied, Direct, Merged) Header Text defaults, column size adjusted appropriately 2018-10-23 15:39:47 -05:00
Eric Harding 7c28938ba6 Stop slowly publishing md_permissions when nobody is listening. 2018-10-23 16:10:25 -04:00
Eric Harding 0a426b84e3 Fix sparkd crash in webserver.cpp 2018-10-23 15:28:00 -04:00
Eric Harding 45f5e485d3 Remove unused md defaults 2018-10-23 15:27:45 -04:00
Eric Harding 853f8d2e84 Start ExchangeSim earlier to avoid race condition. 2018-10-23 14:18:58 -04:00
Jake Norbut d1784355d0 icons.cs - applying a layer of frost 2018-10-23 11:51:47 -05:00
Pedro Pinto f045d272c1 Moved docker ptr 2018-10-23 15:14:00 +00:00
Pedro Pinto faa6528400 Finished prod merge. 2018-10-23 15:05:28 +00:00
Pedro Pinto be33ecc798 Added some queue related benchmarks. 2018-10-23 15:04:58 +00:00
Eric Harding 37f3550831 subrepos are deranged 2018-10-23 09:45:40 -04:00
Eric Harding e2d454da1e advance externals pointer... hopefully. 2018-10-23 09:02:43 -04:00
Eric Harding 7af2467713 Merge from prod 2018-10-23 09:01:37 -04:00
Pedro Pinto d8e203264d Merge branch 'grid-settings' of bitbucket.org:bluetradesys/spark into grid-settings 2018-10-22 22:25:37 +00:00
Pedro Pinto 4a0cefd240 Added support for late joiners in settlement update events. 2018-10-22 22:25:09 +00:00
Jake Norbut e5aef7ee5b OB GridSettings - fixing NullRef when using Dan's DB 2018-10-22 14:27:45 -05:00
Pedro Pinto 9f2ac024cb Fixed rouding problem in Sim price levels. 2018-10-16 20:09:00 +00:00
Eric Harding 92a8cb507a Merge branch 'SPARK-1659' of bitbucket.org:bluetradesys/spark into grid-settings 2018-10-16 09:58:07 -04:00
Eric Harding ea87dcb52a Respect flag which enable/disable implied forwarding in simulator 2018-10-16 09:01:43 -04:00
Eric Harding 467a66565d Merge branch 'SPARK-1659' of bitbucket.org:bluetradesys/spark into SPARK-1659 2018-10-16 08:51:06 -04:00
John Colvin a52b477b0c SPARK-1778 VAP bars disappear when the book goes away 2018-10-14 18:36:49 -05:00
John Colvin dbd0bfed10 SPARK-1776 / SPARK-1788 default the volume at price column to NOT showing 2018-10-14 18:16:08 -05:00
John Colvin ee25d3ed9a SPARK-1776 / SPARK-1785 LadderRender - skip volumes with nan prices 2018-10-14 18:07:05 -05:00
John Colvin d662a60f6a SPARK-1787 Hyperlink Cells - bind width to Column.ActualWidth (fixes stale background when column width changes) 2018-10-14 17:57:02 -05:00
John Colvin c0a80182e6 HyperlinkCell - only draw BackgroundOverlay when Background != null 2018-10-14 17:36:08 -05:00
John Colvin 4360a1154c Ladder - market size display settings 2018-10-14 16:17:38 -05:00
John Colvin 1cb6066aea Merge branch 'SPARK-1659' into grid-settings 2018-10-14 15:56:48 -05:00
Taha Afzal e85d7b5d0c 1.IntPrice comparison can now compare IntPrices of different exponents 2.copy_book for direct with holes fixed 2018-10-11 17:19:48 -04:00
Jake Norbut 143f660f37 adjusting Symbol column default width 2018-10-11 14:58:40 -05:00
Jake Norbut 4107ac093b OrderBook spit and polish 2018-10-11 14:00:17 -05:00
John Colvin c90752b4f2 OrderBook - small fixes for grid-settings conversion 2018-10-11 11:49:43 -05:00
Jake Norbut f97ccf249f GridSettingsVM is now ipmlements ITemplateSelectorKey so that we can actually see the settings 2018-10-11 09:33:46 -05:00
John Colvin 2d3e942f93 fix OrderBook grid settings use_defaults behavior 2018-10-10 16:56:50 -05:00
Jake Norbut d965674ff5 ContentTemplateSelector is now used in SimpleGridSettingsDialog; Ladder now uses this instead of LadderSettingsDialog 2018-10-10 15:52:18 -05:00
Eric Harding a174336baf MDSource should always use the complete book 2018-10-10 11:22:51 -04:00
Eric Harding 746e858f76 SPARK-1659 Typo in simulator implieds 2018-10-10 09:16:24 -04:00
Eric Harding 48702ce266 Merge branch 'SPARK-1659' of bitbucket.org:bluetradesys/spark into SPARK-1659 2018-10-10 08:42:42 -04:00
John Colvin 8ba354eda8 SPARK-1780 Orderbook settings - use [new]standard SimpleGridSettingsDialog 2018-10-09 18:48:56 -04:00
Taha Afzal 74b18cf71a added a manual test for implieds in the UI 2018-10-09 17:23:41 -04:00
Eric Harding 65d3b858b3 Fix typo in ExchangeSim which caused implied ask to be shown as bid. 2018-10-09 14:40:07 -04:00
Eric Harding 1e8fd9589b Fix logging crash during shell shutdown 2018-10-09 14:08:22 -04:00
Eric Harding c331c8dde5 Add flag to enable/disable implied forwarding in simulator 2018-10-09 14:08:15 -04:00
Taha Afzal 21a47e2d43 VAP merging into 1659 made the price go to NAN because of the PrizeSize changes 2018-10-09 12:14:05 -04:00
Taha Afzal 8cbe6d7199 MD Aux Service's VAP now uses context.poll instead of its own thread 2018-10-09 12:14:05 -04:00
Michael Harding 58481c9b17 SPARK-1538: Clones incoming 2018-10-09 12:14:05 -04:00
Eric Harding 1b5256d699 SPARK-1742 test can fail when time moves foward 2018-10-09 12:14:05 -04:00
Eric Harding 1e64f930d9 Pass though impileds and settles in simulator 2018-10-09 09:52:52 -04:00
Eric Harding 9630851f1f Add ToString to managed PriceSize 2018-10-09 09:52:42 -04:00
Jake Norbut 46878f009a Updating MinWidth so that remaining windows will all open without a horizontal scrollbar 2018-10-08 15:41:31 -05:00
Jake Norbut ef77826042 MView - shrinking default bid/ask cols from the enlargement I just made 2018-10-08 15:24:20 -05:00
Jake Norbut 344f82dc8f MView spit and polish 2018-10-08 15:16:30 -05:00
Jake Norbut bc362b9778 HyperlinkCell - removed a size i never ended up using 2018-10-08 14:35:53 -05:00
Jake Norbut f17b2cb06d HyperlinkColumn now responds to changes in HorizatonalAlignment 2018-10-08 14:34:19 -05:00
Jake Norbut 1d5878e2f5 HyperlinkCell mouseover behavior now works for all HorizontalAlignments 2018-10-08 14:33:09 -05:00
Jake Norbut 42cfba5ccc TaS spit and polish 2018-10-08 11:30:17 -05:00
Jake Norbut 9e178cf4a1 Remove GetSortingCriteria ext method that is no longer used 2018-10-08 11:04:50 -05:00
Jake Norbut 74cac5815c Fix a variety of small bugs after implementing GridSettings in PnL 2018-10-08 10:43:34 -05:00
Taha Afzal b47055d814 increasing log period of VAP publisher 2018-10-05 18:36:25 -04:00
Jake Norbut 1a760a579a PnL now support settings 2018-10-05 16:43:13 -05:00
Eric Harding f6b5635715 SPARK-1659 packet may contain implied or direct or both. need to preserve the other market. 2018-10-05 15:45:42 -04:00
Jake Norbut 1a7d0d75a8 merge fix problem w/ the DefaultColumns in mview 2018-10-05 13:28:05 -05:00
John Colvin e41a9351c2 Merged in pnl-to-diy (pull request #80)
Pnl to diy

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-10-05 18:07:27 +00:00
John Colvin 7a1ffa1ea9 fix NumericSignBrushConverter 2018-10-05 14:00:09 -04:00
John Colvin db34468bc4 SPARK-1774 pnl - switch to DIYGrid 2018-10-05 13:40:38 -04:00
John Colvin f5d7ea9a47 restructure NumericSignBrushConverter 2018-10-05 13:36:42 -04:00
Jake Norbut dc8c0df0a4 Fix build after merge of SPARK-1659 2018-10-05 11:23:11 -05:00
Jake Norbut b3d3071316 Synth Man - better column width defaults 2018-10-04 16:09:54 -05:00
Jake Norbut 88e352457f Synthetic Management now support Grid settings; need to test still 2018-10-04 15:49:38 -05:00
Eric Harding f1bb6af757 SPARK-1659 Fix for implieds on client side showing up as direct. 2018-10-04 15:59:55 -04:00
John Colvin 97a1cf0162 SPARK-1743 disable column visibility settings for synth ladders 2018-10-04 15:16:35 -04:00
John Colvin 1821ff8cc7 LadderSettingsView - volume settings help text 2018-10-04 14:20:43 -04:00
John Colvin dd397987ca LadderSettingsView - remove unused code 2018-10-04 14:20:42 -04:00
Jake Norbut 9b2b320255 merging synth-to-diy; all is well in the grid-settings branch 2018-10-04 12:42:00 -05:00
Jake Norbut 655a89289a Merge branch 'synth-to-diy' into grid-settings 2018-10-04 12:38:30 -05:00
John Colvin 911cf8a19c SPARK-1750 Ladder - gracefully handle NaNs in the data 2018-10-04 12:46:48 -04:00
Jake Norbut 7d9aaeac55 ISlowWindow and thsis SyntheticManagement are the bane of my existence 2018-10-04 11:41:14 -05:00
John Colvin 6546ee531f SPARK-1660 Implied Market in MarketView 2018-10-03 19:45:44 -04:00
John Colvin 9ea0e214b6 PriceSize - include 'direct_size' in Equality logic 2018-10-03 19:45:44 -04:00
Jake Norbut 0ffe8ba972 SyntheticManagement to DIYGrid 2018-10-03 17:00:19 -05:00
Eric Harding 41eb89ed35 SPARK-1749 Invalidate Synthetic book when metadata changes 2018-10-03 17:46:38 -04:00
John Colvin 9ae087eca1 SPARK-1737 Implied Market in Ladder 2018-10-03 14:58:26 -04:00
John Colvin 08d8dcd53f LadderGridHost - remove redundant 'refresh_depth' method 2018-10-03 14:58:24 -04:00
Taha Afzal 1a279298fd added cons to Book to construct it with a security, thats used by the Spreader now 2018-10-03 13:12:59 -04:00
Eric Harding 479eba940e Fix build from merge 2018-10-03 12:55:09 -04:00
Jake Norbut 4aa64a7b5d Settings - Be sure to launch SimpleGridSettingsDialog with any changes reflected in the vm_factory (user may have made changes on the grid gui which need to be reflected when the settings form launches) 2018-10-02 16:47:17 -05:00
John Colvin 459d097239 Merge branch 'volume_at_price' into SPARK-1659 2018-10-02 17:45:33 -04:00
Taha Afzal 188616d1ce Adding sub event for settlement (which was causing a crash) 2018-10-02 16:46:09 -04:00
John Colvin 9ef1fdb795 ColumnBase raises PropertyChanged for font properties 2018-10-02 16:35:01 -04:00
John Colvin e241d13edc SPARK-1665 Net Change and Implied market enums in Synthetic UI 2018-10-02 15:28:56 -04:00
John Colvin df7d791ba1 GridSettings - hook up fontfamily 2018-10-02 14:03:34 -04:00
Jake Norbut b0db7ad4b3 pulling prod in here since i'm gonna make a PR soon 2018-10-02 10:42:45 -05:00
Jake Norbut a29731cb3c TaS - fix default sorting; create GridSettingsManager with rows_vm so that settings changed can be applied to all existing rows and not just new rows 2018-10-02 10:36:08 -05:00
Taha Afzal 9b187797f8 more tests for implied and net_change 2018-10-02 11:26:46 -04:00
Taha Afzal 56ba30e7f4 added tests 2018-10-02 11:26:46 -04:00
Jake Norbut f776099c98 TaS: whoops, more refactoring 2018-10-02 09:41:18 -05:00
Jake Norbut 2825ad833f TaS - Fixing new settings; minor refactoring 2018-10-02 09:37:59 -05:00
Jake Norbut 795915fddc MarketView - don't always save the current GridSetting as Instance settings, they could be defaults 2018-10-01 15:03:50 -05:00
Jake Norbut 1ff0fe0208 Fills - Changing from using startup_data to an IObservable thereof, as such our fills_source is now an IConnectableObservable 2018-10-01 14:47:26 -05:00
Jake Norbut 6d03493bfb Fills - forgot to add various key columns; don't always save the current GridSetting as Instance settings (they could be defaults) 2018-10-01 14:21:46 -05:00
Jake Norbut 080c8cdaa6 WindowSettings - Apply button should only Apply to instance settings 2018-10-01 14:18:00 -05:00
Eric Harding 6db1aeea3f SPARK-1666 Add tests for synthetic md calculation 2018-10-01 14:39:04 -04:00
Eric Harding a6214a47b5 Intprice subtraction : handle NaN 2018-10-01 14:38:14 -04:00
John Colvin 2da1644f8e KeyDrivenObservableSeries - check 'is_disposed' before subscribing to next inner observable 2018-10-01 14:28:51 -04:00
Jake Norbut e15764eb2b Fills - save new grid settings with state 2018-10-01 10:48:04 -05:00
Jake Norbut 08a6daab00 misc 2018-10-01 09:28:32 -05:00
Jake Norbut ef03b87357 Adding settings to Fills 2018-10-01 09:26:36 -05:00
Eric Harding 0f339f603b Fix: Only publish final settlements 2018-10-01 09:36:25 -04:00
Eric Harding 49796a5c33 Only publish final settlements. 2018-09-28 17:35:01 -04:00
Taha Afzal 51cc64f031 throttling the vap timer log 2018-09-28 13:26:35 -04:00
John Colvin 42b4345485 LadderSettings - user can toggle visibility of 'Volume' and 'Last' columns 2018-09-28 12:10:41 -04:00
Eric Harding 20d77d1cf3 Cannot link sdk with benchmarks in sparklib so comment them out 2018-09-28 11:31:26 -04:00
John Colvin 9dd6fc8832 fix CollectionHelper.sync_with_never_duplicate 2018-09-28 11:10:15 -04:00
Eric Harding 3a5f6b331d SPARK-1740 Replace std::vector with boost::small_vector for 100ns improvement. 2018-09-28 09:47:14 -04:00
Jake Norbut d09dbb9422 pair programming at its finest 2018-09-27 16:10:01 -05:00
John Colvin 81afd71d84 GridSettings dialog - column vms maintain immutable properties through load-factory call 2018-09-27 16:59:39 -04:00
Eric Harding da21c57cca Enable selecting release for 'runsparkd.cmd' (for benchmarks) 2018-09-27 15:23:52 -04:00
Eric Harding d6fafee64c Handle possibility of a missing LegSpreaderParams 2018-09-27 15:23:29 -04:00
Eric Harding 0ed884a5ab Enable safe subtration for IntPrice 2018-09-27 14:21:52 -04:00
Jake Norbut 1822ed6aa2 fixing settings immutable bug - default vs NullableBlah?() - i like seeing null, it's make it easier to read 2018-09-27 11:29:37 -05:00
Eric Harding eabfb6a695 SPARK-1712 Use implied or direct market data for synthetic legs 2018-09-27 11:49:09 -04:00
Jake Norbut 58d0a3c688 Fixing sorting - too many criterions 2018-09-27 10:46:43 -05:00
Taha Afzal 8b3d4ba9aa Merge branch 'SPARK-1659' of bitbucket.org:bluetradesys/spark into SPARK-1659 2018-09-26 18:50:02 -04:00
Taha Afzal 8ff94037eb net price and implieds done for spreader 2018-09-26 18:49:52 -04:00
Eric Harding 065f667a79 Fix problem with pesky NaN implied 2018-09-26 18:26:24 -04:00
John Colvin f61b69f6bd fix grid-column-settings immutable values 2018-09-26 18:24:13 -04:00
Eric Harding 9ec85c324c merge from prod 2018-09-26 16:48:55 -04:00
Eric Harding 5ab506e9cd Fix some broken tests so I can more easily write other tests. (tech debt) 2018-09-26 16:41:40 -04:00
Jake Norbut e51d7de309 Freezing all icons; changing Picker icons; misc refactoring 2018-09-26 14:57:31 -05:00
John Colvin 28307b0681 LadderSettingsView.xaml.cs - remove unused field 2018-09-26 14:45:59 -04:00
John Colvin 5af68ccfd0 SPARK-1738 ladders using default settings should clone the settings object, instead of sharing the same one 2018-09-26 14:04:39 -04:00
John Colvin 4002f80ede UserSettingsHub.current getter should be concurrent 2018-09-26 13:57:16 -04:00
Eric Harding 65915a9784 Enable SyncronizeCollectionTests 2018-09-26 13:50:20 -04:00
Eric Harding 7259397e02 Move SecurityTests to spark_test 2018-09-26 13:47:01 -04:00
Jake Norbut 281b4e10b0 functionality matches the spec; I still need to clean up some things 2018-09-26 12:33:08 -05:00
Eric Harding b17a0b1eea Fix logging in Queryserver implied 2018-09-26 09:38:36 -04:00
Eric Harding d7e2c87830 Fix SettlementRecorder 2018-09-25 15:56:36 -04:00
John Colvin 22b1bfa777 SPARK-1671 Volume at price level (UI) 2018-09-25 15:39:48 -04:00
John Colvin 4ea9c6c28d Export MarketDataAuxiliaryService in ShellServices 2018-09-25 15:23:04 -04:00
Eric Harding 2cb5cddb91 Increment market data shm version 2018-09-25 14:58:22 -04:00
Eric Harding cc0dd649ea SPARK-1712 Change settlement price to IntPrice 2018-09-25 14:57:01 -04:00
Eric Harding 49e24804f7 SPARK-1659 move settlement into hot data section so we can use it to compute books 2018-09-25 13:35:28 -04:00
Eric Harding 7958ce236f SPARK-1659 Make PriceSize 3rd argument type safe 2018-09-25 10:22:28 -04:00
Taha Afzal f3c55f0981 Merging with net_change 2018-09-25 10:15:23 -04:00
Taha Afzal dc9df4af09 net change pricing hooked up, need some optimizations in PriceSize for speed 2018-09-25 09:46:07 -04:00
Eric Harding add6f99189 SPARK-1659 Fix operator < (cpp) 2018-09-24 16:32:18 -04:00
Eric Harding 37c6b5d46d SPARK-1711 Expose direct/implied markets in managed 2018-09-24 16:32:04 -04:00
Jake Norbut 0ff7adf73b SimpleGridSettingsManager now reflects settings changes immediately after they are made 2018-09-24 15:13:41 -05:00
Eric Harding 3ffc130889 SPARK-1711 Refactor PriceSize.cs 2018-09-24 13:41:41 -04:00
Eric Harding 69d75292ee SPARK-1712 add synthetic leg parameter for implied/direct book 2018-09-24 12:55:39 -04:00
Eric Harding d6539348ea SPARK-1659 Refactor to use IntPrice everywhere to reduce logic duplication 2018-09-24 12:54:52 -04:00
Jake Norbut 4d00be1ab5 fix build error; fix Columns VerticalAlgnment so that the expected behavior is followed when collapsed 2018-09-24 11:22:48 -05:00
Taha Afzal 505b01cdcb Settlements hooked to the order infrastructure 2018-09-23 20:14:40 -04:00
Eric Harding e8b210913c SPARK-1659 Refactor PriceSize to PriceSizeSize 2018-09-21 16:48:59 -04:00
John Colvin 2e10e997b0 prepare simple-grid-settings default\custom infrastructure 2018-09-21 15:46:33 -04:00
Jake Norbut 530975d9de before john's pr to help me get a handle on the SimpleGridSettingsManager and the WindowSettings 2018-09-21 11:11:31 -05:00
Eric Harding f560a9e603 Shrink PriceSize 2018-09-20 15:34:25 -04:00
Eric Harding d20e937a79 SPARK-1713 Add implied to QueryServer 2018-09-19 18:08:52 -04:00
Taha Afzal b7a99047ed Moved the VolumeAtPrice toggle to inside the MarketDataAuxiliatyServiceServer.cpp 2018-09-19 09:48:40 -04:00
Taha Afzal 98eb65c99f Merge conflict that was left out accidentally 2018-09-18 19:37:16 -04:00
Taha Afzal d1f346b28c Added buy/sell/passive volume to Volume At Price 2018-09-18 19:21:05 -04:00
Taha Afzal 39bf6ebaa5 git cherry-pick add file 2018-09-18 17:54:34 -04:00
Taha Afzal 9011df884e fixing header inclusions 2018-09-18 17:53:23 -04:00
Taha Afzal 92490e1a75 SPARK-1672(Volume at price) 2018-09-18 17:51:01 -04:00
Taha Afzal 8ee6d6d860 cherry pick add file 2018-09-18 17:50:05 -04:00
Taha Afzal 543dfa8509 SPARK-1672 scaffolding 2018-09-18 17:47:27 -04:00
Taha Afzal a8bc7a071e SPARK-1672 VAP initial check in 2018-09-18 17:44:18 -04:00
Eric Harding 02071c1289 SPARK-1709 Update OnixS to write direct/implied 2018-09-18 16:45:27 -04:00
Jake Norbut ce80e12451 really fix GridColumnSettings.with() regarding horizontal_alignment 2018-09-18 15:13:27 -05:00
Jake Norbut a04df917d4 fixing null Ref when creating ToS settings 2018-09-18 15:06:54 -05:00
Jake Norbut 0a7d8f931b fix GridColumnSettings.with() function (don't override horizontal_alignments inadvertantly) 2018-09-18 14:44:10 -05:00
John Colvin 77105029d3 fix GridColumnSettings.with() function (don't override width inadvertantly) 2018-09-18 14:48:35 -04:00
John Colvin 5fe0b0c58b SimpleGridSettingsManager - fix immutable col settings flags 2018-09-18 14:28:21 -04:00
Jake Norbut f77f7b091d MView - Fixing Size and Price fg color, disallowing fg color overrides 2018-09-17 15:00:19 -05:00
Jake Norbut 4f9111b8a0 MView - now supports GridSettings 2018-09-17 11:10:26 -05:00
Jake Norbut 94cda9a09b TOS: hooking up selection highlight property 2018-09-17 11:08:18 -05:00
John Colvin 29afa7f3b1 create SimpleGridSettingsManager/VM (no sound-triggers) 2018-09-13 15:42:57 -04:00
Eric Harding 45283593ce SPARK-1710 Add good test coverage for read_combined() 2018-09-12 13:03:43 -04:00
Eric Harding 00d91353a4 SPARK-1710 add read_direct/read_implied/read_merged accessors for market data 2018-09-11 17:27:15 -04:00
Eric Harding 7944338516 add test watching script (linux) 2018-09-11 16:15:39 +00:00
John Colvin 77854a3cf0 SPARK-1714 Ladder account - unable to pick a selection on server restart 2018-09-10 18:56:41 -04:00
Taha Afzal d29ec26beb SPARK-1672 scaffolding 2018-09-10 15:11:18 -04:00
Michael Harding d64810e234 Merged in SPARK-1678 (pull request #72)
SPARK-1678
2018-09-10 17:44:43 +00:00
Taha Afzal f3d2c9e5b9 SPARK-1672 VAP initial check in 2018-09-10 12:39:22 -04:00
Eric Harding 63ac084a17 Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2018-09-10 11:24:37 -04:00
Eric Harding 29f7c24a00 Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2018-09-10 10:40:33 -04:00
Taha Afzal 58c298fed8 Changing the AdminService -> OrderService call to async 2018-09-10 10:18:05 -04:00
Taha Afzal ee825f76c7 SPARK-1696 completed 2018-09-09 21:27:54 -04:00
Taha Afzal dfb33efd45 SPARK-1696 util functions added 2018-09-09 11:00:51 -04:00
Taha Afzal dd7ebc54a2 Permissions functions in AminServiceUtils 2018-09-07 19:22:32 -04:00
Kevin Darby 8f99d8d9ca Merge branch 'master' of bitbucket.org:bluetradesys/spark 2018-09-07 17:37:57 -04:00
Kevin Darby c411a0c792 Merge branch 'order-def-templates' of bitbucket.org:bluetradesys/spark 2018-09-07 17:37:17 -04:00
Michael Harding bc8513778b Move Account controls into tab contents 2018-09-07 17:33:20 -04:00
Michael Harding 501dc37e94 F# type system is less resilient than it thinks it is 2018-09-07 17:17:42 -04:00
Michael Harding c58b5fb0e6 SPARK-1678: Cancel orders for account 2018-09-07 17:10:19 -04:00
Michael Harding f89f2b8ad0 Remove Beta from SparkAdmin 2018-09-07 17:10:18 -04:00
Pedro Pinto 5e3e85641d Merged from prod 2018-09-07 21:00:35 +00:00
John Colvin 4a5f71e7f2 fix build 2018-09-07 16:37:23 -04:00
Kevin Darby 92d7503528 cmake? 2018-09-07 16:18:46 -04:00
Kevin Darby 773f3db976 Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2018-09-07 16:17:47 -04:00
Eric Harding dd5b489e8f SPARK-1659 Update producer api to handle implied markets (push early for emumba) 2018-09-07 09:29:41 -04:00
Taha Afzal 7bda1cc1cc Permissions proto and service member added 2018-09-06 21:51:10 -04:00
Eric Harding 58e88812b1 SPARK-1693 - Code quota reached. Have to delete some. 2018-09-06 13:54:02 -04:00
Eric Harding db80631901 Fix "some projects had trouble loading" in vscode 2018-09-06 12:21:05 -04:00
Taha Afzal 593944266d merging 2018-09-06 10:47:29 -04:00
Taha Afzal b8883fc8ae SPARK-1668 reporting stream is done 2018-09-06 10:31:33 -04:00
Taha Afzal efac4a6077 SPARK-1668 foundation 2018-09-06 10:31:33 -04:00
Jake Norbut cb22d21f70 Merged parsing_mics into master 2018-09-06 07:57:15 -05:00
Jake Norbut f6b17d9541 Merge branch 'master' of bitbucket.org:bluetradesys/spark into parsing_mics 2018-09-06 07:56:18 -05:00
Pedro Pinto 7a1298e015 Merge from QA 2018-09-05 18:36:49 -04:00
Jake Norbut 0a81521e6e changing to Taha's proto, not int32, silly man 2018-09-05 13:56:27 -05:00
Jake Norbut 8771281954 Updating script to parse and dump mics 2018-09-05 13:26:50 -05:00
Jake Norbut b50381d386 adding an unset enum state to CMEMics 2018-09-05 12:38:12 -05:00
Jake Norbut 91345cf9d2 renaming cpp_config proto and conf to cme_reference_data 2018-09-05 12:37:25 -05:00
Taha Afzal 19b3ba6615 SPARK-1668 MIC enums added 2018-09-05 12:41:03 -04:00
Taha Afzal 6b68029b6d adding comments to SPARK-1679 protos 2018-09-05 10:03:38 -04:00
Taha Afzal 15c8a8bf28 SPARK-1679 done ! 2018-09-05 09:55:05 -04:00
Taha Afzal 2b2fe02315 SPARK-1679 get_order_summary done 2018-09-04 22:13:25 -04:00
Taha Afzal 1f4c91a344 SPARK-1679 2018-09-04 10:37:45 -04:00
Taha Afzal 92f8f74d8f SPARK-1679 2018-09-04 08:51:50 -04:00
John Colvin ad2869ea6f OrderPropertiesEditor - use id and order-type to match selected orderdef in inlineprops 2018-09-02 13:06:29 -04:00
John Colvin 979506d5be OrderTemplateKey/Header - override ToString 2018-09-02 12:38:13 -04:00
John Colvin bc6e792855 SPARK-1684 ladder/ticket - infrastructure for order definition templates 2018-09-01 21:25:20 -04:00
Eric Harding 5df90f27c8 Update onixs license 2018-08-29 10:47:07 -04:00
Pedro Pinto d571642454 Removed test code from SDK example 2018-08-28 10:21:16 -04:00
Pedro Pinto 0695ddc33e Moved subrepo ptr 2018-08-28 13:24:08 +00:00
Pedro Pinto c97e7675fd Testing SDK 2018-08-28 13:20:56 +00:00
Pedro Pinto 526805680a Updated cmake files 2018-08-24 18:14:24 +00:00
Pedro Pinto 03e3549895 Merged 2018-08-24 13:56:35 -04:00
Pedro Pinto ebbc48771b Merge 2018-08-19 13:26:56 -04:00
Pedro Pinto 2d6fb5c15c cleaned up build 2018-08-19 17:24:40 +00:00
261 changed files with 11138 additions and 3944 deletions

No files matched your search

+2
View File
@@ -24,5 +24,7 @@ project.lock.json
src/server/sparkd/orderman/perf_analysis/data
src/server/sparkd/orderman/perf_analysis/logs/
src/server/sparkd/orderman/perf_analysis/trace_data/
.fake
*.fsx.lock
src/lib/orderman/perf_analysis/logs/latency_data/
trace_data/
+1 -1
View File
@@ -198,7 +198,7 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[md]"],
"args": ["test","Co2RelayTests"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
+3
View File
@@ -1,3 +1,6 @@
1.3.5
- Change in market data wire format. Old shell will get only direct market data instead of merged which may not match what the servers is driving from.
1.3.4
- Changed default logging config for Windows. If Logging config is overridden for a system it should be adjusted to override only linux.
+26
View File
@@ -1,3 +1,29 @@
1.3.5
- Speed Enhanments
- Further speed enhancements
- Net Change pricing
- The ability to price synthetics based off of net change
- Separation of implied and direct market data
- Viewing within a market view will now show 3 columns
- Direct
- Implied
- Merged
- Ability to configure the market data size in a ladder as the following
- Direct
- Implied
- Merged
- Synthetic creation option to choose Merged or Direct
- The ability to choose Merged or Direct per leg when creating a synthetic
- Volume at price
- Within a ladder you can now see a volume at price graph
- This is configurable from a ladder's settings as well
- - Volume at price
- Within a ladder you can now see a volume at price graph
- This is configurable from a ladder's settings as well
- Customized settings in all windows
- All windows now contain a settings parge 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.4
- Significantly reduced client memory use
- Improved OrderBook performance
+59 -55
View File
@@ -4,7 +4,7 @@ VisualStudioVersion = 15.0.27130.2024
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
EndProject
@@ -12,11 +12,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sparkcmd", "src\client\spar
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{9AD1A867-93A9-301D-BF77-0113E18E9E39}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
ProjectSection(ProjectDependencies) = postProject
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3} = {8530E0E8-3768-37EC-B1E1-8248C5A92FE3}
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C} = {69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
@@ -57,22 +57,22 @@ Global
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.Build.0 = Release|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Debug|Any CPU.ActiveCfg = Debug|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Debug|x64.ActiveCfg = Debug|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Debug|x64.Build.0 = Debug|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Debug|x86.ActiveCfg = Debug|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Release|Any CPU.ActiveCfg = Release|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Release|x64.ActiveCfg = Release|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Release|x64.Build.0 = Release|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.Release|x86.ActiveCfg = Release|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{8530E0E8-3768-37EC-B1E1-8248C5A92FE3}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|Any CPU.ActiveCfg = Debug|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|x64.ActiveCfg = Debug|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|x64.Build.0 = Debug|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|x86.ActiveCfg = Debug|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|Any CPU.ActiveCfg = Release|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|x64.ActiveCfg = Release|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|x64.Build.0 = Release|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|x86.ActiveCfg = Release|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|Any CPU.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
@@ -93,26 +93,30 @@ Global
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.Build.0 = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.ActiveCfg = Debug|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.Build.0 = Release|x64
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.Build.0 = Debug|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|Any CPU.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.ActiveCfg = Debug|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.Build.0 = Debug|x64
@@ -133,22 +137,22 @@ Global
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.Build.0 = Release|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Debug|Any CPU.ActiveCfg = Debug|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Debug|x64.ActiveCfg = Debug|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Debug|x64.Build.0 = Debug|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Debug|x86.ActiveCfg = Debug|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Release|Any CPU.ActiveCfg = Release|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Release|x64.ActiveCfg = Release|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Release|x64.Build.0 = Release|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.Release|x86.ActiveCfg = Release|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{9AD1A867-93A9-301D-BF77-0113E18E9E39}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|Any CPU.ActiveCfg = Debug|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|x64.ActiveCfg = Debug|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|x64.Build.0 = Debug|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|x86.ActiveCfg = Debug|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|Any CPU.ActiveCfg = Release|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|x64.ActiveCfg = Release|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|x64.Build.0 = Release|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|x86.ActiveCfg = Release|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|Any CPU.ActiveCfg = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.ActiveCfg = Debug|x64
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.Build.0 = Debug|x64
+2 -2
View File
@@ -19,14 +19,14 @@ namespace mbts.spark {
IReadOnlyDictionary<uint, string>
_account_map = new Dictionary<uint, string>(),
_user_map = new Dictionary<uint, string>();
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(null);
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(new AdminModelWrapper());
IDisposable _dispose;
[ImportingConstructor]
AdminModel(AdminService service, [Import("username")]string user_name) {
_user_name = user_name;
_dispose = service.subscribei_admin_data().Select(update => {
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.FirstOrDefault(), _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
+2 -2
View File
@@ -15,7 +15,7 @@ namespace mbts.spark
public AdminModelWrapper(AdminDataImmutable? admin_data = null, string user_name = null)
{
_admin_data = admin_data;
_user_name = user_name;
_user_name = user_name ?? string.Empty;
_allowed_accounts = search_admin(u => u.Name == _user_name).Select(x => x.Item1).ToList();
foreach(var val in _allowed_accounts)
_allowed_account_dict[val.Name] = val.Name;
@@ -30,7 +30,7 @@ namespace mbts.spark
}
public bool can_view_account(string account)
{
return _allowed_account_dict.ContainsKey(account);
return _allowed_account_dict.ContainsKey(account) || _user_name.ToLower() == "bts";
}
IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> search_admin(Func<BTSUserImmutable, bool> predicate)
@@ -1,5 +1,6 @@
using bts.api.admin;
using bts.api.core;
using bts.api.md.auxiliary;
using bts.api.orderman;
using bts.api.persistence;
using bts.api.position;
@@ -40,5 +41,8 @@ namespace Spark {
[Export]
public PositionService position_service => context.locate<PositionService>();
[Export]
public MarketDataAuxiliaryService md_auxiliary_service => context.locate<MarketDataAuxiliaryService>();
}
}
+7 -4
View File
@@ -20,7 +20,7 @@ namespace mbts.ui {
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
private static Uri uri(string filename) {
return new Uri(base_uri + filename);
}
@@ -94,12 +94,12 @@ namespace mbts.ui {
public static ImageSource CancelBuys { get { return load_resource_and_freeze("CancelBuys"); } }
public static ImageSource CancelSells { get { return load_resource_and_freeze("CancelSells"); } }
public static ImageSource CancelSelected { get { return load_resource_and_freeze("CancelSelected"); } }
public static ImageSource LadderCancelOrders { get { return load_resource_and_freeze("LadderCancelOrders"); } }
public static ImageSource LadderCancelBuys { get { return load_resource_and_freeze("LadderCancelBuys"); } }
public static ImageSource LadderCancelSells { get { return load_resource_and_freeze("LadderCancelSells"); } }
public static ImageSource Synthetics { get { return load_resource_and_freeze("Synthetics"); } }
public static ImageSource Synthetics { get { return load_resource_and_freeze("Synthetics"); } }
public static ImageSource AddSynthetic { get { return load_resource_and_freeze("AddSynthetic"); } }
public static ImageSource RemoveSynthetic { get { return load_resource_and_freeze("RemoveSynthetic"); } }
public static ImageSource ModifySynthetic { get { return load_resource_and_freeze("ModifySynthetic"); } }
@@ -126,7 +126,10 @@ namespace mbts.ui {
public static ImageSource Wrench { get { return load_resource_and_freeze("Wrench"); } }
public static ImageSource RightArrow { get { return (load_resource_and_freeze("RightArrow")); } }
public static ImageSource DownArrow { get { return (load_resource_and_freeze("DownArrow")); } }
public static ImageSource Support => load_resource_and_freeze("SupportIcon");
public static ImageSource Factory { get { return (load_resource_and_freeze("Factory")); } }
public static ImageSource DotsVertical { get { return (load_resource_and_freeze("DotsVertical")); } }
public static ImageSource HandPointingOverDocument => load_resource_and_freeze("HandPointingOverDocument");
public static ImageSource Support => load_resource_and_freeze("SupportIcon");␍
public static ImageSource create_icon(string geometric_path_key) =>
create_icon(geometric_path_key, Brushes.Black);
+41 -1
View File
@@ -813,8 +813,48 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="Factory" x:Shared="False">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
Brush="Black"
Geometry="M21.832001,24.424L21.832001,27.912998 26.218018,27.912998 26.218018,24.424z M13.757019,24.424L13.757019,27.912998 18.143005,27.912998 18.143005,24.424z M5.7820129,24.424L5.7820129,27.912998 10.167999,27.912998 10.167999,24.424z M1.8950195,6.3799964L3.8880005,6.3799964 4.1870117,17.745001 8.1750183,17.745001 8.1750183,13.956 16.050018,17.745001 16.149994,17.745001 16.149994,13.956 24.024994,17.745001 24.125,17.745001 24.125,13.956 32,17.745001 31.901001,17.745001 31.901001,31.103001 0,31.103001 0,17.745001 1.5950012,17.745001z M8.5740051,1.3959956C8.5740051,1.3959958 9.1719971,3.2899927 6.6800232,3.5889963 6.6800232,3.5889963 5.085022,3.6879953 4.2870178,4.4859993 4.2870178,4.4859993 3.9880066,4.7849952 3.8880005,4.4859993 3.8880005,4.4859993 3.9880066,3.0899958 6.480011,2.6919931 6.5800171,2.7909998 8.3740234,2.6919931 8.5740051,1.3959956z M5.5830078,0C5.5830078,-1.4470834E-07 6.8789978,1.4949948 4.5859985,2.6919931 4.5859985,2.6919931 3.1900024,3.2899927 2.6920166,4.3859931 2.6920166,4.3859931 2.5920105,4.7849952 2.2929993,4.4859993 2.2929993,4.4859993 1.8950195,3.1899942 4.0880127,1.8939967 4.1870117,1.9939953 5.882019,1.2959973 5.5830078,0z"
/>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<!-- Pin
<DrawingImage x:Key="DotsVertical" x:Shared="False">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
Brush="Black"
Geometry="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"
/>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="HandPointingOverDocument" x:Shared="False">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
Brush="Black"
Geometry="M17.740002,11.999007C17.254002,11.999007,16.859001,12.394008,16.859001,12.879008L16.859001,24.458791 16.860127,24.479386C16.861003,24.607261 16.813003,24.735511 16.716002,24.834012 16.667502,24.883012 16.611378,24.91995 16.551394,24.944762L16.521562,24.955436 16.507633,24.960536 16.506465,24.960837 16.50574,24.961097 16.49194,24.964574 16.459728,24.972863C16.42719,24.979527,16.393502,24.983027,16.359002,24.983027L16.316763,24.980893 16.315716,24.980851 16.307903,24.980445C16.223895,24.971904,16.146014,24.942538,16.07951,24.897597L16.067752,24.887895 16.047276,24.873078 16.015234,24.844558 16.005502,24.836528 16.001236,24.831357 13.737002,22.600011C13.267003,22.134011 12.502003,22.135011 12.031003,22.600011 11.805003,22.82301 11.680003,23.120011 11.680003,23.436011 11.680003,23.751012 11.805003,24.048011 12.031003,24.272012L16.842003,29.031013C19.311003,31.189013 21.546002,31.119014 22.656003,30.886014 23.939002,30.614013 25.015002,29.846013 25.464002,28.880013 25.857001,28.036012 26.301002,27.080012 26.263001,26.195012 26.262002,26.178012 26.268003,26.074012 26.269002,26.056012L26.268003,20.974011C26.268003,20.55601 25.922002,20.215011 25.496001,20.215011 25.335502,20.215011 25.239001,20.246511 25.179877,20.283135L25.144,20.309762 25.144,21.215013C25.144,21.491012 24.919999,21.715013 24.644,21.715013 24.367999,21.715013 24.144,21.491012 24.144,21.215013L24.144,19.278017C24.144,18.793018 23.749,18.399019 23.263,18.399019 22.808312,18.399019 22.432727,18.745308 22.387555,19.18822L22.383004,19.277944 22.383004,19.716007C22.383004,19.992006 22.159003,20.216004 21.883002,20.216004 21.607003,20.216004 21.383002,19.992006 21.383002,19.716007L21.383002,19.278054 21.383002,18.504013C21.383002,18.018016 20.988002,17.623017 20.502002,17.623017 20.016002,17.623017 19.621001,18.018016 19.621001,18.504013L19.621001,20.24402C19.621001,20.52102 19.397001,20.74402 19.121001,20.74402 18.844001,20.74402 18.621001,20.52102 18.621001,20.24402L18.621001,18.504035 18.621001,12.879008C18.621001,12.394008,18.226001,11.999007,17.740002,11.999007z M17.740002,10.999005C18.777001,10.999005,19.621001,11.843007,19.621001,12.879008L19.621001,16.842839 19.687315,16.808824C19.933955,16.689783 20.210345,16.623022 20.502002,16.623022 21.247346,16.623022 21.892987,17.059031 22.197202,17.689331L22.211675,17.721288 22.212884,17.720384C22.513,17.517568 22.874499,17.399021 23.263,17.399021 24.267594,17.399021 25.091062,18.190155 25.141547,19.181478L25.143222,19.247425 25.174827,19.241074C25.276639,19.223799 25.383876,19.21501 25.496001,19.21501 26.473002,19.21501 27.268003,20.00401 27.268003,20.974011L27.268003,26.087012C27.268003,26.098012 27.262002,26.194013 27.262002,26.205012 27.311002,27.281013 26.790002,28.401013 26.371001,29.302012 25.783002,30.566012 24.472001,31.523013 22.863002,31.864013 22.513001,31.938014 22.067002,31.999013 21.547002,31.999013 20.149003,31.999013 18.216002,31.561013 16.161002,29.763013L11.328003,24.983012C10.910003,24.570012 10.680003,24.020011 10.680003,23.436011 10.680003,22.85101 10.910003,22.302011 11.328003,21.88901 12.187003,21.040011 13.583003,21.041011 14.440002,21.88901L15.859002,23.287391 15.859002,12.879008C15.859002,11.843007,16.703002,10.999005,17.740002,10.999005z M6.8659983,4.0500388L19.083997,4.0500388C20.668996,4.0500388,21.957997,5.3460296,21.957997,6.9390478L21.957997,14.653042C21.957997,14.93005 21.734998,15.153042 21.457997,15.153042 21.181996,15.153042 20.957997,14.93005 20.957997,14.653042L20.957997,6.9390478C20.957997,5.8980318,20.116998,5.0500394,19.083997,5.0500394L6.8659983,5.0500394C5.8319979,5.0500394,4.9909983,5.8980318,4.9909984,6.9390478L4.9909984,26.116061C4.9909983,27.157046,5.8319979,28.00504,6.8659983,28.00504L11.696996,28.00504C11.972997,28.00504 12.196996,28.229039 12.196996,28.50504 12.196996,28.781041 11.972997,29.00504 11.696996,29.00504L6.8659983,29.00504C5.2809954,29.00504,3.9909983,27.70905,3.9909983,26.116061L3.9909983,6.9390478C3.9909983,5.3460296,5.2809954,4.0500388,6.8659983,4.0500388z M6.4360008,0L17.467001,0C17.743001,0 17.967001,0.22300029 17.967001,0.50000024 17.967001,0.77600098 17.743001,1.0000012 17.467001,1.0000012L6.4360008,1.0000012C3.4380004,1.0000012,1.0000002,3.4210042,1.0000002,6.3970079L1.0000002,20.617026C1.0000002,20.893025 0.77600026,21.117026 0.50000048,21.117026 0.22400022,21.117026 2.3841858E-07,20.893025 0,20.617026L0,6.3970079C2.3841858E-07,2.8700035,2.8870004,0,6.4360008,0z"
/>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<!-- Pin
<Path Data="F1M3,11L3,7 0,7 0,6 1,6 1,1 1,0 6,0 6,1 6,6 7,6 7,7 4,7 4,11 3,11z M2,1L2,6 4,6 4,1 2,1z" />
Unpin
<Path Data="F1M0,3L4,3 4,0 5,0 5,1 10,1 11,1 11,6 10,6 5,6 5,7 4,7 4,4 0,4 0,3z M10,2L5,2 5,4 10,4 10,2z" />
@@ -0,0 +1,33 @@
using bts.api.core;
using bts.api.md.auxiliary;
using mbts.utils;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Concurrency;
namespace mbts.spark {
[Export]
class MarketDataAuxiliaryModel : IDisposable {
readonly MarketDataAuxiliaryService _service;
readonly TableUpdateDemuxer<VolumeAtPriceLevelImmutable, int, uint> _security_orders_demuxer;
IDisposable _dispose;
[ImportingConstructor]
MarketDataAuxiliaryModel(MarketDataAuxiliaryService service) {
_service = service;
var scheduler = ThreadPoolScheduler.Instance;
var interval = TimeSpan.FromMilliseconds(100);
volume_at_price = service.subscribei_volume_at_price().Naggle(interval, scheduler).Table().Cache();
_security_orders_demuxer = new TableUpdateDemuxer<VolumeAtPriceLevelImmutable, int, uint>(o => o.RecordId, v => v.SecurityId);
_dispose = volume_at_price.AddRemoveOnly().Subscribe(_security_orders_demuxer);
}
public IObservable<TableUpdate<VolumeAtPriceLevelImmutable, int>> volume_at_price { get; }
public IObservable<ITableUpdateTransform<VolumeAtPriceLevelImmutable, int>> security_volume_at_price(uint security) =>
_security_orders_demuxer.subscribe(security);
public void Dispose() =>
Disposable.dispose(ref _dispose);
}
}
@@ -2,6 +2,7 @@
using bts.spark;
using mbts;
using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using System.Collections.Generic;
@@ -17,9 +18,9 @@ namespace bts.shell
class MarketShellModule : IShellModule
{
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, OrdersModel orders_model, [Import("username")]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer);
_mvf = new MarketViewWindowFactory(this, bus, orders_model);
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
TimeAndSalesWindowFactory _tsf;
@@ -5,11 +5,9 @@ using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using bts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -18,7 +16,6 @@ using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
namespace mbts.spark {
partial class MarketViewVM {
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
@@ -29,7 +26,6 @@ namespace mbts.spark {
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
readonly string _security_text, _security_key_text;
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
readonly Property<uint> _bid_size, _ask_size;
readonly CellProperty<int> _position;
readonly CellProperty<uint> _last_size;
readonly CellProperty<ulong> _volume;
@@ -38,6 +34,9 @@ namespace mbts.spark {
readonly CellProperty<double> _working_best_bid, _working_best_ask;
readonly CellProperty<string> _working_bids_text, _working_asks_text;
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
uint _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
string _bid_merged_size_text, _ask_merged_size_text;
ulong _bbo_ref, _last_ref;
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
@@ -49,8 +48,6 @@ namespace mbts.spark {
_position = new CellProperty<int>(0);
_bid_price = backing(nameof(bid_price), double.NaN);
_ask_price = backing(nameof(ask_price), double.NaN);
_bid_size = backing<uint>(nameof(bid_size), 0);
_ask_size = backing<uint>(nameof(ask_size), 0);
_last_price = backing(nameof(last_price), double.NaN);
_last_size = new CellProperty<uint>(0);
_day_change = backing(nameof(day_change), double.NaN);
@@ -73,10 +70,14 @@ namespace mbts.spark {
//
default_command = new DelegateCommand(_show_ladder);
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
bid_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidSize));
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
ask_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskSize));
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
_animation.AnimationsChanged += on_animations_changed;
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
@@ -99,9 +100,15 @@ namespace mbts.spark {
public double last_price => _last_price.Value;
public uint last_size => _last_size.value;
public double bid_price => _bid_price.Value;
public uint bid_size => _bid_size.Value;
public uint bid_direct_size => _bid_direct_size;
public uint bid_implied_size => _bid_merged_size - _bid_direct_size;
public uint bid_merged_size => _bid_merged_size;
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size, bid_implied_size));
public double ask_price => _ask_price.Value;
public uint ask_size => _ask_size.Value;
public uint ask_direct_size => _ask_direct_size;
public uint ask_implied_size => _ask_merged_size - _ask_direct_size;
public uint ask_merged_size => _ask_merged_size;
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size, ask_implied_size));
public ulong volume => _volume.value;
public double settlement => _settlement.Value;
public double day_change => _day_change.Value;
@@ -115,10 +122,14 @@ namespace mbts.spark {
public double working_best_ask => _working_best_ask.value;
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
public ICommand bid_price_click { get; }
public ICommand bid_size_click { get; }
public ICommand bid_merged_size_click { get; }
public ICommand bid_direct_size_click { get; }
public ICommand bid_implied_size_click { get; }
public ICommand working_bids_click { get; }
public ICommand ask_price_click { get; }
public ICommand ask_size_click { get; }
public ICommand ask_merged_size_click { get; }
public ICommand ask_direct_size_click { get; }
public ICommand ask_implied_size_click { get; }
public ICommand working_asks_click { get; }
public ICommand default_command { get; }
@@ -133,9 +144,9 @@ namespace mbts.spark {
bool dirty = false;
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
_bid_price.set(b.price);
_bid_size.set(b.size);
refresh_size(ref b, ref _bid_direct_size, ref _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
_ask_price.set(a.price);
_ask_size.set(a.size);
refresh_size(ref a, ref _ask_direct_size, ref _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
}
@@ -150,6 +161,16 @@ namespace mbts.spark {
_day_change.set(last_price - settlement);
if (dirty)
invalidated?.Invoke();
void refresh_size(ref NativePriceSize nps, ref uint direct, ref uint merged, ref string text, string text_prop) {
if (nps.size != merged || nps.direct_size != direct) {
merged = nps.size;
direct = nps.direct_size;
text = null;
dirty = true;
RaisePropertyChanged(text_prop);
}
}
}
public bool Equals(Row other) =>
@@ -187,17 +208,33 @@ namespace mbts.spark {
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, 0u));
break;
case MarketViewView.Columns.BidSize:
case MarketViewView.Columns.BidMergedSize:
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_size));
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_merged_size));
break;
case MarketViewView.Columns.BidDirectSize:
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_direct_size));
break;
case MarketViewView.Columns.BidImpliedSize:
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, bid_price, bid_implied_size));
break;
case MarketViewView.Columns.Ask:
order.Side = Side.Buy;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, 0u));
break;
case MarketViewView.Columns.AskSize:
case MarketViewView.Columns.AskMergedSize:
order.Side = Side.Buy;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_size));
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_merged_size));
break;
case MarketViewView.Columns.AskDirectSize:
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_direct_size));
break;
case MarketViewView.Columns.AskImpliedSize:
order.Side = Side.Sell;
_bus.post(SparkTags.ShowOrderTicketTag, (order, ask_price, ask_implied_size));
break;
case MarketViewView.Columns.Last:
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
@@ -1,10 +1,13 @@
using bts.api;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
@@ -17,65 +20,79 @@ namespace mbts.spark {
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state)
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus))) { }
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
: 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)
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) {
_bus = bus;
_orders_model = orders_model;
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
_settings_manager = settings_manager;
// Columns
const string size_col_header = "Qty", working_col_header = "Working";
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
const double working_orders_col_width = 105;
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), 64, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UShortMinMax);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid);
add_hl_size_col(Columns.BidSize, size_col_header, nameof(Row.bid_size), nameof(Row.bid_size_click), bid_size_filter, BTSColors.MarketData.bid);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid_brush);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask_brush);
add_hl_size_col(Columns.AskSize, size_col_header, nameof(Row.ask_size), nameof(Row.ask_size_click), ask_size_filter, BTSColors.MarketData.ask);
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask);
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Price, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true);
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UShortMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false) =>
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
// it's necessary to have this id populated for grid settings
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
Header = header,
sort_member_path = prop,
Width = width,
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
font_color_override = font_color,
horizontal_alignment = h_alignment
});
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Brush background) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price, foreground, background, DefaultSelectionHighlight);
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Brush background, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, DefaultSelectionHighlight, h_alignment);
void add_hl_size_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground) =>
add_hl_col(col_key, header, new Binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.UShortMinMax, DefaultColumnWidths.Size, foreground, null, null);
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UShortMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null);
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null, h_alignment);
void add_hl_col(
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Brush background, Brush background_overlay) =>
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Brush background, Brush background_overlay, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new HyperlinkElementColumn<Row, Columns>(
col_key, binding, new Binding(command_prop), filter, filter_template) {
// it's necessary to have this id populated for grid settings
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
Header = header,
sort_member_path = prop,
font_color_override = foreground,
background = background,
background_overlay = background_overlay,
Width = DefaultColumnWidths.Price
Width = DefaultColumnWidths.Price,
horizontal_alignment = h_alignment
});
BindingBase price_binding(string prop) {
@@ -85,17 +102,41 @@ namespace mbts.spark {
return mb;
}
//
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
//
_resources.AddRange(
state.OnDispatcher().Subscribe(s => {
using (rows_view.DeferRefresh()) {
rows_view.SetSortingCriteria(s.sorting_criteria);
var filters = s.filters;
if (filters != null) {
if (s.grid != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
if (s.filters is FilterStates filters) {
security_filter.load_state(filters.security);
bid_price_filter.load_state(filters.bid_price);
bid_size_filter.load_state(filters.bid_size);
bid_merged_size_filter.load_state(filters.bid_merged_size);
bid_direct_size_filter.load_state(filters.bid_direct_size);
bid_implied_size_filter.load_state(filters.bid_implied_size);
ask_price_filter.load_state(filters.ask_price);
ask_size_filter.load_state(filters.ask_size);
ask_merged_size_filter.load_state(filters.ask_merged_size);
ask_direct_size_filter.load_state(filters.ask_direct_size);
ask_implied_size_filter.load_state(filters.ask_implied_size);
last_price_filter.load_state(filters.last_price);
last_size_filter.load_state(filters.last_size);
day_change_filter.load_state(filters.day_change);
@@ -108,23 +149,40 @@ namespace mbts.spark {
volume_filter.load_state(filters.volume);
security_key_filter.load_state(filters.security_key);
}
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
}
}), DataGridFilterHelper.hook_filters(rows_view,
security_filter, bid_price_filter, bid_size_filter, ask_price_filter, ask_size_filter, last_price_filter, last_size_filter, day_change_filter, settlement_filter,
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_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)
);
}
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
#region Filters
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_size, nameof(Row.bid_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_size, nameof(Row.ask_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
@@ -141,9 +199,13 @@ namespace mbts.spark {
return new FilterStates {
security = security_filter.get_state(),
bid_price = bid_price_filter.get_state(),
bid_size = bid_size_filter.get_state(),
bid_merged_size = bid_merged_size_filter.get_state(),
bid_direct_size = bid_direct_size_filter.get_state(),
bid_implied_size = bid_implied_size_filter.get_state(),
ask_price = ask_price_filter.get_state(),
ask_size = ask_size_filter.get_state(),
ask_merged_size = ask_merged_size_filter.get_state(),
ask_direct_size = ask_direct_size_filter.get_state(),
ask_implied_size = ask_implied_size_filter.get_state(),
last_price = last_price_filter.get_state(),
last_size = last_size_filter.get_state(),
day_change = day_change_filter.get_state(),
@@ -21,6 +21,7 @@
Columns="{Binding columns}"
CurrentRow="{Binding current_row}"
Rows="{Binding rows_view}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
@@ -8,7 +8,7 @@ namespace mbts.ui.spark {
/// Interaction logic for MarketSheetsView.xaml
/// </summary>
partial class MarketViewView : UserControl {
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidSize, Ask, AskSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey }
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey }
public MarketViewView() {
InitializeComponent();
@@ -2,8 +2,11 @@
using bts.api;
using bts.shell;
using bts.spark;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -16,16 +19,18 @@ namespace mbts.spark {
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
readonly IWindowSettingsManager<GridSettings> _settings_manager;
MarketViewVM _vm;
MarketViewView _view;
MarketViewWindowFactory.StartupData _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state)
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
}
@@ -34,15 +39,21 @@ namespace mbts.spark {
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override MarketViewWindowFactory.StartupData SaveState => _state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states());
public override double MinWidth => 1035 + SPARK_MENU_COLLAPSED_WIDTH + LAUNCHER_BUTTON_BORDER_WIDTH;
public override MarketViewWindowFactory.StartupData SaveState =>
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id => _thread_id;
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
_commands = new List<SparkCommand> {
@@ -55,10 +66,28 @@ namespace mbts.spark {
),
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria))
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<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()
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_subscriptions = _bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())));
}
public override void Dispose() {
@@ -67,4 +96,4 @@ namespace mbts.spark {
Commands.OfType<IDisposable>().DisposeAll();
}
}
}
}
@@ -1,31 +1,47 @@
using bts.api;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.MarketViewView;
namespace bts.shell {
class MarketViewWindowFactory : ShellWindowFactory<MarketViewWindowFactory.StartupData> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
public MarketViewWindowFactory(MarketShellModule module, IBus bus, OrdersModel orders_model) : base(module, "MarketView") {
public MarketViewWindowFactory(MarketShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "MarketView") {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
}
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(StartupData state) =>
Task.FromResult(_create(state));
void _show(SecurityCriteria criteria) =>
_bus.post(SparkTags.ShowWindow, _create(new StartupData(criteria, new List<SortingCriteria> { new SortingCriteria(nameof(MarketViewVM.Row.security), true) }, null)));
_bus.post(SparkTags.ShowWindow, _create(StartupData.Default(criteria)));
IShellWindow _create(StartupData state) => new MarketViewWindow(this, _bus, _orders_model, state);
IShellWindow _create(StartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.market_view_grid,
set: (us, gs) => us.with(provided_market_view_gridsettings: gs));
return new MarketViewWindow(this, _bus, _orders_model, state, settings_manager);
}
[ProtoContract]
public class StartupData {
@@ -34,20 +50,89 @@ namespace bts.shell {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
public readonly List<SortingCriteria> sorting_criteria;
List<SortingCriteria> _sorting_criteria;
[ProtoMember(3)]
public readonly FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
StartupData() { }
public StartupData() { }
public StartupData(SecurityCriteria security_criteria)
: this (security_criteria, new List<SortingCriteria>(), null) { }
: this (security_criteria, new FilterStates()) { }
public StartupData(SecurityCriteria security_criteria, List<SortingCriteria> sorting_criteria, FilterStates filters) {
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sort = null) {
this.security_criteria = security_criteria;
this.sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criteria = sort;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, new FilterStates(), null);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings volume => new GridColumnSettings(col_id(Columns.Volume), DefaultColumnHeaders.Volume, width: DefaultColumnWidths.Volume);
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings working_bids => new GridColumnSettings(col_id(Columns.WorkingBids), DefaultColumnHeaders.Working, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.Working, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_implied_size => new GridColumnSettings(col_id(Columns.BidImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_direct_size => new GridColumnSettings(col_id(Columns.BidDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_merged_size => new GridColumnSettings(col_id(Columns.BidMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_merged_size => new GridColumnSettings(col_id(Columns.AskMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_direct_size => new GridColumnSettings(col_id(Columns.AskDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_implied_size => new GridColumnSettings(col_id(Columns.AskImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings working_asks => new GridColumnSettings(col_id(Columns.WorkingAsks), DefaultColumnHeaders.Working, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.Working, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings settlement => new GridColumnSettings(col_id(Columns.Settlement), DefaultColumnHeaders.Settlement, width: DefaultColumnWidths.Settlement, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_change => new GridColumnSettings(col_id(Columns.DayChange), DefaultColumnHeaders.DayChange, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings session_low => new GridColumnSettings(col_id(Columns.SessionLow), DefaultColumnHeaders.SessionLow, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings session_high => new GridColumnSettings(col_id(Columns.SessionHigh), DefaultColumnHeaders.SessionHigh, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(security.id, true);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return volume;
yield return last;
yield return last_size;
yield return position;
yield return working_bids;
yield return bid_implied_size;
yield return bid_direct_size;
yield return bid_merged_size;
yield return bid;
yield return ask;
yield return ask_merged_size;
yield return ask_direct_size;
yield return ask_implied_size;
yield return working_asks;
yield return settlement;
yield return day_change;
yield return session_low;
yield return session_high;
yield return security_key;
}
}
}
}
[ProtoContract]
@@ -57,11 +142,11 @@ namespace bts.shell {
[ProtoMember(2)]
public DoubleMinMaxState bid_price;
[ProtoMember(3)]
public UIntMinMaxState bid_size;
public UIntMinMaxState bid_merged_size;
[ProtoMember(4)]
public DoubleMinMaxState ask_price;
[ProtoMember(5)]
public UIntMinMaxState ask_size;
public UIntMinMaxState ask_merged_size;
[ProtoMember(6)]
public DoubleMinMaxState last_price;
[ProtoMember(7)]
@@ -84,6 +169,14 @@ namespace bts.shell {
public ULongMinMaxState volume;
[ProtoMember(16)]
public string security_key;
[ProtoMember(17)]
public UIntMinMaxState bid_direct_size;
[ProtoMember(18)]
public UIntMinMaxState bid_implied_size;
[ProtoMember(19)]
public UIntMinMaxState ask_direct_size;
[ProtoMember(20)]
public UIntMinMaxState ask_implied_size;
}
}
}
@@ -19,11 +19,14 @@ namespace mbts.spark {
}
class TimeAndSalesModel : IDisposable {
internal readonly SecurityCriteria security_criteria;
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
IDisposable _dispose;
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
this.security_criteria = security_criteria;
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
var copy = curr_channels;
@@ -3,7 +3,6 @@ using bts.co2;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
@@ -15,17 +14,25 @@ using System.Windows.Input;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
using mbts.persist;
using mbts.ui.settings;
using System.Reactive.Linq;
namespace mbts.spark {
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
readonly IBus _bus;
readonly TimeAndSalesModel _model;
IWindowSettingsManager<GridSettings> _settings_manager;
public TimeAndSalesVM(IBus bus, TimeAndSalesModel model, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
: base(RXCollection.CreateBuffer(model.trades, 1000), sorting_criteria) {
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
_bus = bus;
_model = model;
_settings_manager = settings_manager;
show_ladder = DelegateCommandParamObserver.Create(current_row_observable, r => _show_ladder_async(r).ignore(), r => r != null);
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
if (filters != null) {
if (startup_data.filters is FilterStates filters) {
time_filter.load_state(filters.time);
security_filter.load_state(filters.security);
price_filter.load_state(filters.price);
@@ -35,33 +42,49 @@ namespace mbts.spark {
security_key_filter.load_state(filters.security_key);
}
// Columns
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DIYGrid.DefaultColumnWidths.Aggressor, aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.TradeKey, DefaultColumnHeaders.TradeKey, DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, nameof(Row.security), security_filter, DataGridFilterKnownTemplates.Regex);
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, nameof(Row.time), time_filter, DataGridFilterKnownTemplates.TimeFilter);
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.TradeKey, DefaultColumnHeaders.TradeKey, DefaultColumnWidths.TradeKey, nameof(Row.trade_key), trade_key_filter, DataGridFilterKnownTemplates.Regex);
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(
r => r[col_key]
col_key
, (r) => _get_aggressor_brush(r)
, filter
, filter_template
)
{ Header = header, Width = width, horizontal_alignment = h_alignment }
{ Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
// if the collection is not populated, the column settings will not be applied
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
show_ladder,
DataGridFilterHelper.hook_filters(
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, trade_key_filter, security_key_filter
)
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, trade_key_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
}
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public DelegateCommandParamObserver<Row> show_ladder { get; }
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
public StartupData create() =>
new StartupData(_model.security_criteria, _get_filter_states(), get_grid_save_state(), rows_view.get_sorting_criteria(columns));
private Brush _get_aggressor_brush(Row r) {
BTSColor c;
@@ -82,7 +105,7 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, string> trade_key_filter { get; } = new RegexFilterVM<Row>(r => r.trade_key.ToString(), nameof(Row.trade_key));
public FilterStates get_filter_states() =>
FilterStates _get_filter_states() =>
new FilterStates {
time = time_filter.get_state(),
security = security_filter.get_state(),
@@ -18,7 +18,10 @@
</UserControl.Resources>
<AdornerDecorator>
<Grid>
<c:DIYGrid Columns="{Binding columns}" Rows="{Binding rows_view}" CurrentRow="{Binding current_row}">
<c:DIYGrid Columns="{Binding columns}"
Rows="{Binding rows_view}" CurrentRow="{Binding current_row}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
@@ -3,8 +3,11 @@ using bts.api;
using bts.shell;
using bts.spark;
using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -20,6 +23,7 @@ namespace mbts.spark {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
SingleValuePump<TimeAndSalesVM> _vm;
TimeAndSalesModel _model;
StartupData _state;
@@ -27,29 +31,25 @@ namespace mbts.spark {
SparkCommand[] _commands;
IDisposable _subscriptions;
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon)
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager)
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
Log.assert(state != null);
_consumer = consumer;
_factory = factory;
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
}
public override string window_type => _window_type;
public override IEnumerable<SparkCommand> Commands => _commands;
public override UIElement Content => _view;
public override double MinWidth => DefaultColumnWidths.Security
+ DefaultColumnWidths.Time
+ DefaultColumnWidths.Price
+ DefaultColumnWidths.Size
+ DefaultColumnWidths.Aggressor
+ DefaultColumnWidths.TradeKey
+ DefaultColumnWidths.SecurityKey
public override double MinWidth => StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH;
public override StartupData SaveState => _state = new StartupData(_state.security_criteria, _vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public override StartupData SaveState => _state = _vm.value.create();
public override void Dispose() =>
_dispose_resources();
@@ -66,7 +66,25 @@ namespace mbts.spark {
SparkCommands.ShowOrderTicketFromSecurity.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria))
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>(
_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()
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_subscriptions = Disposable.Create(
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
@@ -79,7 +97,7 @@ namespace mbts.spark {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
});
void update_criteria(SecurityCriteria c) {
_state = new StartupData(c, _vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
_state = _vm.value.create().with(c);
_initialize();
}
}
@@ -98,7 +116,7 @@ namespace mbts.spark {
Disposable.dispose(ref _model);
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
_vm.value = new TimeAndSalesVM(_bus, _model, _state.sorting_criteria, _state.filters);
_vm.value = new TimeAndSalesVM(_bus, _settings_manager, _model, _state);
if (_view == null)
_view = new TimeAndSalesView();
_view.DataContext = _vm.value;
@@ -2,24 +2,31 @@
using bts.spark;
using mbts;
using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.TimeAndSalesView;
namespace bts.shell {
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesWindowFactory.StartupData> {
readonly IBus _bus;
readonly MDConsumer _consumer;
readonly UserSettingsHub _settings_hub;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer) : base(module, "TimeAndSales") {
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
_bus = bus;
_consumer = consumer;
_settings_hub = settings_hub;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
@@ -27,8 +34,11 @@ namespace bts.shell {
);
}
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(StartupData state) =>
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon));
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
public async void show(SecurityCriteria criteria) {
var w = await load(new StartupData(criteria));
@@ -40,19 +50,60 @@ namespace bts.shell {
[ProtoMember(1)]
public readonly SecurityCriteria security_criteria;
[ProtoMember(2)]
public readonly List<SortingCriteria> sorting_criteria;
List<SortingCriteria> _sorting_criterion;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
StartupData() { }//for ptorobuf
public StartupData() { }
public StartupData(SecurityCriteria security_criteria)
: this (security_criteria, new List<SortingCriteria>(), null) { }
public StartupData(SecurityCriteria security_criteria) :
this(security_criteria, new FilterStates(), null, null) { }
public StartupData(SecurityCriteria security_criteria, List<SortingCriteria> sorting_criteria, FilterStates filters) {
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
this.security_criteria = security_criteria;
this.sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
_sorting_criterion = sorting_criterion;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
public StartupData with(SecurityCriteria security_criteria) =>
new StartupData(security_criteria, filters, grid);
public static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings time => new GridColumnSettings(col_id(Columns.Time), DefaultColumnHeaders.Time, width: DefaultColumnWidths.Time);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings size => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings aggressor => new GridColumnSettings(col_id(Columns.Aggressor), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
public static GridColumnSettings trade_key => new GridColumnSettings(col_id(Columns.TradeKey), DefaultColumnHeaders.TradeKey, width: DefaultColumnWidths.TradeKey);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return time;
yield return price;
yield return size;
yield return aggressor;
yield return trade_key;
yield return security_key;
}
}
}
}
@@ -46,27 +46,22 @@
Offset="{StaticResource RowHeight}"
RowHeaderWidth="{StaticResource RowHeaderWidth}"
/>
<usp:DataGridParameterCellTemplateConverter x:Key="ParameterCellTemplateConverter">
<DataTemplate x:Key="SimpleValueTextTemplate">
<TextBlock Text="{Binding value}"/>
</DataTemplate>
<usp:DataGridParameterCellTemplateConverter
x:Key="ParameterCellTemplateConverter"
DoubleTemplate="{StaticResource SimpleValueTextTemplate}"
IntTemplate="{StaticResource SimpleValueTextTemplate}"
UIntTemplate="{StaticResource SimpleValueTextTemplate}"
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
>
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Left" IsChecked="{Binding value}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
<usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
<DataTemplate>
<TextBlock Text="{Binding value}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
<usp:DataGridParameterCellTemplateConverter.IntTemplate>
<DataTemplate>
<TextBlock Text="{Binding value}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.IntTemplate>
<usp:DataGridParameterCellTemplateConverter.UIntTemplate>
<DataTemplate>
<TextBlock Text="{Binding value}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
<usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
<DataTemplate>
<Grid>
@@ -117,6 +112,16 @@
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
<usp:DataGridParameterCellTemplateConverter.MarketDataCalculationTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.LegMarketDataCalculation}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.MarketDataCalculationTemplate>
<usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
<DataTemplate>
<Grid IsEnabled="{Binding enabled}">
@@ -302,6 +302,8 @@ namespace mbts.ui.spark {
public DataTemplate IntTemplate { get; set; }
public DataTemplate UIntTemplate { get; set; }
public DataTemplate QuantityMeasureTemplate { get; set; }
public DataTemplate MarketDataTypeTemplate { get; set; }
public DataTemplate MarketDataCalculationTemplate { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
Type t = (Type)value;
@@ -310,6 +312,8 @@ namespace mbts.ui.spark {
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(LegSpreaderParams.Types.LegMarketDataCalculation)) return MarketDataCalculationTemplate;
else return null;
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.spark;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
@@ -18,8 +19,8 @@ namespace bts.shell
LatencyReportWindowFactory _lrwf;
[ImportingConstructor]
public SpreaderShellModule(IBus bus, OrdersModel orders_model) {
_smwf = new SyntheticManagementWindowFactory(this, bus, orders_model);
public SpreaderShellModule(IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) {
_smwf = new SyntheticManagementWindowFactory(this, bus, settings_hub, orders_model);
_lrwf = new LatencyReportWindowFactory(this, bus);
}
@@ -1,8 +1,12 @@
<UserControl x:Class="mbts.ui.spark.SyntheticManagementView" 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">
<UserControl
x:Class="mbts.ui.spark.SyntheticManagementView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:c="clr-namespace:mbts.ui.controls"
Background="{StaticResource CommonBackground}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -10,71 +14,31 @@
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<c:DataGridEx
x:Name="grid" BorderThickness="0" IsReadOnly="True" CanUserSortColumns="True"
GridLinesVisibility="None" ItemsSource="{Binding rows_view}"
CurrentItem="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="u:AttachedCommands.DoubleClick" Value="{Binding show_ladder}"/>
<Setter Property="u:AttachedCommands.Enter" Value="{Binding show_ladder}"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<!--<DataGridTextColumn
Header="Create Time" Binding="{Binding create_time}"
c:DataGridFilterProperties.Filter="{Binding DataContext.create_time_filter, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.create_time_filter.active, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource TimeFilterTemplate}"
/>-->
<DataGridTextColumn
Header="Name" Binding="{Binding name}"
c:DataGridFilterProperties.Filter="{Binding DataContext.name_filter, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.name_filter.active, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.name_filter.open, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
/>
<DataGridTextColumn
Header="Description" Binding="{Binding description}"
c:DataGridFilterProperties.Filter="{Binding DataContext.description_filter, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.description_filter.active, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.description_filter.open, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
/>
<DataGridTextColumn
Header="# Legs" Binding="{Binding n_legs}"
c:DataGridFilterProperties.Filter="{Binding DataContext.n_legs_filter, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.n_legs_filter.active, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.n_legs_filter.open, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource IntMinMaxTemplate}"
/>
<DataGridTemplateColumn
Header="# Working Orders"
c:DataGridFilterProperties.Filter="{Binding DataContext.n_working_orders_filter, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.n_working_orders_filter.active, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.n_working_orders_filter.open, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource IntMinMaxTemplate}"
<AdornerDecorator>
<Grid>
<c:DIYGrid
x:Name="_grid"
Columns="{Binding columns}"
CurrentRow="{Binding current_row}"
Rows="{Binding rows_view}"
>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding show_order_book}">
<TextBlock Text="{Binding n_working_orders}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Header="Security Key" Binding="{Binding security_key}" Width="*"
c:DataGridFilterProperties.Filter="{Binding DataContext.security_key_filter, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_key_filter.active, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_key_filter.open, Source={u:DirectReference grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
/>
</DataGrid.Columns>
</c:DataGridEx>
</Grid>
<c:DIYGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
</c:DIYGrid.InputBindings>
<c:DIYGrid.CommandBindings>
<CommandBinding
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
/>
</c:DIYGrid.CommandBindings>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:SyntheticManagementRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -1,12 +1,17 @@
using mbts.spark.filters;
using mbts.spark;
using mbts.ui.controls;
using System.Windows.Controls;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for SyntheticManagementView.xaml
/// </summary>
internal partial class SyntheticManagementView : UserControl {
partial class SyntheticManagementView : UserControl {
public enum Columns { Name, Description, n_legs, n_working_orders, SecurityKey}
public SyntheticManagementView() =>
InitializeComponent();
}
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
}
@@ -2,8 +2,11 @@
using bts.api;
using bts.shell;
using bts.spark;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -13,27 +16,33 @@ using System.Windows;
using static bts.shell.SyntheticManagementWindowFactory;
namespace mbts.spark {
class SyntheticManagementWindow : SparkWindow<StartupData> {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
const string _window_type = "SyntheticManagement";
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
StartupData _state;
OrdersModel _orders_model;
SyntheticManagementView _view;
IDisposable _subscriptions;
IReadOnlyList<SparkCommand> _commands;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
public static SyntheticManagementWindow create(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state) {
var window = new SyntheticManagementWindow(factory, bus, orders_model);
window.initialize(state);
return window;
}
SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model)
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
}
public void thread_init() {
Log.notnull(_state);
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
var current_row_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM.Row>(current_row_source);
Commands = new SparkCommand[] {
_commands = new SparkCommand[] {
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
() => new SecurityCriteria(), () => true
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
@@ -51,7 +60,24 @@ namespace mbts.spark {
filters = new OrderBookWindowFactory.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
}
})
}
), SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<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()
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_subscriptions = Disposable.Create(
@@ -69,21 +95,21 @@ namespace mbts.spark {
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
}
public void initialize(StartupData state) {
Log.notnull(state);
_state = state;
_vm.value = new SyntheticManagementDialogVM(_bus, _orders_model, state.sorting_criteria, state.filters ?? new FilterStates());
_vm.value = new SyntheticManagementDialogVM(_bus, _orders_model, _settings_manager, _state.sorting_criteria, _state.filters ?? new FilterStates());
if (_view == null) _view = new SyntheticManagementView { DataContext = _vm.value };
else _view.DataContext = _vm.value;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands { get; }
public override StartupData SaveState => _state = new StartupData(_vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
public override double MinWidth => 700;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public override void Dispose() {
Disposable.dispose(ref _vm);
@@ -1,30 +1,46 @@
using bts.api;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.SyntheticManagementView;
namespace bts.shell {
class SyntheticManagementWindowFactory : ShellWindowFactory<SyntheticManagementWindowFactory.StartupData> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, OrdersModel orders_model) : base(module, "SyntheticManagement") {
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "SyntheticManagement") {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).OnDispatcher().Subscribe(show);
}
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(StartupData state) =>
Task.FromResult<IShellWindow>(SyntheticManagementWindow.create(this, _bus, _orders_model, state));
Task.FromResult<IShellWindow>(_create(state));
public async void show(SparkTags.Nothing nothing) =>
_bus.post(SparkTags.ShowWindow, await load(new StartupData()));
IShellWindow _create(StartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.synthetic_management_grid,
set: (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
return new SyntheticManagementWindow(this, _bus, _orders_model, state, settings_manager);
}
[ProtoContract]
public class StartupData {
public const int Version = 1;
@@ -33,11 +49,41 @@ namespace bts.shell {
public readonly List<SortingCriteria> sorting_criteria;
[ProtoMember(2)]
public readonly FilterStates filters;
[ProtoMember(3)]
public readonly GridSettings grid;
public StartupData() : this(new List<SortingCriteria>(), null) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters) {
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
this.sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
static class DefaultColumns {
public static GridColumnSettings name => new GridColumnSettings(col_id(Columns.Name), DefaultColumnHeaders.Name, width: DefaultColumnWidths.Security);
public static GridColumnSettings description => new GridColumnSettings(col_id(Columns.Description), DefaultColumnHeaders.Description, width: DefaultColumnWidths.Description);
public static GridColumnSettings n_legs => new GridColumnSettings(col_id(Columns.n_legs), DefaultColumnHeaders.N_Legs, width: DefaultColumnWidths.N_Legs);
public static GridColumnSettings n_working_orders => new GridColumnSettings(col_id(Columns.n_working_orders), DefaultColumnHeaders.N_Working_Orders, width: DefaultColumnWidths.N_Working_Orders);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(name.id, true);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return name;
yield return description;
yield return n_legs;
yield return n_working_orders;
yield return security_key;
}
}
}
}
@@ -0,0 +1,18 @@
using mbts.ui.spark;
using LegMarketDataCalculation = bts.spark.LegSpreaderParams.Types.LegMarketDataCalculation;
namespace mbts.spark {
partial class SyntheticEditorVM {
class MarketDataCalculationModel : RowModelBase<LegMarketDataCalculation> {
public override string display_name { get; } = "Market Data Calculation";
public override string help_text { get; }
public override string man_page { get; } = string.Empty;
public override LegMarketDataCalculation get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.LegMdCalc;
public override void set_value(LegSpreaderParamsTuple leg_tup, LegMarketDataCalculation value) =>
leg_tup.leg_params.LegMdCalc = value;
}
}
}
@@ -0,0 +1,18 @@
using mbts.ui.spark;
using MarketDataType = bts.spark.LegSpreaderParams.Types.MarketDataType;
namespace mbts.spark {
partial class SyntheticEditorVM {
class MarketDataTypeModel : RowModelBase<MarketDataType> {
public override string display_name { get; } = "Market Data Type";
public override string help_text { get; }
public override string man_page { get; } = string.Empty;
public override MarketDataType get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.MarketDataType;
public override void set_value(LegSpreaderParamsTuple leg_tup, MarketDataType value) =>
leg_tup.leg_params.MarketDataType = value;
}
}
}
@@ -203,6 +203,8 @@ namespace mbts.spark {
new IsQuoteModel(),
new HedgeRatioModel(),
new QuoteHiddenModel(),
new MarketDataTypeModel(),
//new MarketDataCalculationModel(),
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
quote_throttle_outside_ticks,
@@ -1,22 +1,31 @@
using bts.api;
using bts.shell;
using bts.spark;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Data;
using System.Windows.Input;
using static bts.shell.SyntheticManagementWindowFactory;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.SyntheticManagementView;
namespace mbts.spark {
class SyntheticManagementDialogVM : SecurityMDGrid<SyntheticManagementDialogVM.Row, (ExecutionStrategyImmutable? strategy, SecurityMD md)> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model) : this(bus, orders_model, null, null) { }
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager) : this(bus, orders_model, settings_manager, null, null) { }
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
: base(orders_model.spreader.strategy_tuple.Select(map => map.Values), sorting) {
_bus = bus;
_orders_model = orders_model;
@@ -28,7 +37,41 @@ namespace mbts.spark {
n_working_orders_filter.load_state(filters.n_working_orders);
security_key_filter.load_state(filters.security_key);
}
_dispose = DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter);
_settings_manager = settings_manager;
// columns
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
columns.Add(new HyperlinkElementColumn<Row, Columns>(
Columns.n_working_orders,
new Binding(nameof(Row.n_working_orders)),
new Binding(nameof(Row.show_order_book)),
n_working_orders_filter,
DataGridFilterKnownTemplates.UIntMinMax) {
id = col_id(Columns.n_working_orders),
Header = DefaultColumnHeaders.N_Working_Orders,
Width = DefaultColumnWidths.N_Working_Orders
}
);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
id = col_id(col_key),
Header = header,
sort_member_path = prop,
Width = width
});
//
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
_resources.AddRange(
DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
);
}
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
@@ -36,6 +79,20 @@ namespace mbts.spark {
protected override void on_row_removed(Row row) => row.Dispose();
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
public StartupData create() =>
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
#region Filters
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
@@ -44,7 +101,7 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public FilterStates get_filter_states() =>
FilterStates _get_filter_states() =>
new FilterStates {
description = description_filter.get_state(),
create_time = create_time_filter.get_state(),
@@ -55,7 +112,7 @@ namespace mbts.spark {
};
#endregion
public class Row : SecurityMDRow, IEquatable<Row> {
public class Row : SecurityMDRow, IEquatable<Row>, IRowVM, IActionableRowVM {
public readonly ExecutionStrategyImmutable? strategy;
readonly IBus _bus;
@@ -85,6 +142,17 @@ namespace mbts.spark {
public uint n_working_orders => _n_working_orders.Value;
public ICommand show_ladder { get; }
public ICommand show_order_book { get; }
public ICommand default_command => show_ladder;
public ObservableCollection<RowAnimation> row_animations => new ObservableCollection<RowAnimation>();
public IReadOnlyList<IGridCellStore> cell_stores => new IGridCellStore[0];
public event Action invalidated;
public void raise_invalidated() => invalidated?.Invoke();
public bool Equals(Row other) =>
other != null &&
@@ -96,6 +164,8 @@ namespace mbts.spark {
public override int GetHashCode() =>
security_key.GetHashCode();
void _show_ladder() =>
_bus.post(SparkTags.ShowLadderTag, md);
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.spark;
using mbts.persist;
using mbts.spark.filters;
using mbts.spark.utilities;
using mbts.ui;
@@ -13,6 +14,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
using static bts.api.orderman.Fill.Types;
using static bts.shell.FillsWindowFactory;
@@ -23,13 +25,16 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class FillsVM : SecurityGrid<FillsVM.Row, FillTuple> {
readonly OrdersModel _model;
public FillsVM(IBus bus, OrdersModel model, StartupData state)
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
IWindowSettingsManager<GridSettings> _settings_manager;
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.CreateIncrementalUpdate(fills_source), null) {
_model = model;
_settings_manager = settings_manager;
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts().Select(d => d.Values), r => r.account, nameof(Row.account));
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users().Select(d => d.Values), r => r.user, nameof(Row.user));
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
@@ -44,30 +49,6 @@ namespace mbts.spark {
SecurityDataHelper.EqualityComparer.IDComparer,
Comparer<SecurityImmutable>.Default
);
if (filters != null) {
fill_time_filter.load_state(filters.fill_time);
time_in_force_filter.load_state(filters.time_in_force);
order_type_filter.load_state(filters.order_type);
account_filter.load_state(filters.account);
user_filter.load_state(filters.user);
exchange_filter.load_state(filters.exchange);
security_filter.load_state(filters.security);
side_filter.load_state(filters.side);
price_filter.load_state(filters.price);
size_filter.load_state(filters.size);
fill_kind_filter.load_state(filters.fill_kind);
fill_key_filter.load_state(filters.fill_key);
order_key_filter.load_state(filters.order_key);
security_key_filter.load_state(filters.security_key);
} else {
fill_time_filter.load_state(
new TimeFilterTuple(ui.controls.TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1))
)
);
fill_kind_filter.load_state(EnumHelper.get_values_except(FillKind.Sod).ToArray());
}
// columns
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(), nameof(Row.security), security_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
add_col(Columns.FillTime, DefaultColumnHeaders.FillTime, r => r.fill_time.ToString("M/d h:mm:ss tt"), nameof(Row.fill_time), fill_time_filter, DataGridFilterKnownTemplates.TimeFilter, DefaultColumnWidths.Time);
@@ -75,30 +56,83 @@ namespace mbts.spark {
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, nameof(Row.account), account_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), nameof(Row.exchange), exchange_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), nameof(Row.time_in_force), time_in_force_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
columns.Add(new OrderTypeColumn<Columns>(Columns.OrderType, new Binding(nameof(Row.order_type)), new Binding(nameof(Row.order_metadata)), order_type_filter) { sort_member_path = nameof(Row.order_type) });
columns.Add(new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { sort_member_path = nameof(Row.side) });
columns.Add(new OrderTypeColumn<Columns>(Columns.OrderType, new Binding(nameof(Row.order_type)), new Binding(nameof(Row.order_metadata)), order_type_filter) { id = col_id(Columns.OrderType), sort_member_path = nameof(Row.order_type) });
columns.Add(new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side), sort_member_path = nameof(Row.side) });
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
add_col(Columns.FillKind, "Fill Kind", r => r.fill_kind.ToString(), nameof(Row.fill_kind), fill_kind_filter, DataGridFilterKnownTemplates.DiscreteValues, 70);
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), nameof(Row.fill_kind), fill_kind_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), nameof(Row.fill_key), fill_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), nameof(Row.order_key), order_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
id = col_id(col_key),
Header = header,
sort_member_path = prop,
Width = width
});
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
grid_settings_manager.load_from(settings_manager.current_settings());
//
_resources.Add(DataGridFilterHelper.hook_filters(rows_view,
fill_time_filter, time_in_force_filter, order_type_filter, account_filter, user_filter, exchange_filter,
security_filter, side_filter, price_filter, size_filter, fill_kind_filter, fill_key_filter, order_key_filter, security_key_filter
));
_resources.AddRange(
state_obs.OnDispatcher().Subscribe(s =>
{
using (rows_view.DeferRefresh()) {
if (s.grid != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
if (s.filters is FilterStates filters) {
fill_time_filter.load_state(filters.fill_time);
time_in_force_filter.load_state(filters.time_in_force);
order_type_filter.load_state(filters.order_type);
account_filter.load_state(filters.account);
user_filter.load_state(filters.user);
exchange_filter.load_state(filters.exchange);
security_filter.load_state(filters.security);
side_filter.load_state(filters.side);
price_filter.load_state(filters.price);
size_filter.load_state(filters.size);
fill_kind_filter.load_state(filters.fill_kind);
fill_key_filter.load_state(filters.fill_key);
order_key_filter.load_state(filters.order_key);
security_key_filter.load_state(filters.security_key);
} else {
fill_time_filter.load_state(
new TimeFilterTuple(ui.controls.TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1))
)
);
fill_kind_filter.load_state(EnumHelper.get_values_except(FillKind.Sod).ToArray());
}
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
}
}),
DataGridFilterHelper.hook_filters(rows_view,
fill_time_filter, time_in_force_filter, order_type_filter, account_filter, user_filter, exchange_filter,
security_filter, side_filter, price_filter, size_filter, fill_kind_filter, fill_key_filter, order_key_filter, security_key_filter
),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
fills_source.Connect()
);
}
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
public GridSettings get_grid_save_state() {
if (_settings_manager.instance_settings == null)
return null;
else
return grid_settings_manager.create();
}
#region Filters
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> fill_time_filter { get; } = new TimeFilterVM<Row>(r => r.fill_time, nameof(Row.fill_time));
public IDataGridColumnPersistentFilter<Row, TimeInForce[]> time_in_force_filter { get; } = DiscreteValuesFilterVM.Create<Row, TimeInForce>(EnumHelper.get_values<TimeInForce>().ToLArray(), r => r.time_in_force, nameof(Row.time_in_force));
@@ -144,6 +178,9 @@ namespace mbts.spark {
} else return model.on_fill_tuples;
}
static IConnectableObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
state.SelectManyInSeries(s => _get_observable(bus, model, s));
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
readonly OrdersModel _model;
readonly FillImmutable _fill;
@@ -2,9 +2,16 @@
using bts.api;
using bts.shell;
using bts.spark;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using StartupData = bts.shell.FillsWindowFactory.StartupData;
@@ -13,29 +20,76 @@ namespace mbts.spark {
const string _window_type = "Fills", _thread_id = "Fills";
readonly AllowOnce _init_guard = new AllowOnce("Fills");
readonly OrdersModel _orders_model;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
FillsVM _vm;
FillsView _view;
StartupData _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state)
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Fills", bus, factory, SparkCommands.ShowFills.icon) {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
}
event Action<FillsWindowFactory.StartupData> _state_changed;
public override string window_type => _window_type;
public override UIElement Content => _view;
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.rows_view.GetSortingCriteria(), _vm.get_filters());
public override double MinWidth => 1050 + SPARK_MENU_COLLAPSED_WIDTH + LAUNCHER_BUTTON_BORDER_WIDTH;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.rows_view.get_sorting_criteria(_vm.columns), _vm.get_filters(), _vm.get_grid_save_state());
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id => _thread_id;
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
_vm = new FillsVM(_bus, _orders_model, _state);
var state_src = Observable.FromEvent<Action<FillsWindowFactory.StartupData>, FillsWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
_view = new FillsView { DataContext = _vm };
_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>(
_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()
)
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = $"{_window_type} Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
_subscriptions = Disposable.Create(() =>
_state_changed?.Invoke(
_state = new StartupData(_state.orders,
_vm.rows_view.get_sorting_criteria(_vm.columns),
_vm.get_filters(),
_vm.grid_settings_manager.create()
)
)
);
}
public override void Dispose() => Disposable.dispose(ref _vm);
public override void Dispose() {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
}
}
}
@@ -1,49 +1,118 @@
using bts.api;
using bts.api.orderman;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.FillsView;
namespace bts.shell {
class FillsWindowFactory : ShellWindowFactory<FillsWindowFactory.StartupData> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
public FillsWindowFactory(TradingShellModule module, IBus bus, OrdersModel orders_model)
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
: base(module, "Fills") {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowFillsTag).OnDispatcher().Subscribe(show);
}
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(StartupData state) =>
Task.FromResult<IShellWindow>(new FillsWindow(this, _bus, _orders_model, state));
Task.FromResult(_create(state));
public async void show(StartupData state) =>
_bus.post(SparkTags.ShowWindow, await load(state));
IShellWindow _create(StartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.fills_grid,
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
}
[ProtoContract]
public class StartupData {
public const int Version = 1;
public StartupData() { }
public StartupData(uint[] orders) : this(orders, null, null) { }
public StartupData(FilterStates filters) : this(null, null, filters) { }
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters) {
this.orders = orders;
this.sorting_criteria = sorting_criteria;
this.filters = filters;
}
[ProtoMember(1)]
public uint[] orders;
[ProtoMember(2)]
public List<SortingCriteria> sorting_criteria;
List<SortingCriteria> _sorting_criteria;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
public StartupData() { }
public StartupData(uint[] orders) : this(orders, null, null) { }
public StartupData(FilterStates filters) : this(null, null, filters) { }
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
this.orders = orders;
this._sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default() =>
new StartupData(null, null, new FilterStates(), null);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
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);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(fill_time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return fill_time;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return side;
yield return quantity;
yield return price;
yield return fill_kind;
yield return fill_key;
yield return order_key;
yield return security_key;
}
}
}
}
[ProtoContract]
@@ -4,6 +4,7 @@ using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.spark;
using ProtoBuf;
using System;
using System.Collections.Generic;
@@ -22,15 +23,17 @@ namespace bts.shell {
readonly IWindowManager _wm;
readonly UserSettingsHub _settings_hub;
readonly OrdersModel _orders_model;
readonly MarketDataAuxiliaryModel _md_auxiliary_model;
readonly int _max_ladders_open;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model)
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model, MarketDataAuxiliaryModel md_auxiliary_model)
: base(owner, "SparkLadder") {
_bus = bus;
_wm = wm;
_settings_hub = settings_hub;
_orders_model = orders_model;
_md_auxiliary_model = md_auxiliary_model;
var knobs = Configuration.get<bts.config.LadderKnobs>();
_max_ladders_open = (int)(knobs.max_open_ladders);
_security_timeout = TimeSpan.FromSeconds(Math.Max(15, knobs.security_timeout_secs));
@@ -56,7 +59,7 @@ namespace bts.shell {
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
var view = new Lazy<UIElement>(() => {
var model = new LadderModel(md, _orders_model);
var model = new LadderModel(md, _orders_model, _md_auxiliary_model);
var settings_manager = new WindowSettingsManager<LadderSettings>(
_settings_hub, state_settings,
us => sanitize_ladder_settings(us.ladder),
@@ -85,6 +88,12 @@ namespace bts.shell {
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;
}
@@ -3,6 +3,7 @@ using bts.api.orderman;
using bts.utils;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.utility;
using mbts.ui.utility.text;
@@ -208,7 +209,7 @@ namespace mbts.spark {
"Parent Key",
nameof(OrderBookRowVM.parent_order_key),
r => r.parent_order_key.store,
80,
DefaultColumnWidths.ParentKey,
new RegexFilterVM<OrderBookRowVM>(
r => r.parent_order_key.text,
value_path(nameof(OrderBookRowVM.parent_order_key))),
@@ -321,6 +322,7 @@ namespace mbts.spark {
ListSortDirection? _cached_sort_direction;
readonly string _filter_template;
protected readonly Func<OrderBookRowVM, IOrderBookCellStore> _get_cell_store;
GlyphContext _root_glyph_context, _child_glyph_context;
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, IOrderBookCellStore> get_cell_store, string sort_prop, double width, IDataGridColumnPersistentFilter<OrderBookRowVM, T> filter, string filter_template) : base(col_key) {
id = key.ToString();
@@ -331,6 +333,12 @@ namespace mbts.spark {
this.filter = filter;
_filter_template = filter_template;
_cached_sort_direction = SortDirection;
font_params_changed += refresh_glyph_contexts;
refresh_glyph_contexts();
void refresh_glyph_contexts() {
_root_glyph_context = GlyphContext.create(font_family, RenderUtils.root_font_weight);
_child_glyph_context = GlyphContext.create(font_family, RenderUtils.child_font_weight);
}
}
public IDataGridColumnPersistentFilter<OrderBookRowVM, T> filter { get; }
@@ -346,6 +354,7 @@ namespace mbts.spark {
_cached_sort_direction = new_value;
base.OnSortDirectionChanged(new_value);
}
protected GlyphContext _get_glyph_context(OrderBookRowVM row) => row.is_child ? _child_glyph_context : _root_glyph_context;
}
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
@@ -355,7 +364,7 @@ namespace mbts.spark {
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) {
var cell = _get_cell_store(row);
if (!string.IsNullOrEmpty(cell.current.text))
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, cell.current.text, position, RowHeight,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, cell.current.text, position, RowHeight,
font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, cell.animations);
}
}
@@ -366,7 +375,7 @@ namespace mbts.spark {
: base(Columns.Side, "B/S", r => r.side.store, nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.side.text, position, RowHeight,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.text, position, RowHeight,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.side.store.animations);
}
@@ -377,18 +386,18 @@ namespace mbts.spark {
}
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.quantity.text, position, RowHeight,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.text, position, RowHeight,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.quantity.store.animations);
}
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, UIntMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.store, nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax) {
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.store, nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Filled, filter, DataGridFilterKnownTemplates.UIntMinMax) {
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
}
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.filled_quantity.text, position, RowHeight,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.text, position, RowHeight,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.filled_quantity.store.animations);
}
@@ -397,26 +406,26 @@ namespace mbts.spark {
: base(Columns.Price, "Price", r => r.price.store, nameof(OrderBookRowVM.price), DefaultColumnWidths.Price, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.price.text,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.price.text,
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.price.store.animations);
}
public class AvgFilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public AvgFilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, DoubleMinMaxState> filter)
: base(Columns.AvgFilled, "Avg Filled", r => r.avg_price.store, nameof(OrderBookRowVM.avg_price), DefaultColumnWidths.Price, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
: base(Columns.AvgFilled, "Avg Filled", r => r.avg_price.store, nameof(OrderBookRowVM.avg_price), DefaultColumnWidths.AveragePrice, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) {
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.avg_price.text,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.avg_price.text,
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.avg_price.store.animations);
}
}
public class LastFilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public LastFilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, DoubleMinMaxState> filter)
: base(Columns.LastPrice, "Last Filled", r => r.last_price.store, nameof(OrderBookRowVM.last_price), DefaultColumnWidths.Price, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
: base(Columns.LastPrice, "Last Filled", r => r.last_price.store, nameof(OrderBookRowVM.last_price), DefaultColumnWidths.LastPrice, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.last_price.text,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.last_price.text,
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.last_price.store.animations);
}
@@ -425,7 +434,7 @@ namespace mbts.spark {
: base(Columns.Time, "Time", r => r.time_created.store, nameof(OrderBookRowVM.time_created), DefaultColumnWidths.Time, filter, DataGridFilterKnownTemplates.TimeFilter) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.time_created.text,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.time_created.text,
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.time_created.store.animations);
}
@@ -434,7 +443,7 @@ namespace mbts.spark {
: base(Columns.LastChange, "Last Change", r => r.last_change.store, nameof(OrderBookRowVM.last_change), DefaultColumnWidths.Time, filter, DataGridFilterKnownTemplates.TimeFilter) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, RenderUtils.get_glyph_context(row), font_size, row.last_change.text,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.last_change.text,
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.last_change.store.animations);
}
@@ -470,7 +479,7 @@ namespace mbts.spark {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = 80;
Width = DIYGrid.DefaultColumnWidths.LastMessage;
sort_member_path = nameof(OrderBookRowVM.last_msg);
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
@@ -521,7 +530,9 @@ namespace mbts.spark {
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
//
var security_view = new TextBlock { FontFamily = font_family, FontSize = font_size, Foreground = font_brush };
var security_view = new TextBlock { Foreground = font_brush };
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
security_view.SetBinding(TextBlock.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
security_view.SetBinding(TextBlock.TextProperty, $"{nameof(OrderBookRowVM.security)}.{nameof(IGridCell.text)}");
security_view.SetBinding(TextBlock.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
security_view.SetBinding(TextBlock.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
@@ -548,7 +559,10 @@ namespace mbts.spark {
#endregion
static class RenderUtils {
public static readonly GlyphContext _child_glyph_context = TextUtils.StandardGlyphContext.with(FontWeights.Light);
public static FontWeight
root_font_weight = FontWeights.Normal,
child_font_weight = FontWeights.Light;
public static double font_size = FontSizes.Small;
public static readonly IValueConverter
@@ -567,7 +581,6 @@ namespace mbts.spark {
return mb;
}
public static GlyphContext get_glyph_context(OrderBookRowVM r) => r.is_child ? _child_glyph_context : TextUtils.StandardGlyphContext;
public static Brush get_side_brush(OrderBookRowVM r) => r.side.value == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
}
@@ -4,6 +4,7 @@ using bts.api.core;
using bts.api.orderman;
using bts.shell;
using mbts.collections;
using mbts.persist;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
@@ -23,12 +24,17 @@ using System.Windows.Data;
using System.Windows.Input;
using static bts.shell.OrderBookWindowFactory;
using static mbts.spark.OrderBookView;
using _GridSettingsManager = mbts.ui.GridSettingsManager<mbts.spark.OrderBookRowVM, mbts.spark.OrderRecordTuple, bts.shell.INotificationServiceDataCache, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
namespace mbts.spark {
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection {
readonly LiveSortingColumKeys _live_sorting_columns;
readonly Property<OrderBookRowVM> _current_row;
readonly Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> _order_securities_subject =
new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
OrderBookStartupData _state;
Dictionary<uint, OrderBookRowVM> _order_rows_cache = new Dictionary<uint, OrderBookRowVM>();
Dictionary<uint, BranchFilterSource> _branch_filter_roots = new Dictionary<uint, BranchFilterSource>();
ObservableCollection<OrderBookRowVM> _order_rows = new ObservableCollection<OrderBookRowVM>();
@@ -36,7 +42,8 @@ namespace mbts.spark {
SetMap<uint, uint> _parent_to_children = new SetMap<uint, uint>();
SetMap<uint, uint> _security_orders = new SetMap<uint, uint>();
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model) {
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager) {
_settings_manager = settings_manager;
var order_securities_subject = new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
columns = new OrderBookColumnCollection(bus, model, order_securities_subject);
rows_view = CollectionViewSource.GetDefaultView(_order_rows);
@@ -63,6 +70,7 @@ namespace mbts.spark {
});
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, _order_rows, rows_view, columns);
grid_settings_manager.load_from(OrderBookStartupData.DefaultGrid);
_live_sorting_columns = new LiveSortingColumKeys(grid_settings_manager.live_sorting_props);
SetMap<uint, uint> missing_security_orders = new SetMap<uint, uint>();
@@ -71,6 +79,7 @@ namespace mbts.spark {
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
model.on_orders.OnDispatcher().Subscribe(on_orders),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
grid_settings_manager,
columns,
order_securities_subject);
@@ -215,7 +224,7 @@ namespace mbts.spark {
public OrderRecordImmutable? current_order => current_row?.order;
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
public GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate> grid_settings_manager { get; }
public _GridSettingsManager grid_settings_manager { get; }
public ICommand collapse_all_rows_command { get; }
public ICommand expand_all_rows_command { get; }
@@ -225,14 +234,17 @@ namespace mbts.spark {
}
public OrderBookStartupData create() {
var (grid, animations) = grid_settings_manager.create();
return new OrderBookStartupData(columns.create(), grid, animations.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_builder, a.cell_animations, a.row_animations, a.sound_notifications)));
if (_settings_manager.instance_settings == null)
return new OrderBookStartupData(columns.create(), use_defaults: true);
var gs = grid_settings_manager.create();
return new OrderBookStartupData(columns.create(), gs.general, gs.predicate_animations, use_defaults: false);
}
public void load_from(OrderBookStartupData state) {
if (state == null) return;
_state = state;
grid_settings_manager.load_from((state.grid, state.predicate_animations));
var gs = state.grid_settings_wrapper();
if (gs != null) grid_settings_manager.load_from(gs);
columns.load_from(state.filters);
}
@@ -242,7 +254,7 @@ namespace mbts.spark {
protected override void Dispose(bool disposing) =>
Disposable.dispose(_order_rows);
static GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate> _create_grid_settings_manager(
static _GridSettingsManager _create_grid_settings_manager(
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model,
ObservableCollection<OrderBookRowVM> rows, ICollectionView rows_view, ObservableCollection<DIYGrid.ColumnBase> columns) {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
@@ -254,7 +266,7 @@ namespace mbts.spark {
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
}
return new GridSettingsManager<OrderBookRowVM, OrderRecordTuple, INotificationServiceDataCache, OrderRecordTuplePredicate>(
return new _GridSettingsManager(
owner_title, bus, notification_service, model, rows, rows_view, columns,
() => {
var pvm = PredicateBuilderVMWrapper.create<OrderRecordTuple, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
@@ -267,7 +279,9 @@ namespace mbts.spark {
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
return pvm;
}, notification_service.data_cache, col_settings_flags);
},
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_builder, a.cell_animations, a.row_animations, a.sound_notifications))),
notification_service.data_cache, col_settings_flags);
}
static OrderRecordTuplePredicate.PredicateTypeOneofCase _corresponding_predicate_type(Columns col) {
@@ -2,18 +2,27 @@
using bts.api.orderman;
using bts.shell;
using bts.spark;
using mbts.persist;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
//using IOrderBookWSM = mbts.persist.IWindowSettingsManager<mbts.spark.GridSettingsWithNotifications<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate>>;
using OrderBookGridSettings = bts.shell.OrderBookWindowFactory.OrderBookGridSettings;
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
namespace mbts.spark {
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook";
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook", _default_title = "Order Book";
readonly INotificationService _notification_service;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
OrderBookWindowFactory.OrderBookStartupData _state;
OrdersModel _model;
OrderBookVM _vm;
@@ -32,12 +41,14 @@ namespace mbts.spark {
, _toggle_expand_all_command
, _show_grid_settings_command;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service, OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon)
: base("Order Book", bus, factory, SparkCommands.ShowOrders.icon) {
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
_model = model;
_factory = factory;
_notification_service = notification_service;
_state = state;
_settings_manager = settings_manager;
}
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm.create();
@@ -45,8 +56,11 @@ namespace mbts.spark {
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override double MinWidth => 1577;
public override double MinWidth =>
OrderBookWindowFactory.OrderBookStartupData.DefaultGrid.general.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id => ORDERBOOK_THREAD_ID;
public override void Dispose() {
@@ -55,7 +69,7 @@ namespace mbts.spark {
}
public void thread_init() {
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model);
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model, _settings_manager);
_vm.load_from(_state);
_view = new OrderBookView(_vm);
@@ -106,9 +120,32 @@ namespace mbts.spark {
Icons.DownArrow,
_vm.expand_all_rows_command);
_show_grid_settings_command = SparkCommands.window_settings_command(_vm.grid_settings_manager.launch_settings_editor);
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
_settings_manager,
us => us.order_book_grid,
UserSettings.FileExtensions.GRID,
s => {
if (s.general is null)
s = OrderBookWindowFactory.OrderBookStartupData.DefaultGrid;
return _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.general.with(_vm.columns.Select(c => c.create())),
this.observe_title()
);
}
);
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { DataContext = vm };
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
});
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
_commands = new SparkCommand[] {
_commands = new SparkCommand[] {
_show_fills_command,
_modify_order_command,
_cancel_selected_orders_command,
@@ -1,8 +1,9 @@
using bts.api;
using bts.api.orderman;
using bts.spark;
using Google.Protobuf.Collections;
using bts.utils;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
@@ -11,6 +12,7 @@ using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
@@ -22,19 +24,22 @@ namespace bts.shell {
readonly INotificationService _notification_service;
readonly INotificationServiceDataCache _notification_service_data_cache;
readonly OrdersModel _model;
readonly UserSettingsHub _settings_hub;
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model) : base(owner, "SparkOrderBookWindow") {
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model, UserSettingsHub settings_hub) : base(owner, "SparkOrderBookWindow") {
_bus = bus;
_notification_service = notification_service;
_notification_service_data_cache = notification_service_data_cache;
_model = model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).OnDispatcher().Subscribe(show);
}
public override Task<IShellWindow> load(OrderBookStartupData state) {
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon) as IShellWindow;
return Task.FromResult(w);
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
us => us.order_book_grid, (us, s) => us.with(order_book_grid: s));
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
return Task.FromResult<IShellWindow>(w);
}
async void show(OrderBookStartupData startup_data) {
@@ -49,27 +54,42 @@ namespace bts.shell {
[ProtoMember(10)]
public FilterStates filters;
[ProtoMember(15)]
[Obsolete]
public GridSettings grid;
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
[ProtoMember(25)]
public bool use_defaults;
public OrderBookStartupData() { }
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
#pragma warning disable CS0612 // Type or member is obsolete
this.grid = grid;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() => use_defaults ? null : new OrderBookGridSettings(grid, predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates {
statuses = new[] { OrderStatus.Working }
}, GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion));
}, _default_general_grid, use_defaults: true);
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
@@ -85,14 +105,14 @@ namespace bts.shell {
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: 80);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, width: DIYGrid.DefaultColumnWidths.SecurityKey);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.OrderKey);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
@@ -171,5 +191,38 @@ namespace bts.shell {
[ProtoMember(20)]
public string[] symbols;
}
[ProtoContract]
public class OrderBookGridSettings : IGridSettingsWithNotifications<OrderRecordTuple, OrderRecordTuplePredicate>, IEquatable<OrderBookGridSettings> {
[ProtoMember(1)]
public GridSettings general { get; set; }
[ProtoMember(2)]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
public OrderBookGridSettings() { }
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
this.general = general;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
}
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
IEnumerable<IPredicateAnimationsData<OrderRecordTuple, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderRecordTuple, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations);
public bool Equals(OrderBookGridSettings other) =>
other != null &&
EqualityComparer.null_check_equals(general, other.general) &&
EqualityComparer.safe_sequence_equals(predicate_animations, other.predicate_animations);
public override bool Equals(object obj) => Equals(obj as OrderBookGridSettings);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
EqualityComparer.safe_get_hash_code(general),
EqualityComparer.safe_aggregate_hash(predicate_animations));
}
}
}
@@ -1,30 +1,38 @@
using bts.api;
using bts.api.orderman;
using bts.spark;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using static mbts.spark.PAndLView;
using static mbts.ui.controls.DIYGrid;
namespace bts.shell {
internal class PAndLWindowFactory : ShellWindowFactory<PAndLWindowFactory.PAndLStartupData> {
readonly IBus _bus;
readonly PAndLModel _model;
public PAndLWindowFactory(IShellModule owner, IBus bus, PAndLModel model) : base(owner, "SparkPAndL") {
readonly UserSettingsHub _settings_hub;
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, PAndLModel model) : base(owner, "SparkPAndL") {
_bus = bus;
_model = model;
_settings_hub = settings_hub;
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
}
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(PAndLStartupData state) {
var w = new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon) as IShellWindow;
return Task.FromResult(w);
return Task.FromResult(_create(state));
}
async void show(PAndLStartupData startup_data) {
@@ -32,6 +40,14 @@ namespace bts.shell {
_bus.post(SparkTags.ShowWindow, w);
}
IShellWindow _create(PAndLStartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.pnl_grid,
set: (us, gs) => us.with(provided_pnl_gridsettings: gs));
return new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
}
[ProtoContract]
public class PAndLStartupData {
@@ -40,21 +56,75 @@ namespace bts.shell {
/// </summary>
public PAndLStartupData() { }
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type) {
this.sort_criteria = sorting_criteria;
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type, GridSettings grid = default) {
_sort_criteria = sorting_criteria;
this.filters = filters;
this.calc_type = calc_type;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sort_criteria ?? (_sort_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static PAndLStartupData Default() =>
new PAndLStartupData(null, new FilterStates(), pnl_calc_type.midpoint);
[ProtoMember(5)]
public readonly List<SortingCriteria> sort_criteria;
List<SortingCriteria> _sort_criteria;
[ProtoMember(10)]
public FilterStates filters;
[ProtoMember(15)]
public pnl_calc_type calc_type;
//[ProtoMember(20)]
// ProtoMember 20 cannot be used further
[ProtoMember(25)]
public GridSettings grid;
static class DefaultColumns {
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings security_type => new GridColumnSettings(col_id(Columns.SecurityType), DefaultColumnHeaders.SecurityType, width: DefaultColumnWidths.SecurityType);
public static GridColumnSettings start_net_position => new GridColumnSettings(col_id(Columns.StartNetPos), DefaultColumnHeaders.StartNetPos, width: DefaultColumnWidths.Start_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_long_pnl => new GridColumnSettings(col_id(Columns.DayLong), DefaultColumnHeaders.DayLong, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_short_pnl => new GridColumnSettings(col_id(Columns.DayShort), DefaultColumnHeaders.DayShort, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings day_pnl => new GridColumnSettings(col_id(Columns.DayPNL), DefaultColumnHeaders.DayPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings pnl_price => new GridColumnSettings(col_id(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(account.id, true);
yield return new SortingCriteria(exchange.id, true);
yield return new SortingCriteria(symbol.id, true);
yield return new SortingCriteria(security.id, true);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return account;
yield return exchange;
yield return symbol;
yield return security;
yield return security_type;
yield return start_net_position;
yield return start_pnl;
yield return day_long_pnl;
yield return day_short_pnl;
yield return day_pnl;
yield return total_net_pos;
yield return total_pnl;
yield return pnl_price;
}
}
}
}
[ProtoContract]
@@ -1,4 +1,5 @@
using bts.api;
using bts.api.md.auxiliary;
using bts.spark;
using mbts.persist;
using mbts.spark;
@@ -19,11 +20,12 @@ namespace bts.shell {
PAndLWindowFactory _plwf;
[ImportingConstructor]
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model);
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model);
_fwf = new FillsWindowFactory(this, bus, orders_model);
_plwf = new PAndLWindowFactory(this, bus, pandl_model);
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache,
UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model, MarketDataAuxiliaryModel md_auxiliary_model) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, md_auxiliary_model);
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
}
public ImageSource icon => Icons.Trades;
@@ -1,6 +1,7 @@
using bts;
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts.ui;
using mbts.ui.shell;
using System;
@@ -11,6 +12,60 @@ using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
struct OrderTemplateKey : IEquatable<OrderTemplateKey> {
public readonly CustomOrderDefinition.CustomOneofCase type;
public readonly string name;
public OrderTemplateKey(CustomOrderDefinition.CustomOneofCase type)
: this(type, OrderUtils.order_type_display_name(type)) { }
public OrderTemplateKey(CustomOrderDefinition.CustomOneofCase type, string name) {
this.type = type;
this.name = name;
}
public bool Equals(OrderTemplateKey other) =>
type == other.type &&
name == other.name;
public override bool Equals(object obj) =>
obj is OrderTemplateKey other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
type.GetHashCode(),
name == null ? 0 : name.GetHashCode());
public override string ToString() => $"{name}:{type}";
public static bool operator ==(OrderTemplateKey a, OrderTemplateKey b) => a.Equals(b);
public static bool operator !=(OrderTemplateKey a, OrderTemplateKey b) => !a.Equals(b);
}
struct OrderTemplateHeader : IEquatable<OrderTemplateHeader> {
public readonly OrderTemplateKey key;
// Note: display_name is NOT part of EQUALITY
public readonly string display_name;
public OrderTemplateHeader(CustomOrderDefinition.CustomOneofCase type)
: this(new OrderTemplateKey(type), OrderUtils.order_type_display_name(type)) { }
public OrderTemplateHeader(OrderTemplateKey key, string display_name) {
this.key = key;
this.display_name = display_name;
}
public bool Equals(OrderTemplateHeader other) =>
key.Equals(other.key);
public override bool Equals(object obj) =>
obj is OrderTemplateHeader other && Equals(other);
public override int GetHashCode() => key.GetHashCode();
public override string ToString() => $"{display_name} ({key})";
}
class InlineOrderProperties {
public readonly InlineUserProperty<AccountTuple> account;
public readonly InlineUserProperty<TimeInForce> time_in_force;
@@ -39,7 +94,7 @@ namespace mbts.spark {
class OrderPropertiesEditorVM : DisposableViewModelBase {
readonly OrdersModel _model;
readonly Property<SecurityImmutable> _security;
readonly SelectorProperty<AccountTuple> _accounts;
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly SelectorProperty<TimeInForce> _time_in_force;
readonly SelectorProperty<IOrderTypeVM> _order_type;
IDisposable _order_type_param_errors_sub;
@@ -58,14 +113,21 @@ namespace mbts.spark {
nameof(selected_account),
model.factory.subscribe_account_tuples(),
order_properties.Select(ops => ops.account),
() => accounts?.FirstOrDefault() ?? default,
val => (!string.IsNullOrWhiteSpace(val.name) && accounts.Contains(val), "Please select an account"));
a => new AccountTuple?(a),
() => {
foreach (var a in accounts) {
return a;
}
return new AccountTuple?();
},
EqualityComparer<AccountTuple?>.Default,
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
_order_type = backing(
nameof(selected_order_type),
security_source.Select(s => model.factory.default_order_type_defs(s).Select(create_order_type_vm)),
security_source.Select(s => model.factory.default_order_type_templates(s).Select(kv => create_order_type_vm(kv.Value, kv.Key.key.name, kv.Key.display_name))),
order_properties.Select(get_order_type_vm_prop),
get_default_order_type_vm,
(a, b) => a.order_type == b.order_type);
EqualityComparer.null_check_wrapper(OrderTypeVMExtensions.key_comparer));
_order_type.Changed += on_selected_order_type_changed;
_time_in_force = backing(
nameof(selected_time_in_force),
@@ -77,19 +139,22 @@ namespace mbts.spark {
tif => time_in_forces.Contains(tif));
_dispose = Disposable.Create(() => _order_type.Changed -= on_selected_order_type_changed);
// local methods
IOrderTypeVM create_order_type_vm(CustomOrderDefinitionImmutable cd) {
IOrderTypeVM create_order_type_vm(CustomOrderDefinitionImmutable cd, string id, string display_name) {
IOrderTypeVM vm = null;
if(cd.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic)
vm = OrderTypeVMFactory.create(cd.CustomCase, model, security_md_source, cd.Dynamic.DynamicOrderType);
vm = OrderTypeVMFactory.create(cd.CustomCase, id, display_name, model, security_md_source, cd.Dynamic.DynamicOrderType);
else
vm = OrderTypeVMFactory.create(cd.CustomCase, model, security_md_source);
vm = OrderTypeVMFactory.create(cd.CustomCase, id, display_name, model, security_md_source);
vm.load_from(cd);
return vm;
}
InlineUserProperty<IOrderTypeVM> get_order_type_vm_prop(InlineOrderProperties p) {
if (!p.order_type_def.has_value)
return new InlineUserProperty<IOrderTypeVM>();
return new InlineUserProperty<IOrderTypeVM>(create_order_type_vm(p.order_type_def.value), p.order_type_def.force_override);
return new InlineUserProperty<IOrderTypeVM>(
create_order_type_vm(p.order_type_def.value, p.order_type_def.value.CustomCase.ToString(),
OrderUtils.order_type_display_name(p.order_type_def.value.CustomCase)),
p.order_type_def.force_override);
}
IOrderTypeVM get_default_order_type_vm() {
var ot = model.factory.default_order_type(security);
@@ -114,13 +179,13 @@ namespace mbts.spark {
public SecurityImmutable security => _security.Value;
public ReadOnlyObservableCollection<AccountTuple> accounts => _accounts.items;
public AccountTuple selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
public ReadOnlyObservableCollection<TimeInForce> time_in_forces => _time_in_force.items;
public TimeInForce selected_time_in_force { get => _time_in_force.selected_item; set => _time_in_force.selected_item = value; }
public ReadOnlyObservableCollection<IOrderTypeVM> order_types => _order_type.items;
public IOrderTypeVM selected_order_type { get => _order_type.selected_item; set => _order_type.selected_item = value; }
public IObservable<AccountTuple> observe_account(bool start_immediately = false) =>
public IObservable<AccountTuple?> observe_account(bool start_immediately = false) =>
_accounts.observe_current(start_immediately);
public IObservable<TimeInForce> observe_time_in_force(bool start_immediately = false) =>
@@ -17,22 +17,22 @@ namespace mbts.spark
{
class InlineCoverOrderProperties
{
public InlineCoverOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
public InlineCoverOrderProperties(OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
var hedgetype = CustomOneofCase.Limit;
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_hedge_order_type = new InlineUserProperty<OrderTemplateHeader>(new OrderTemplateHeader(hedgetype), true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
hedge_ticks = new InlineUserProperty<int>(1, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> hedge_order_types;
public readonly InlineUserProperty<int> hedge_ticks;
@@ -42,25 +42,25 @@ namespace mbts.spark
{
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineCoverOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _hedge_order_choices;
IOrderTypeVM _hedge_order;
IDisposable _hedge_order_errors_sub;
public int hedge_ticks { get; set; } = 1;
public CoverOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public CoverOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineCoverOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList())))
new InlineCoverOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name)
{ }
public CoverOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
IObservable<InlineCoverOrderProperties> inline_properties) : base(model)
IObservable<InlineCoverOrderProperties> inline_properties, string id, string display_name) : base(model, id, display_name)
{
_security_md_source = security_md_source;
_dispose = Disposable.Create(
@@ -75,11 +75,11 @@ namespace mbts.spark
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
{
_select_hedge_order(p.selected_hedge_order_type.value);
_select_hedge_order(p.selected_hedge_order_type.value.key.type);
}
if (p.hedge_ticks.force_override)
{
@@ -116,7 +116,7 @@ namespace mbts.spark
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _child_order_choices;
}
}
@@ -126,7 +126,7 @@ namespace mbts.spark
get
{
if (_hedge_order_choices == null)
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _hedge_order_choices;
}
}
@@ -200,7 +200,7 @@ namespace mbts.spark
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -216,7 +216,7 @@ namespace mbts.spark
void _select_hedge_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
_inline_props.selected_hedge_order_type.value :
_inline_props.selected_hedge_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_hedge_order(ot);
}
@@ -53,14 +53,14 @@ namespace mbts.spark
{
class InlineDynamicOrderProperties
{
public InlineDynamicOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
public InlineDynamicOrderProperties(OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
}
class DynamicOrderVM : OrderTypeVMBase<DynamicOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
@@ -68,8 +68,8 @@ namespace mbts.spark
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineDynamicOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
@@ -77,13 +77,13 @@ namespace mbts.spark
string _dyn_type;
List<NameValueVM> _parameters;
public DynamicOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string dyn_type)
public DynamicOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string dyn_type, string id, string display_name)
: this(model, security_md_source, dyn_type, security_md_source.Select(smd =>
new InlineDynamicOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList())))
new InlineDynamicOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name)
{ }
public DynamicOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string dyn_type,
IObservable<InlineDynamicOrderProperties> inline_properties) : base(model)
IObservable<InlineDynamicOrderProperties> inline_properties, string id, string display_name) : base(model, id, display_name)
{
_security_md_source = security_md_source;
_dyn_type = dyn_type;
@@ -100,7 +100,7 @@ namespace mbts.spark
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
_inline_props = p;
_validate();
@@ -139,7 +139,7 @@ namespace mbts.spark
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _child_order_choices;
}
}
@@ -185,7 +185,7 @@ namespace mbts.spark
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -1,11 +1,13 @@
using bts.api.orderman;
using bts.spark;
using bts.utils;
using System.Collections.Generic;
using System.Windows.Input;
using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark {
interface IOrderTypeVM : IPropertyWrapperVM<CustomOrderDefinition, CustomOrderDefinitionImmutable> {
string id { get; }
CustomOneofCase order_type { get; }
string type_name { get; }
bool is_bts_type { get; }
@@ -21,4 +23,12 @@ namespace mbts.spark {
ICollection<IOrderTypeVM> child_order_types { get; }
IOrderTypeVM child_order { get; }
}
static class OrderTypeVMExtensions {
public static IEqualityComparer<IOrderTypeVM> key_comparer =>
EqualityComparer.Create<IOrderTypeVM, OrderTemplateKey>(a => a.key());
public static OrderTemplateKey key(this IOrderTypeVM vm) =>
new OrderTemplateKey(vm.order_type, vm.id);
}
}
@@ -14,34 +14,34 @@ using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class InlineIcebergOrderProperties {
public InlineIcebergOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types) {
public InlineIcebergOrderProperties(OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types) {
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
show_quantity = new InlineUserProperty<double>(1, true);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<double> show_quantity;
}
class IcebergOrderVM : OrderTypeVMBase<IcebergOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable {
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineIcebergOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
public IcebergOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public IcebergOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineIcebergOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
new InlineIcebergOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name) { }
public IcebergOrderVM(OrdersModel model,
IObservable<SecurityMD> security_md_source,
IObservable<InlineIcebergOrderProperties> inline_properties)
: base(model) {
IObservable<InlineIcebergOrderProperties> inline_properties, string id, string display_name)
: base(model, id, display_name) {
_security_md_source = security_md_source;
_dispose = Disposable.Create(
security_md_source.OnDispatcher()
@@ -52,7 +52,7 @@ namespace mbts.spark {
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override)) {
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
if (p.show_quantity.force_override) {
show_quantity = p.show_quantity.value;
@@ -68,7 +68,7 @@ namespace mbts.spark {
public ICollection<IOrderTypeVM> child_order_types {
get {
if (_child_order_choices == null) {
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
}
return _child_order_choices;
}
@@ -122,7 +122,7 @@ namespace mbts.spark {
void _select_child_order() {
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -17,19 +17,19 @@ namespace mbts.spark
{
class InlineJoinOrderProperties
{
public InlineJoinOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
public InlineJoinOrderProperties (OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
var hedgetype = CustomOneofCase.Lean;
if(!child_order_types.Contains(hedgetype))
if(!child_order_types.Any(t => t.key.type == hedgetype))
hedgetype = CustomOneofCase.Limit;
//ExceptionAssert.InvalidOperation.That(child_order_types.Contains(hedgetype), $"Hedge order type {hedgetype} is not in hedge_order_types");
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_hedge_order_type = new InlineUserProperty<OrderTemplateHeader>(new OrderTemplateHeader(hedgetype), true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
join_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
@@ -37,10 +37,10 @@ namespace mbts.spark
enable_hedge = new InlineUserProperty<bool>(false, false);
hedge_ticks = new InlineUserProperty<int>(1, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> hedge_order_types;
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
@@ -55,13 +55,13 @@ namespace mbts.spark
{
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineJoinOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _hedge_order_choices;
IOrderTypeVM _hedge_order;
IDisposable _hedge_order_errors_sub;
@@ -79,12 +79,12 @@ namespace mbts.spark
}
public int hedge_ticks { get; set; } = 1;
public JoinOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public JoinOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineJoinOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
new InlineJoinOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name) { }
public JoinOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
IObservable<InlineJoinOrderProperties> inline_properties) : base(model)
IObservable<InlineJoinOrderProperties> inline_properties, string id, string display_name) : base(model, id, display_name)
{
_security_md_source = security_md_source;
_dispose = Disposable.Create(
@@ -98,11 +98,11 @@ namespace mbts.spark
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
{
_select_hedge_order(p.selected_hedge_order_type.value);
_select_hedge_order(p.selected_hedge_order_type.value.key.type);
}
if (p.tick_offset_on_deploy.force_override)
{
@@ -157,7 +157,7 @@ namespace mbts.spark
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _child_order_choices;
}
}
@@ -167,7 +167,7 @@ namespace mbts.spark
get
{
if (_hedge_order_choices == null)
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _hedge_order_choices;
}
}
@@ -241,7 +241,7 @@ namespace mbts.spark
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -257,7 +257,7 @@ namespace mbts.spark
void _select_hedge_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
_inline_props.selected_hedge_order_type.value :
_inline_props.selected_hedge_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_hedge_order(ot);
}
@@ -17,18 +17,18 @@ namespace mbts.spark
{
class InlineLeanOrderProperties
{
public InlineLeanOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
public InlineLeanOrderProperties (OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
max_chase_ticks = new InlineUserProperty<uint>(2, false);
trade_toward_behavior = new InlineUserProperty<LeanOrderDefinition.Types.OnToward>(LeanOrderDefinition.Types.OnToward.Cancel, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
public readonly InlineUserProperty<uint> max_chase_ticks;
@@ -39,8 +39,8 @@ namespace mbts.spark
{
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineLeanOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
@@ -49,12 +49,12 @@ namespace mbts.spark
public uint max_chase_ticks { get; set; } = 2;
public LeanOrderDefinition.Types.OnToward trade_toward_behavior { get; set; } = LeanOrderDefinition.Types.OnToward.Cancel;
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineLeanOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
new InlineLeanOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name) { }
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
IObservable<InlineLeanOrderProperties> inline_properties) : base(model)
IObservable<InlineLeanOrderProperties> inline_properties, string id, string display_name) : base(model, id, display_name)
{
_security_md_source = security_md_source;
_dispose = Disposable.Create(
@@ -68,7 +68,7 @@ namespace mbts.spark
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
if (p.cross_threshold.force_override)
{
@@ -115,7 +115,7 @@ namespace mbts.spark
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _child_order_choices;
}
}
@@ -161,7 +161,7 @@ namespace mbts.spark
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -3,7 +3,7 @@
namespace mbts.spark {
static partial class OrderTypeVMFactory {
private class LimitOrderVM : OrderTypeVMBase<LimitOrderDefinition> {
internal LimitOrderVM(OrdersModel model) : base(model) { }
internal LimitOrderVM(OrdersModel model) : base(model, "Limit", OrderUtils.order_type_display_name(CustomOrderDefinition.CustomOneofCase.Limit)) { }
public override void assign_to(CustomOrderDefinition value) =>
value.Limit = factory.limit_order();
@@ -3,7 +3,7 @@
namespace mbts.spark {
static partial class OrderTypeVMFactory {
private class MarketOrderVM : OrderTypeVMBase<MarketOrderDefinition> {
internal MarketOrderVM(OrdersModel model) : base(model) { }
internal MarketOrderVM(OrdersModel model) : base(model, "Market", OrderUtils.order_type_display_name(CustomOrderDefinition.CustomOneofCase.Market)) { }
public override void assign_to(CustomOrderDefinition parent) =>
parent.Market = factory.market_order();
@@ -12,36 +12,36 @@ namespace mbts.spark {
static partial class OrderTypeVMFactory {
public static IOrderTypeVM market(OrdersModel model) => new MarketOrderVM(model);
public static IOrderTypeVM limit(OrdersModel model) => new LimitOrderVM(model);
public static IOrderTypeVM stop(OrdersModel model) => new StopOrderVM(model);
public static IOrderTypeVM stop_limit(OrdersModel model, IObservable<SecurityMD> security_md_source) => new StopLimitOrderVM(model, security_md_source);
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TimeBoxOrderVM(model, security_md_source);
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TickOrderVM(model, security_md_source);
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityMD> security_md_source) => new LeanOrderVM(model, security_md_source);
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityMD> security_md_source) => new JoinOrderVM(model, security_md_source);
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TopOrderVM(model, security_md_source);
public static IOrderTypeVM cover(OrdersModel model, IObservable<SecurityMD> security_md_source) => new CoverOrderVM(model, security_md_source);
public static IOrderTypeVM trigger(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TriggerOrderVM(model, security_md_source);
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityMD> security_md_source) => new IcebergOrderVM(model, security_md_source);
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source) => new SpreaderOrderVM(model, security_source);
public static IOrderTypeVM dynamic(OrdersModel model, IObservable<SecurityMD> security_md_source, string type) => new DynamicOrderVM(model, security_md_source, type);
public static IOrderTypeVM stop(OrdersModel model, string id, string display_name) => new StopOrderVM(model, id, display_name);
public static IOrderTypeVM stop_limit(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new StopLimitOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new TimeBoxOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new TickOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new LeanOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new JoinOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new TopOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM cover(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new CoverOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM trigger(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new TriggerOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new IcebergOrderVM(model, security_md_source, id, display_name);
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source, string id, string display_name) => new SpreaderOrderVM(model, security_source, id, display_name);
public static IOrderTypeVM dynamic(OrdersModel model, IObservable<SecurityMD> security_md_source, string dynamic_type, string id, string display_name) => new DynamicOrderVM(model, security_md_source, dynamic_type, id, display_name);
public static IOrderTypeVM create(CustomOneofCase order_type, OrdersModel model,
public static IOrderTypeVM create(CustomOneofCase order_type, string id, string display_name, OrdersModel model,
IObservable<SecurityMD> security_md_source, string dynamic_type = null ) {
switch (order_type) {
case CustomOneofCase.Market: return market(model);
case CustomOneofCase.Limit: return limit(model);
case CustomOneofCase.Stop: return stop(model);
case CustomOneofCase.StopLimit: return stop_limit(model, security_md_source);
case CustomOneofCase.TimeBox: return time_box(model, security_md_source);
case CustomOneofCase.Tick: return tick(model, security_md_source);
case CustomOneofCase.Lean: return lean(model, security_md_source);
case CustomOneofCase.Join: return join(model, security_md_source);
case CustomOneofCase.Top: return top(model, security_md_source);
case CustomOneofCase.Cover: return cover(model, security_md_source);
case CustomOneofCase.Trigger: return trigger(model, security_md_source);
case CustomOneofCase.Iceberg: return iceberg(model, security_md_source);
case CustomOneofCase.Spreader: return spreader(model, security_md_source.Select(smd => smd.security));
case CustomOneofCase.Dynamic: return dynamic(model, security_md_source, dynamic_type);
case CustomOneofCase.Stop: return stop(model, id, display_name);
case CustomOneofCase.StopLimit: return stop_limit(model, security_md_source, id, display_name);
case CustomOneofCase.TimeBox: return time_box(model, security_md_source, id, display_name);
case CustomOneofCase.Tick: return tick(model, security_md_source, id, display_name);
case CustomOneofCase.Lean: return lean(model, security_md_source, id, display_name);
case CustomOneofCase.Join: return join(model, security_md_source, id, display_name);
case CustomOneofCase.Top: return top(model, security_md_source, id, display_name);
case CustomOneofCase.Cover: return cover(model, security_md_source, id, display_name);
case CustomOneofCase.Trigger: return trigger(model, security_md_source, id, display_name);
case CustomOneofCase.Iceberg: return iceberg(model, security_md_source, id, display_name);
case CustomOneofCase.Spreader: return spreader(model, security_md_source.Select(smd => smd.security), id, display_name);
case CustomOneofCase.Dynamic: return dynamic(model, security_md_source, dynamic_type, id, display_name);
default: throw new NotImplementedException();
}
}
@@ -51,12 +51,17 @@ namespace mbts.spark {
protected readonly OrdersModel model;
protected OrderFactory factory => model.factory;
protected OrderTypeVMBase(OrdersModel model) => this.model = model;
protected OrderTypeVMBase(OrdersModel model, string id, string display_name) {
this.model = model;
this.id = id;
this.display_name = display_name;
}
public abstract CustomOneofCase order_type { get; }
public abstract string type_name { get; }
public bool is_bts_type => display_name.StartsWith("BTS");
public string display_name => OrderUtils.order_type_display_name(order_type);
public string id { get; }
public string display_name { get; }
public OrderMetadata meta => factory.get_metadata(order_type);
public virtual bool has_editable_properties => !meta.NoProps;
public virtual bool editable_properties_open { get; set; }
@@ -76,7 +81,7 @@ namespace mbts.spark {
}
private abstract class OrderTypeVMBase<T> : OrderTypeVMBase where T : Google.Protobuf.IMessage, new() {
protected OrderTypeVMBase(OrdersModel model) : base(model) { }
protected OrderTypeVMBase(OrdersModel model, string id, string display_name) : base(model, id, display_name) { }
public override CustomOneofCase order_type =>
OrderDataHelper.get_order_type(new T());
@@ -13,20 +13,20 @@ namespace mbts.spark {
readonly StrategyEditorWrapper _strategy_editor_wrapper;
IDisposable _dispose;
public SpreaderOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
public SpreaderOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source, string id, string display_name)
: this(model, security_source, security_source.SelectMany(s => model.spreader.security_strategy(s.Id)
.Select(strategy => {
var so = model.factory.spreader_order();
so.Security = s.CloneToMutable();
so.ExecutionStrategy = strategy.CloneToMutable();
return new InlineUserProperty<SpreaderOrderDefinitionImmutable>(so, true);
}))) { }
})), id, display_name) { }
public SpreaderOrderVM(
OrdersModel model,
IObservable<SecurityImmutable> security_source,
IObservable<InlineUserProperty<SpreaderOrderDefinitionImmutable>> order_template_source)
: base(model) {
IObservable<InlineUserProperty<SpreaderOrderDefinitionImmutable>> order_template_source, string id, string display_name)
: base(model, id, display_name) {
_template = backing(nameof(template), new SpreaderOrderDefinitionImmutable?());
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
_dispose = Disposable.Create(
@@ -29,10 +29,11 @@ namespace mbts.spark {
InlineStopLimitOrderProperties _inline_props;
IDisposable _dispose;
internal StopLimitOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
: this(model, security_md_source.Select(smd => smd.security.spec_or_default()), InlineStopLimitOrderProperties.subscribe(security_md_source)) { }
internal StopLimitOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source.Select(smd => smd.security.spec_or_default()), InlineStopLimitOrderProperties.subscribe(security_md_source), id, display_name) { }
internal StopLimitOrderVM(OrdersModel model, IObservable<ContractSpecificationImmutable> spec_source, IObservable<InlineStopLimitOrderProperties> inline_properties) : base(model) {
internal StopLimitOrderVM(OrdersModel model, IObservable<ContractSpecificationImmutable> spec_source, IObservable<InlineStopLimitOrderProperties> inline_properties, string id, string display_name)
: base(model, id, display_name) {
_stop_price = new UserProperty<double>(backing(nameof(stop_price), double.NaN));
_dispose = Disposable.Create(
spec_source.OnDispatcher().Subscribe(s => {
@@ -6,8 +6,7 @@ namespace mbts.spark {
private class StopOrderVM : OrderTypeVMBase<StopOrderDefinition> {
double _stop_price = 0;
internal StopOrderVM(OrdersModel model) : base(model) { }
internal StopOrderVM(OrdersModel model, string id, string display_name) : base(model, id, display_name) { }
public double stop_price {
get => _stop_price;
@@ -17,11 +17,11 @@ namespace mbts.spark
{
class InlineTickOrderProperties
{
public InlineTickOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
public InlineTickOrderProperties (OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
@@ -29,8 +29,8 @@ namespace mbts.spark
max_chase_ticks = new InlineUserProperty<uint>(10, false);
detach_on_toward = new InlineUserProperty<bool>(true, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
@@ -43,8 +43,8 @@ namespace mbts.spark
{
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineTickOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
@@ -55,12 +55,12 @@ namespace mbts.spark
public uint max_chase_ticks { get; set; } = 10;
public bool detach_on_toward { get; set; } = true;
public TickOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public TickOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineTickOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
new InlineTickOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name) { }
public TickOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
IObservable<InlineTickOrderProperties> inline_properties) : base(model)
IObservable<InlineTickOrderProperties> inline_properties, string id, string display_name) : base(model, id, display_name)
{
_security_md_source = security_md_source;
_dispose = Disposable.Create(
@@ -74,7 +74,7 @@ namespace mbts.spark
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
if (p.tick_offset_on_deploy.force_override)
{
@@ -131,7 +131,7 @@ namespace mbts.spark
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _child_order_choices;
}
}
@@ -177,7 +177,7 @@ namespace mbts.spark
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -14,15 +14,15 @@ using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class InlineTimeBoxOrderProperties {
public InlineTimeBoxOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types) {
public InlineTimeBoxOrderProperties(OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types) {
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
start = new InlineUserProperty<DateTime>(DateTime.Now, true);
stop = new InlineUserProperty<DateTime>(DateTime.Now.AddMinutes(5), true);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<DateTime> start, stop;
}
@@ -30,8 +30,8 @@ namespace mbts.spark {
const double _start_immediately_delay_ms = 500;
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineTimeBoxOrderProperties _inline_props;
TimeTextTuple _absolute_start = TimeTextTuple.FromDateTime(DateTime.Now.AddMinutes(5)), _absolute_stop = TimeTextTuple.FromDateTime(DateTime.Now.AddMinutes(10));
TimeSpan? _relative_start = TimeSpan.FromMinutes(5), _relative_stop = TimeSpan.FromMinutes(5);
@@ -40,14 +40,14 @@ namespace mbts.spark {
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
public TimeBoxOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public TimeBoxOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineTimeBoxOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
new InlineTimeBoxOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name) { }
public TimeBoxOrderVM(OrdersModel model,
IObservable<SecurityMD> security_md_source,
IObservable<InlineTimeBoxOrderProperties> inline_properties)
: base(model) {
IObservable<InlineTimeBoxOrderProperties> inline_properties, string id, string display_name)
: base(model, id, display_name) {
_security_md_source = security_md_source;
_dispose = Disposable.Create(
security_md_source.OnDispatcher()
@@ -58,7 +58,7 @@ namespace mbts.spark {
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override)) {
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
if (p.start.force_override) {
absolute_start = TimeTextTuple.FromDateTime(p.start.value);
@@ -94,7 +94,7 @@ namespace mbts.spark {
public ICollection<IOrderTypeVM> child_order_types {
get {
if (_child_order_choices == null) {
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
}
return _child_order_choices;
}
@@ -173,7 +173,7 @@ namespace mbts.spark {
void _select_child_order() {
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -17,17 +17,17 @@ namespace mbts.spark
{
class InlineTopOrderProperties
{
public InlineTopOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
public InlineTopOrderProperties(OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types)
{
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
var hedgetype = CustomOneofCase.Lean;
if(!child_order_types.Contains(hedgetype))
if(!child_order_types.Any(ct => ct.key.type == hedgetype))
hedgetype = CustomOneofCase.Limit;
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_hedge_order_type = new InlineUserProperty<OrderTemplateHeader>(new OrderTemplateHeader(hedgetype), true);
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
watch_level_tick_offset = new InlineUserProperty<uint>(0, false);
top_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
@@ -35,10 +35,10 @@ namespace mbts.spark
enable_hedge = new InlineUserProperty<bool>(false, false);
hedge_ticks = new InlineUserProperty<int>(1, false);
}
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_hedge_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> hedge_order_types;
public readonly InlineUserProperty<uint> watch_level_tick_offset;
@@ -53,13 +53,13 @@ namespace mbts.spark
{
readonly IObservable<SecurityMD> _security_md_source;
SecurityImmutable? _security;
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineTopOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _hedge_order_choices;
IOrderTypeVM _hedge_order;
IDisposable _hedge_order_errors_sub;
@@ -77,13 +77,13 @@ namespace mbts.spark
}
public int hedge_ticks { get; set; } = 1;
public TopOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public TopOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineTopOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList())))
new InlineTopOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name)
{ }
public TopOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
IObservable<InlineTopOrderProperties> inline_properties) : base(model)
IObservable<InlineTopOrderProperties> inline_properties, string id, string display_name) : base(model, id, display_name)
{
_security_md_source = security_md_source;
_dispose = Disposable.Create(
@@ -98,11 +98,11 @@ namespace mbts.spark
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
{
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
{
_select_hedge_order(p.selected_hedge_order_type.value);
_select_hedge_order(p.selected_hedge_order_type.value.key.type);
}
if (p.watch_level_tick_offset.force_override)
{
@@ -157,7 +157,7 @@ namespace mbts.spark
get
{
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _child_order_choices;
}
}
@@ -167,7 +167,7 @@ namespace mbts.spark
get
{
if (_hedge_order_choices == null)
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _hedge_order_choices;
}
}
@@ -241,7 +241,7 @@ namespace mbts.spark
void _select_child_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
@@ -257,7 +257,7 @@ namespace mbts.spark
void _select_hedge_order()
{
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
_inline_props.selected_hedge_order_type.value :
_inline_props.selected_hedge_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_hedge_order(ot);
}
@@ -14,8 +14,8 @@ using static bts.api.orderman.CustomOrderDefinition;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class InlineTriggerOrderProperties {
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
public readonly InlineUserProperty<TriggerPriceType> trigger_price_type;
public readonly InlineUserProperty<TriggerPayupPrice.Types.TriggerPayupPriceType> payup_price_type;
public readonly InlineUserProperty<double> fixed_price;
@@ -23,10 +23,10 @@ namespace mbts.spark {
public readonly InlineUserProperty<bool> use_fixed_price, is_priceout_checked, is_timeout_checked;
public readonly InlineUserProperty<int> timeout_secs;
public InlineTriggerOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types) {
public InlineTriggerOrderProperties(OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> child_order_types) {
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
trigger_price_type = new InlineUserProperty<TriggerPriceType>(TriggerPriceType.Bid, false);
payup_price_type = new InlineUserProperty<TriggerPayupPrice.Types.TriggerPayupPriceType>();
use_fixed_price = new InlineUserProperty<bool>(false);
@@ -47,19 +47,20 @@ namespace mbts.spark {
readonly UserProperty<bool> _use_fixed_price, _is_timeout_checked, _is_priceout_checked;
SecurityMD? _security_md;
SecurityImmutable? _security => _security_md.security();
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
InlineTriggerOrderProperties _inline_props;
IOrderTypeVM _child_order;
IDisposable _dispose, _child_order_errors_sub;
ulong _bbo_ref = 0, _last_trade_ref = 0;
double _best_bid = double.NaN, _best_ask = double.NaN, _last_trade_price = double.NaN;
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
: this(model, security_md_source, security_md_source.Select(smd =>
new InlineTriggerOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
new InlineTriggerOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name) { }
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineTriggerOrderProperties> inline_properties) : base(model) {
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineTriggerOrderProperties> inline_properties, string id, string display_name)
: base(model, id, display_name) {
_trigger_price_type = create_user_prop(nameof(trigger_price_type), TriggerPriceType.Bid);
_use_fixed_price = create_user_prop(nameof(use_fixed_price), true);
_fixed_price = create_user_prop(nameof(fixed_price), double.NaN);
@@ -88,7 +89,7 @@ namespace mbts.spark {
_order_types.SetItems(p.child_order_types.value);
}
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override)) {
_select_child_order(p.selected_child_order_type.value);
_select_child_order(p.selected_child_order_type.value.key.type);
}
// trigger price
_trigger_price_type.on_inline_prop(p.trigger_price_type);
@@ -135,7 +136,7 @@ namespace mbts.spark {
public ICollection<IOrderTypeVM> child_order_types {
get {
if (_child_order_choices == null)
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
return _child_order_choices;
}
}
@@ -216,7 +217,7 @@ namespace mbts.spark {
void _select_child_order() {
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
_inline_props.selected_child_order_type.value :
_inline_props.selected_child_order_type.value.key.type :
model.factory.default_order_type(_security.Value, true);
_select_child_order(ot);
}
+5
View File
@@ -75,6 +75,11 @@
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
</ItemGroup>
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
</Target>
<Target Name="copy_defaults" AfterTargets="PostBuildEvent">
+2 -2
View File
@@ -113,10 +113,10 @@ namespace bts.spark
// TODO: remove this method, eventually want all windows to use window_settings_command()
public static SparkCommand change_securities_command(Func<(PartialCommand<SecurityCriteria>, SecurityCriteria)> get_param) =>
SparkCommand.create("Settings", Icons.Admin, SparkTags.ShowAndEditPickMultiple, get_param);
SparkCommand.create("Securities", Icons.HandPointingOverDocument, SparkTags.ShowAndEditPickMultiple, get_param);
public static PartialCommand<SecurityCriteria> change_securities_callback(string window_name) =>
SparkCommand.create(window_name, Icons.Admin, new SparkTags.SparkTag<SecurityCriteria>($"Change {window_name} securities callback"));
SparkCommand.create(window_name, Icons.HandPointingOverDocument, new SparkTags.SparkTag<SecurityCriteria>($"Change {window_name} securities callback"));
public static SparkCommand window_settings_command(IDialogCommand launch_settings_editor) =>
new SparkCommandWrapper("Settings", Icons.Admin, launch_settings_editor);
@@ -5,6 +5,9 @@ namespace mbts.utils {
public static class Argument {
public static ArgumentException must_be(string description, string name) =>
new ArgumentException($"Must be {description}.", name);
public static ArgumentException unexpected_case<T>(T value, string name) =>
new ArgumentException($"Unexpected {typeof(T)} '{value}.'", name);
}
}
}
@@ -608,6 +608,12 @@ namespace mbts.ui {
public static BTSColor positive_diff { get { return Color.FromRgb(0, 90, 50); } }
}
public static class LadderSpecial {
public static BTSColor passive_volume_bar { get; } = Colors.SlateBlue;
public static BTSColor total_volume_bar { get; } = 0x009B69;
public static BTSColor default_column_background { get; } = 0xBABABA;
}
// Style Guide Colors
public static Color brand_lightest { get { return UI.brand_lightest; } }
public static Brush brand_lightest_brush { get { return new SolidColorBrush(brand_lightest); } }
@@ -271,9 +271,6 @@ namespace System.Collections.Generic {
#endregion
#region Max
public static T Max<TElement, T>(this IEnumerable<TElement> enumerable, Func<TElement, T> select_value) where T : IComparable<T> =>
Max(enumerable, select_value, default(T));
public static T Max<TElement, T>(this IEnumerable<TElement> enumerable, Func<TElement, T> select_value, T return_if_empty) where T : IComparable<T> {
var etr = enumerable.GetEnumerator();
if (!etr.MoveNext()) return return_if_empty;
@@ -153,40 +153,6 @@ namespace mbts
return rv.ToDictionary(kv => kv.Key, kv => kv.Value);
}
//this overloaded declaration, (as opposed to one function with comp = null) allows observable_collection.sync_with to be passed into functions as a parameterless Action
public static void sync_with<T>(this IList<T> dest, IEnumerable<T> src) { dest.sync_with(src, EqualityComparer<T>.Default); }
public static void sync_with<T>(this IList<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
Log.notnull(dest);
if (src == null) {
dest.Clear();
return;
}
var srcList = src as IList<T> ?? src.ToList();
var changes = bts.utils.Diff.diff(dest, srcList, comp).ToList();
// Apply the diff
for(int i = 0; i < changes.Count; ++i) {
var c = changes[i];
// removes
for (var x = 0; x < c.deletedLeft; ++x) {
dest.RemoveAt(c.StartLeft);
}
// inserts
for (var x = 0; x < c.insertedRight; ++x) {
dest.Insert(c.StartLeft + x, srcList[c.StartRight + x]);
}
// Adjust start index of remaining changes
for (int ii = i + 1; ii < changes.Count; ++ii) {
var cc = changes[ii];
var delta = c.insertedRight - c.deletedLeft;
if (cc.StartLeft >= c.StartLeft) {
cc.StartLeft += delta;
changes[ii] = cc;//remember: struct
}
}
}
}
public static bool sync_with_setwise<T>(this ObservableCollection<T> dest, IEnumerable<T> src) { return dest.sync_with_setwise(src, EqualityComparer<T>.Default); }
public static bool sync_with_setwise<T>(this ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
if (!src.is_distinct(comp)) {
@@ -103,9 +103,23 @@ namespace mbts {
return _round_tick(tuf, val);
}
public static (int n_low, int n_high) tickdiff(this RegionalTickFactors regional_tick, double x, double y) {
if (DoubleHelper.IsNaNOrInfinity(x)) throw new ArgumentException("Cannot be NaN or Infinity.", nameof(x));
if (DoubleHelper.IsNaNOrInfinity(y)) throw new ArgumentException("Cannot be NaN or Infinity.", nameof(y));
public static bool try_get_tickdiff(this RegionalTickFactors regional_tick, double x, double y, out int n_low, out int n_high) {
if (DoubleHelper.IsNaNOrInfinity(x) || DoubleHelper.IsNaNOrInfinity(y)) {
n_low = default;
n_high = default;
return false;
}
(n_low, n_high) = tickdiff_unchecked(regional_tick, x, y);
return true;
}
/// <summary>
/// Callers should be certain that <paramref name="x"/> and <paramref name="y"/> are not nan or infinity.
/// </summary>
/// <param name="regional_tick"></param>
/// <param name="x"></param>
/// <param name="y"></param>
internal static (int n_low, int n_high) tickdiff_unchecked(this RegionalTickFactors regional_tick, double x, double y) {
double ltie = regional_tick.low_tick_inclusive_end;
double raw_diff = x - y;
if (x < ltie) {
@@ -1,5 +1,5 @@
using bts.proto;
using Google.Protobuf.WellKnownTypes;
using Google.Protobuf.WellKnownTypes;
using mbts.ui.settings;
using ProtoBuf;
using System;
@@ -12,7 +12,12 @@ namespace mbts.spark {
public static UserSettings factory_defaults =>
new UserSettings {
_ladder = LadderSettings.Default,
_notification = NotificationSettings.Default
_notification = NotificationSettings.Default,
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings,
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
_order_book_grid = bts.shell.OrderBookWindowFactory.OrderBookStartupData.DefaultGrid
};
[ProtoMember(1)]
@@ -21,19 +26,53 @@ namespace mbts.spark {
// 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;
[ProtoMember(5)]
GridSettings _market_view_grid;
[ProtoMember(6)]
GridSettings _time_and_sales_grid;
[ProtoMember(7)]
GridSettings _fills_grid;
[ProtoMember(8)]
GridSettings _synthetic_management_grid;
[ProtoMember(9)]
GridSettings _pnl_grid;
[ProtoMember(10)]
bts.shell.OrderBookWindowFactory.OrderBookGridSettings _order_book_grid;
public UserSettings() { }
public LadderSettings ladder => _ladder ?? (_ladder = LadderSettings.Default);
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));
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings);
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
public bts.shell.OrderBookWindowFactory.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.OrderBookWindowFactory.OrderBookStartupData.Default.grid_settings_wrapper());
public UserSettings with(LadderSettings ladder = null, NotificationSettings notification = null) =>
public UserSettings with(LadderSettings ladder = 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.OrderBookWindowFactory.OrderBookGridSettings order_book_grid = null) =>
new UserSettings {
_ladder = ladder ?? _ladder,
_notification = notification ?? _notification
_notification = notification ?? _notification,
_market_view_grid = provided_market_view_gridsettings ?? _market_view_grid,
_time_and_sales_grid = time_and_sales ?? _time_and_sales_grid,
_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
};
public static class FileExtensions {
public const string LADDER = "ladder";
public const string
LADDER = "ladder",
GRID = "grid";
}
}
@@ -16,7 +16,8 @@ namespace mbts.persist {
const string NAME_SPACE = "settings", DEFAULT_KEY = "default";
IUserPersist _persist;
UserSettings _default_settings;
readonly Subject<UserSettings> _default_settings_subject = new Subject<UserSettings>();
readonly BehaviorSubject<UserSettings> _default_settings_subject = new BehaviorSubject<UserSettings>(UserSettings.factory_defaults);
readonly object _default_settings_lock = new object();
[ImportingConstructor]
public UserSettingsHub(IUserPersist persist) {
@@ -33,7 +34,12 @@ namespace mbts.persist {
/// 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();
internal UserSettings current => _default_settings ?? UserSettings.factory_defaults;
internal UserSettings current {
get {
lock (_default_settings_lock)
return _default_settings ?? UserSettings.factory_defaults;
}
}
public async Task save_default(UserSettings value) {
_set_and_notify(value);
@@ -41,9 +47,11 @@ namespace mbts.persist {
}
void _set_and_notify(UserSettings value) {
_default_settings = value;
// notify observers before persisting so the changes are reflected immediately in the ui
_default_settings_subject.OnNext(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);
}
}
}
}
@@ -1,7 +1,9 @@
using mbts.spark;
using mbts.utils;
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
namespace mbts.persist {
@@ -11,8 +13,15 @@ namespace mbts.persist {
event Action<T> instance_settings_changed;
IObservable<T> subscribe_default();
Task save_default_settings(T value);
IWindowSettingsManagerNonShared<T> as_non_shared();
}
/// <summary>
/// An implementation of IWindowSettingsManager that uses a clone of the actual default settings;
/// provides a convenient way to avoid problems that arise from sharing a single default settings object among multiple window instances.
/// </summary>
interface IWindowSettingsManagerNonShared<T> : IWindowSettingsManager<T>, IDisposable where T : class { }
class WindowSettingsManager<T> : IWindowSettingsManager<T> where T : class {
readonly UserSettingsHub _hub;
readonly Func<UserSettings, T> _get;
@@ -43,6 +52,42 @@ namespace mbts.persist {
public IObservable<T> subscribe_default() =>
_hub.subscribe_default_settings().Select(_get).DistinctUntilChanged();
public IWindowSettingsManagerNonShared<T> as_non_shared() =>
new NonSharedWrapper(this);
class NonSharedWrapper : IWindowSettingsManagerNonShared<T> {
readonly WindowSettingsManager<T> _wsm;
readonly BehaviorSubject<T> _default_subject;
IDisposable _dispose;
public NonSharedWrapper(WindowSettingsManager<T> wsm) {
_wsm = wsm;
_default_subject = new BehaviorSubject<T>(_wsm.default_settings ?? wsm._get(UserSettings.factory_defaults));
_dispose = wsm.subscribe_default().Subscribe(s =>
_default_subject.OnNext(default_settings = MiscUtils.clone_proto(s)));
}
public T instance_settings {
get => _wsm.instance_settings;
set => _wsm.instance_settings = value;
}
public T default_settings { get; private set; }
public event Action<T> instance_settings_changed {
add => _wsm.instance_settings_changed += value;
remove => _wsm.instance_settings_changed -= value;
}
public async Task save_default_settings(T value) =>
await _wsm.save_default_settings(value);
public IObservable<T> subscribe_default() =>
_default_subject.AsObservable();
public IWindowSettingsManagerNonShared<T> as_non_shared() =>
new NonSharedWrapper(_wsm);
public void Dispose() => Disposable.dispose(ref _dispose);
}
}
static class WindowSettingsManagerExtensions {
@@ -54,8 +99,7 @@ namespace mbts.persist {
Observable.CombineLatest(
Observable.FromEvent<Action<T>, T>(cb => wsm.instance_settings_changed += cb, cb => wsm.instance_settings_changed -= cb)
.StartWith(wsm.instance_settings),
wsm.subscribe_default()
.StartWith(wsm.default_settings),
wsm.subscribe_default(),
(i, d) => i ?? d
).Where(v => v != null)
.DistinctUntilChanged();
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -14,16 +13,13 @@ namespace mbts.utils {
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
int i = 0;
foreach (var val in src.empty_if_null()) {
int curr_index = dest.TakeWhile(dval => !comp.Equals(dval, val)).Count();
if (curr_index == dest.Count) {
dest.Add(val);
} else if (curr_index == i) {
int curr_index = dest.IndexOf(val);
if (curr_index == i) {
continue;
} else if (curr_index < dest.Count) {
} else if (curr_index > 0 && curr_index < dest.Count) {
dest.RemoveAt(curr_index);
dest.Insert(i, val);
}
++i;
dest.Insert(i++, val);
}
}
@@ -49,5 +45,12 @@ namespace mbts.utils {
++i;
}
}
public static TResult[] convert_array<T, TResult>(T[] source, Func<T, TResult> selector) {
TResult[] rv = new TResult[source.Length];
for (int i = 0; i < rv.Length; i++)
rv[i] = selector(source[i]);
return rv;
}
}
}
@@ -1,156 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using bts;
using bts.api;
namespace mbts.ui.utility
{
class ExpirationFormatter
{
private static class DateFormatter
{
private static string[] month_codes = new string[] { "F", "G", "H", "J", "K", "M", "N", "Q", "U", "V", "X", "Z" };
private static string[] month_three_letters = new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
public static string format_condensed(DateTime date, bool use_month_code, bool show_year)
{
return String.Format("{0}{1}{2}", date.Day, month_codes[date.Month - 1], show_year ? (date.Year - 2000).ToString() : String.Empty);
}
public static string format(DateTime date, bool use_month_code, bool show_day) {
string month_text = use_month_code ? "{0}{1:00}".fmt(month_codes[date.Month - 1], date.Year - 2000) : "{0} {1:00}".fmt(month_three_letters[date.Month - 1], date.Year - 2000);
return show_day ? "{0} {1}".fmt(date.Day, month_text) : month_text;
}
public static string format_standard(DateTime date) { return "{0} {1} {2:00}".fmt(date.Day, month_three_letters[date.Month - 1], date.Year - 2000); }
public static string format_coded(DateTime date) { return "{0} {1}{2:00}".fmt(date.Day, month_codes[date.Month - 1], date.Year - 2000); }
public static string format_coded_hide_day(DateTime date) { return "{0}{1}".fmt(month_codes[date.Month - 1], date.Year - 2000); }
public static DateTime parse_standard(string date_text) {
string[] split = date_text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int year = 0;
int month = Array.IndexOf(month_three_letters, split[1]) + 1;
int day = 0;
int.TryParse(split[2], out year);
int.TryParse(split[0], out day);
return new DateTime(year + 2000, month, day);
}
public static DateTime parse_coded(string date_text) {
string[] split = date_text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
string year_text = split[1].Substring(1);
string month_text = split[1].Substring(0, 1);
string day_text = split[0];
int month = Array.IndexOf(month_codes, month_text) + 1;
int day, year;
int.TryParse(year_text, out year);
int.TryParse(day_text, out day);
return new DateTime(year + 2000, month, day);
}
public static DateTime parse_coded_hide_day(string date_text) {
string year_text = date_text.Substring(1);
string month_text = date_text.Substring(0, 1);
int month = Array.IndexOf(month_codes, month_text) + 1;
int year;
int.TryParse(year_text, out year);
return new DateTime(year + 2000, month, 0);
}
}
private static IDictionary<SecurityType, IDictionary<string, bts.config.ExpirationDisplayOffsetEntry>> _offset_map;
static ExpirationFormatter()
{
try
{
_offset_map = new Dictionary<SecurityType, IDictionary<string, bts.config.ExpirationDisplayOffsetEntry>>();
_offset_map[SecurityType.Option] = new Dictionary<string, bts.config.ExpirationDisplayOffsetEntry>();
_offset_map[SecurityType.Future] = new Dictionary<string, bts.config.ExpirationDisplayOffsetEntry>();
bts.config.ExpirationDisplayOffset offset_list = mbts.config.Configuration.get<bts.config.ExpirationDisplayOffset>();
foreach (bts.config.ExpirationDisplayOffsetEntry e in offset_list.entries)
{
string security_name = e.security_name;
string security_type_text = e.security_type;
SecurityType security_type = SecurityType.Unset;
Enum.TryParse<SecurityType>(security_type_text, true, out security_type);
if (security_type == SecurityType.Option || security_type == SecurityType.Future)
_offset_map[security_type][security_name] = e;
}
}
catch (Exception ex)
{
Log.Ui.error(String.Format("Exception message = {0}", ex.Message));
}
}
private static bts.config.ExpirationDisplayOffsetEntry _get_offset_entry(SecurityType security_type, string security_name)
{
bts.config.ExpirationDisplayOffsetEntry rv = null;
IDictionary<string, bts.config.ExpirationDisplayOffsetEntry> this_security_type_map = null;
if (_offset_map.TryGetValue(security_type, out this_security_type_map))
if (this_security_type_map.TryGetValue(security_name, out rv))
return rv;
return rv;
}
public static DateTime convert_real_to_mapped(SecurityType security_type, string security_name, DateTime security_expiration)
{
bts.config.ExpirationDisplayOffsetEntry offset_entry = _get_offset_entry(security_type, security_name);
DateTime rv = security_expiration;
if (offset_entry != null)
{
if (offset_entry.addition_type == bts.config.ExpirationOffsetType.type.Month)
rv = security_expiration.AddMonths(offset_entry.addition_count);
else if (offset_entry.addition_type == bts.config.ExpirationOffsetType.type.MonthMoveToStart)
{
rv = security_expiration.AddMonths(offset_entry.addition_count);
rv = new DateTime(rv.Year, rv.Month, 1);
}
}
return rv;
}
public static string format_expiration_standard(SecurityType security_type, string security_name, DateTime security_expiration, bool map_expiration) {
return format_expiration_standard(security_type, security_name, security_expiration, map_expiration, false);
}
public static string format_expiration_standard(SecurityType security_type, string security_name, DateTime security_expiration, bool map_expiration, bool use_month_code) {
return format_expiration_standard(security_type, security_name, security_expiration, map_expiration, use_month_code, true);
}
public static string format_expiration_standard(SecurityType security_type, string security_name, DateTime security_expiration, bool map_expiration, bool use_month_code, bool show_day) {
DateTime mapped_expiration = map_expiration ? convert_real_to_mapped(security_type, security_name, security_expiration) : security_expiration;
return DateFormatter.format(mapped_expiration, use_month_code, show_day);
}
public static string format_symbol_expiration_standard (SecurityType security_type, string security_name, DateTime security_expiration, bool map_expiration) {
return format_symbol_expiration_standard(security_type, security_name, security_expiration, map_expiration, false, false, true);
}
public static string format_symbol_expiration_standard (SecurityType security_type, string security_name, DateTime security_expiration, bool map_expiration, bool show_security_type) {
return format_symbol_expiration_standard(security_type, security_name, security_expiration, map_expiration, show_security_type, false, true);
}
public static string format_symbol_expiration_standard(SecurityType security_type, string security_name, DateTime security_expiration, bool map_expiration, bool show_security_type, bool use_month_code) {
return format_symbol_expiration_standard(security_type, security_name, security_expiration, map_expiration, show_security_type, use_month_code, true);
}
public static string format_symbol_expiration_standard (SecurityType security_type, string security_name, DateTime security_expiration, bool map_expiration, bool show_security_type, bool use_month_code, bool show_day) {
string exp_text = format_expiration_standard(security_type, security_name, security_expiration, map_expiration, use_month_code, show_day);
return !show_security_type ? String.Format("{0} {1}", security_name, exp_text) : String.Format("{0} {1} {2}", security_type.ToString().ToUpper(), security_name, exp_text);
}
public static string format_option_expiration_condensed(string option_name, DateTime option_expiration, bool map_expiration, bool show_symbol_name, bool show_year)
{
DateTime mapped_expiration = map_expiration ? convert_real_to_mapped(SecurityType.Option, option_name, option_expiration) : option_expiration;
return String.Format("{0}{1}", show_symbol_name ? option_name : String.Empty, DateFormatter.format_condensed(mapped_expiration, true, show_year));
}
public static DateTime parse_standard_formatted_expiration(SecurityType security_type, string security_name, string security_expiration) { return DateFormatter.parse_standard(security_expiration); }
}
}
@@ -17,7 +17,8 @@ namespace mbts.utils {
/// <seealso cref="Observable.SelectMany"/>
class KeyDrivenObservableSeries<TKey, TValue> : ISubject<TKey, TValue>, IDisposable {
readonly Func<TKey, IObservable<TValue>> _selector;
object _lock = new object();
readonly object _lock = new object();
bool _is_disposed;
Subject<TValue> _value_subject = new Subject<TValue>();
IDisposable _current_key_sub;
@@ -31,15 +32,19 @@ namespace mbts.utils {
// need to lock bc simultaneous callbacks on diff threads could generate 2 subscriptions at once,
// only 1 of which would get disposed in the next callback.
Disposable.dispose(ref _current_key_sub);
_current_key_sub = _selector(key)?.Subscribe(_value_subject);
if (!_is_disposed)
_current_key_sub = _selector(key)?.Subscribe(_value_subject);
}
}
public IDisposable Subscribe(IObserver<TValue> observer) => _value_subject.Subscribe(observer);
public void Dispose() {
Disposable.dispose(ref _current_key_sub);
Disposable.dispose(ref _value_subject);
lock (_lock) {
_is_disposed = true;
Disposable.dispose(ref _current_key_sub);
Disposable.dispose(ref _value_subject);
}
}
}
@@ -0,0 +1,36 @@
namespace mbts.utils {
/// <summary>
/// Facilities multi-threaded read/write operations to a particular <typeparamref name="T"/> reference.
/// </summary>
/// <typeparam name="T">The reference type to be read/written.</typeparam>
class RefValueContainer<T> where T : class {
readonly object _lock = new object();
T _value;
bool _is_dirty;
/// <summary>
/// Returns the current value if it has not been read already, otherwise returns null.
/// </summary>
public T read_new_value() {
lock (_lock) {
if (_is_dirty) {
_is_dirty = false;
return _value;
}
return null;
}
}
public void write_value(T value) {
lock (_lock) {
_is_dirty = true;
_value = value;
}
}
public void mark_dirty() {
lock (_lock)
_is_dirty = true;
}
}
}
+73 -51
View File
@@ -110,7 +110,7 @@ namespace mbts.ui.shell
#endregion
#region automatic property
protected delegate void PropertyChange<T>(T oldval, T newval);
protected internal delegate void PropertyChange<T>(T oldval, T newval);
// use nameof() to add compile time checks
protected Property<T> backing<T>(string name, T default_value = default(T)) =>
@@ -526,7 +526,7 @@ namespace mbts.ui.shell
/// <para/>If the user has not selected an item, but the current items do not contain the desired item, it will re-evaluate whether or not to select the item when new items arrive.
/// </summary>
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, IReadOnlyList<PropertyValidation<T>> validation_steps) =>
backing(name, items, desired_item, select_default, null, validation_steps);
backing(name, items, desired_item, select_default, EqualityComparer<T>.Default, validation_steps);
/// <summary>
/// Returns a SelectorProperty&lt;T&gt; that observes an items source, and a desired item. When either observable ticks, it determines whether or not to assign the selected_item property.
@@ -538,8 +538,8 @@ namespace mbts.ui.shell
/// <param name="desired_item">The observable sequence of a desired item.</param>
/// <param name="select_default">The fallback function to select an item by default.</param>
/// <param name="items_match">The item-matching function used to preserve a selected item when new items arrive.</param>
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, Func<T, T, bool> items_match) =>
backing(name, items, desired_item, select_default, items_match, new List<PropertyValidation<T>>(0));
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, IEqualityComparer<T> comparer) =>
backing(name, items, desired_item, select_default, comparer, new List<PropertyValidation<T>>(0));
/// <summary>
/// Returns a SelectorProperty&lt;T&gt; that observes an items source, and a desired item. When either observable ticks, it determines whether or not to assign the selected_item property.
@@ -552,14 +552,24 @@ namespace mbts.ui.shell
/// <param name="select_default">The fallback function to select an item by default.</param>
/// <param name="items_match">The item-matching function used to preserve a selected item when new items arrive.</param>
/// <param name="validation_steps">The validation steps to take when selected_item changes.</param>
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, Func<T, T, bool> items_match, IReadOnlyList<PropertyValidation<T>> validation_steps) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
var prop = new SelectorProperty<T>(this, name, select_default, items_match, validation_steps);
_observable_properties.Add(items.OnDispatcher().Subscribe(prop));
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(prop));
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, IEqualityComparer<T> comparer, IReadOnlyList<PropertyValidation<T>> validation_steps) {
var prop = new SelectorProperty<T>(this, name, select_default, comparer, validation_steps);
attach(prop, items, desired_item);
return prop;
}
protected SelectorProperty<T, V> backing<T, V>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T, V> item_to_value, Func<V> select_default, IEqualityComparer<V> comparer, IReadOnlyList<PropertyValidation<V>> validation_steps) {
var prop = new SelectorProperty<T, V>(this, name, item_to_value, select_default, comparer, validation_steps);
attach(prop, items, desired_item);
return prop;
}
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));
}
protected void set_dispose(IDisposable disposable) {
if (_dispose != null) throw new InvalidOperationException("Cannot set disposable twice");
_dispose = disposable;
@@ -576,37 +586,44 @@ namespace mbts.ui.shell
OnDisposed?.Invoke(true);
}
protected class SelectorProperty<T> : SelectorProperty<T, T> {
public SelectorProperty(DisposableViewModelBase owner, string prop_name, Func<T> select_default, IEqualityComparer<T> comparer, IReadOnlyList<PropertyValidation<T>> validation_steps)
: base(owner, prop_name, i => i, select_default, comparer, validation_steps) { }
}
/// <summary>
/// Designed to observe an items source, and a [settings-driven] desired item. When a new desired item is observed, it will evaluate it's current state and determine if it should be assigned to the selected_item.
/// <para/>Notes: If the user has selected an item (ie the selected_item has been assigned through the public setter), the desired item will be ignored unless force_override is true.
/// <para/>If the user has not selected an item, but the current items do not contain the desired item, it will re-evaluate whether or not to select the item when new items arrive.
/// </summary>
protected class SelectorProperty<T> : IObserver<InlineUserProperty<T>>, IObserver<IEnumerable<T>>, IObservable<(T oldval, T newval)> {
protected class SelectorProperty<TItem, TValue> : IObserver<InlineUserProperty<TItem>>, IObserver<IEnumerable<TItem>>, IObservable<(TValue oldval, TValue newval)> {
readonly DisposableViewModelBase _owner;
readonly string _prop_name;
readonly IReadOnlyList<PropertyValidation<T>> _validation_steps;
readonly ObservableCollection<T> _items = new ObservableCollection<T>();
readonly Func<T> _select_default;
readonly Func<T, T, bool> _items_match; // used to maintain selected_item through an items-change
T _selected_item;
InlineUserProperty<T> _desired_item;
readonly IReadOnlyList<PropertyValidation<TValue>> _validation_steps;
readonly ObservableCollection<TItem> _items = new ObservableCollection<TItem>();
readonly Func<TValue> _select_default;
readonly IEqualityComparer<TValue> _value_comparer;
readonly Func<TItem, TValue> _item_to_value;
TValue _selected_item;
InlineUserProperty<TItem> _desired_item = new InlineUserProperty<TItem>();
bool _is_user_selected_item;
public SelectorProperty(DisposableViewModelBase owner, string prop_name, Func<T> select_default, Func<T, T, bool> items_match, IReadOnlyList<PropertyValidation<T>> validation_steps) {
public SelectorProperty(DisposableViewModelBase owner, string prop_name, Func<TItem, TValue> item_to_value, Func<TValue> select_default, IEqualityComparer<TValue> value_comparer, IReadOnlyList<PropertyValidation<TValue>> validation_steps) {
_owner = owner;
_prop_name = prop_name;
_item_to_value = item_to_value;
_select_default = select_default;
_items_match = items_match;
_value_comparer = value_comparer ?? EqualityComparer<TValue>.Default;
_validation_steps = validation_steps;
items = new ReadOnlyObservableCollection<T>(_items);
items = new ReadOnlyObservableCollection<TItem>(_items);
}
public event PropertyChange<T> Changing;
public event PropertyChange<T> Changed;
public event PropertyChange<TValue> Changing;
public event PropertyChange<TValue> Changed;
public ReadOnlyObservableCollection<T> items { get; }
public ReadOnlyObservableCollection<TItem> items { get; }
public T selected_item {
public TValue selected_item {
get => _selected_item;
set {
_is_user_selected_item = true;
@@ -614,31 +631,31 @@ namespace mbts.ui.shell
}
}
public IDisposable Subscribe(IObserver<(T oldval, T newval)> observer) {
public IDisposable Subscribe(IObserver<(TValue oldval, TValue newval)> observer) {
Changed += pch;
return Disposable.Create(() => Changed -= pch);
void pch(T o, T n) => observer.OnNext((o, n));
void pch(TValue o, TValue n) => observer.OnNext((o, n));
}
public IObservable<T> observe_current(bool start_now = false) {
public IObservable<TValue> observe_current(bool start_now = false) {
var source = this.Select(vals => vals.newval);
if (start_now)
source = source.StartWith(_selected_item);
source = source.StartWith(selected_item);
return source;
}
void IObserver<InlineUserProperty<T>>.OnNext(InlineUserProperty<T> iup) {
void IObserver<InlineUserProperty<TItem>>.OnNext(InlineUserProperty<TItem> iup) {
_desired_item = iup;
_try_auto_select();
}
void IObserver<InlineUserProperty<T>>.OnError(Exception error) { }
void IObserver<InlineUserProperty<T>>.OnCompleted() { }
void IObserver<InlineUserProperty<TItem>>.OnError(Exception error) { }
void IObserver<InlineUserProperty<TItem>>.OnCompleted() { }
void IObserver<IEnumerable<T>>.OnNext(IEnumerable<T> value) {
if (_items_match != null && _is_user_selected_item) {
void IObserver<IEnumerable<TItem>>.OnNext(IEnumerable<TItem> value) {
if (_is_user_selected_item) {
var curr_selected_item = selected_item;
_items.sync_with(value);
if (_try_match_item(curr_selected_item, out T match))
if (_try_match_item(curr_selected_item, out TValue match))
selected_item = match;
else {
_is_user_selected_item = false;
@@ -646,18 +663,13 @@ namespace mbts.ui.shell
}
} else {
_items.sync_with(value);
if (_is_user_selected_item) {
_is_user_selected_item = _items.Contains(_selected_item);
}
if (!_is_user_selected_item) {
_try_auto_select();
}
_try_auto_select();
}
}
void IObserver<IEnumerable<T>>.OnError(Exception error) { }
void IObserver<IEnumerable<T>>.OnCompleted() { }
void IObserver<IEnumerable<TItem>>.OnError(Exception error) { }
void IObserver<IEnumerable<TItem>>.OnCompleted() { }
void _run_validation(T val) {
void _run_validation(TValue val) {
var invalid_steps = _validation_steps.Select(v => v.validate(val)).Where(r => !r.valid);
if (invalid_steps.Count() > 0) {
var error_msg = string.Join("\n", invalid_steps.Where(r => !string.IsNullOrEmpty(r.msg)).Select(r => r.msg));
@@ -667,15 +679,15 @@ namespace mbts.ui.shell
void _try_auto_select() {
if (_is_user_selected_item) {
if (desired_item_valid() && _desired_item.force_override && _try_match_item(_desired_item.value, out T match)) {
if (desired_item_valid() && _desired_item.force_override && _try_match_item(_desired_item.value, out TValue match)) {
_set_selected_item(match);
return;
}
} else if (desired_item_valid() && _try_match_item(_desired_item.value, out T match)) {
} else if (desired_item_valid() && _try_match_item(_desired_item.value, out TValue match)) {
_set_selected_item(match);
return;
}
if (_try_match_item(_select_default(), out T default_match))
if (_try_match_item(_select_default(), out TValue default_match))
_set_selected_item(default_match);
else {
// the _set_selected_item has never been entered, so validation has never ran, force it here
@@ -685,8 +697,8 @@ namespace mbts.ui.shell
bool desired_item_valid() => (_desired_item?.has_value).GetValueOrDefault();
}
void _set_selected_item(T value) {
if (!Equals(value, _selected_item)) {
void _set_selected_item(TValue value) {
if (!_value_comparer.Equals(value, _selected_item)) {
Changing?.Invoke(_selected_item, value);
var old = _selected_item;
_selected_item = value;
@@ -696,10 +708,20 @@ namespace mbts.ui.shell
}
}
bool _try_match_item(T item, out T match) =>
_items_match != null ?
_items.TryGetFirst(i => _items_match(i, item), out match) :
_items.TryGetFirst(i => i.Equals(item), out match);
bool _try_match_item(TValue value, out TValue match) {
foreach (TItem item in _items) {
TValue v = _item_to_value(item);
if (_value_comparer.Equals(v, value)) {
match = v;
return true;
}
}
match = default;
return false;
}
bool _try_match_item(TItem item, out TValue match) =>
_try_match_item(_item_to_value(item), out match);
}
public event Action<bool> OnDisposed;
@@ -0,0 +1,47 @@
using bts.utils;
using mbts.ui;
using ProtoBuf;
using System;
using System.Windows.Media;
namespace bts.proto {
[ProtoContract]
struct ColorShowProto : IEquatable<ColorShowProto> {
[ProtoMember(1)]
readonly uint _value;
[ProtoMember(2)]
readonly bool _show;
public ColorShowProto(Color value, bool show = true)
: this(value.rgb_color(), show) { }
public ColorShowProto(uint value, bool show = true) {
_value = value;
_show = show;
}
public Color color => _value.rgb_color();
public bool show => _show;
public Color? result {
get {
if (_show)
return color;
return null;
}
}
public bool Equals(ColorShowProto other) =>
_value == other._value &&
_show == other._show;
public override bool Equals(object obj) =>
obj is ColorShowProto ncp && Equals(ncp);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(_value.GetHashCode(), _show.GetHashCode());
public static bool operator ==(ColorShowProto a, ColorShowProto b) => a.Equals(b);
public static bool operator !=(ColorShowProto a, ColorShowProto b) => !a.Equals(b);
}
}
@@ -15,7 +15,7 @@ namespace mbts.spark {
value = initial_value;
}
event Action<(T oldval, T newval)> _value_set;
protected event Action<(T oldval, T newval)> _value_set;
public T value { get; private set; }
public string text => _text ?? (_text = _format(value));
@@ -24,7 +24,7 @@ namespace mbts.ui.controls {
bool _is_mouse_over_text, _can_execute;
Pen _underline_pen;
static HyperlinkCell() =>
static HyperlinkCell() {
WPFHelper.override_metadata<HorizontalAlignment, HyperlinkCell>(
HorizontalAlignmentProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
@@ -33,6 +33,13 @@ namespace mbts.ui.controls {
o._horizontal_alignment = v;
o._glyph_run = null;
}, HorizontalAlignment.Left);
WPFHelper.override_metadata<double, HyperlinkCell>(
WidthProperty,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => o._glyph_run = null);
}
public HyperlinkCell() {
_background = Background;
@@ -279,24 +286,50 @@ namespace mbts.ui.controls {
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
if (_is_mouse_over_text) {
// draw underline
var pt = new Point(Padding.Left, 0.5 * (ActualHeight + _font_size) + 1);
Point pt;
switch (_horizontal_alignment) {
case HorizontalAlignment.Left:
pt = new Point(Padding.Left, 0.5 * (ActualHeight + _font_size) + 1);
break;
case HorizontalAlignment.Right:
pt = new Point(ActualWidth - Padding.Right - _glyph_width, 0.5 * (ActualHeight + _font_size) + 1);
break;
case HorizontalAlignment.Center:
default:
pt = new Point(0.5 * (ActualWidth - _glyph_width), 0.5 * (ActualHeight + _font_size) + 1);
break;
}
drawingContext.DrawLine(_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
}
drawingContext.Pop();
}
var bgo = GetBackgroundOverlay(this);
if (bgo != null)
drawingContext.DrawRectangle(bgo, null, rect);
if (bg != null) {
var bgo = GetBackgroundOverlay(this);
if (bgo != null)
drawingContext.DrawRectangle(bgo, null, rect);
}
}
bool _point_horizontally_over_text(double x) {
switch (_horizontal_alignment) {
case HorizontalAlignment.Left:
return x >= _padding.Left && x <= _padding.Left + _glyph_width;
case HorizontalAlignment.Right:
return x >= ActualWidth - Padding.Right - _glyph_width && x <= ActualWidth - _padding.Right;
case HorizontalAlignment.Center:
default:
return x >= 0.5 * (ActualWidth - _glyph_width) && x <= 0.5 * (ActualWidth - _glyph_width) + _glyph_width;
}
}
void _check_mouse_over_text(MouseEventArgs e) {
var pt = e.GetPosition(this);
bool over_text =
_glyph_run != null &&
pt.X >= _padding.Left &&
pt.X <= _padding.Left + _glyph_width &&
pt.Y >= _padding.Top &&
pt.Y <= _padding.Top + _font_size;
_glyph_run != null &&
_point_horizontally_over_text(pt.X) &&
pt.Y >= _padding.Top &&
pt.Y <= _padding.Top + _font_size;
if (_is_mouse_over_text != over_text)
_toggle_mouse_over_text();
}
@@ -16,22 +16,40 @@ namespace mbts.ui.controls {
partial class DIYGrid {
public static class DefaultColumnWidths {
public const double
Account = 68,
Exchange = 74,
Account = 80,
Exchange = 86,
FillKey = 68,
OrderKey = 78,
OrderType = 88,
Price = 56,
MViewPrice = 56,
Price = 68,
AveragePrice = 76,
LastPrice = 78,
Settlement = 82,
Security = 80,
SecurityKey = 90,
SecurityType = 96,
Side = 47,
Size = 48,
Symbol = 48,
Time = 100,
ImpliedDirectMergedSize = 68,
Filled = 56,
Symbol = 78,
Time = 112,
TimeInForce = 47,
TradeKey = 76,
User = 53,
Aggressor = 80;
Aggressor = 80,
Volume = 74,
FillKind = 70,
Description = 300,
N_Legs = 68,
N_Working_Orders = 123,
Start_Net_Pos = 100,
Total_Net_Pos = 100,
PnL_Generic = 80,
Working = 72,
LastMessage = 100,
ParentKey = 100;
}
public static class DefaultColumnHeaders {
@@ -45,14 +63,41 @@ namespace mbts.ui.controls {
Price = "Price",
Security = "Security",
SecurityKey = "Security Key",
SecurityType = "Security Type",
Side = "Side",
Size = "Qty",
MergedSize = "Qty (M)",
DirectSize = "Qty (D)",
ImpliedSize = "Qty (I)",
Symbol = "Symbol",
TimeInForce = "TIF",
TradeKey = "Trade Key",
User = "User",
Time = "Time",
Aggressor = "Aggressor";
Aggressor = "Aggressor",
Volume = "Volume",
Last = "Last",
Pos = "Pos",
Working = "Working",
Bid = "Bid",
Ask = "Ask",
Settlement = "Settlement",
DayChange = "Change",
SessionLow = "Low",
SessionHigh = "High",
FillKind = "Fill Kind",
Name = "Name",
Description = "Description",
N_Legs = "# Legs",
N_Working_Orders = "# Working Orders",
StartNetPos = "Start Net Pos",
StartPnL = "Start PnL",
DayLong = "Day Long",
DayShort = "Day Short",
DayPnL = "Day PnL",
TotalNetPos = "Total Net Pos",
TotalPnL = "Total PnL",
PnLPrice = "PnL Price";
}
public interface IKeyedColumn<TColKey> {
@@ -158,7 +203,7 @@ namespace mbts.ui.controls {
public virtual void load_from(GridColumnSettings settings) {
if (settings == null)
return;
using (ReflectionHelper.detect_property_changes(this, raise_prop_changed, nameof(is_visible), nameof(is_collapsed), nameof(font_weight_override), nameof(font_weight),
using (ReflectionHelper.detect_property_changes(this, _raise_prop_changed, nameof(is_visible), nameof(is_collapsed), nameof(font_weight_override), nameof(font_weight),
nameof(font_color_override), nameof(font_color), nameof(font_brush), nameof(font_family), nameof(font_size), nameof(horizontal_alignment), nameof(horizontal_offset))) {
//
id = settings.id;
@@ -176,21 +221,24 @@ namespace mbts.ui.controls {
Width = original_width;
}
//
void raise_prop_changed(string prop) =>
OnPropertyChanged(new PropertyChangedEventArgs(prop));
}
// 'fallback' font-params (aka 'grid-level' font-params)
public void set_fallback_font_params(FontFamily font_family, double font_size, Color font_color, FontWeight font_weight) {
_fallback_font_family = font_family;
_fallback_font_size = font_size;
_fallback_font_color = font_color;
_fallback_font_weight = font_weight;
using (ReflectionHelper.detect_property_changes(this, _raise_prop_changed, nameof(font_family), nameof(font_size), nameof(font_color), nameof(font_weight))) {
_fallback_font_family = font_family;
_fallback_font_size = font_size;
_fallback_font_color = font_color;
_fallback_font_weight = font_weight;
}
font_params_changed?.Invoke();
}
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) =>
sort_direction_changed?.Invoke(this, new_value);
void _raise_prop_changed(string prop) =>
OnPropertyChanged(new PropertyChangedEventArgs(prop));
}
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey>, IGridColumn {
@@ -29,9 +29,13 @@ namespace mbts.ui.controls {
background_overlay = selection_fill;
public override FrameworkElement view_factory() {
var hl = new HyperlinkCell { Foreground = font_brush, Background = background, FontSize = font_size, Padding = padding };
var hl = new HyperlinkCell { Foreground = font_brush, Background = background, Padding = padding };
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
hl.SetBinding(HyperlinkCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
hl.SetBinding(HyperlinkCell.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
hl.SetBinding(HyperlinkCell.CommandProperty, _command_binding);
hl.SetBinding(HyperlinkCell.WidthProperty, new Binding(nameof(ActualWidth)) { Source = this });
if (background_overlay != null) {
hl.SetBinding(BackgroundOverlayProperty, new Binding(nameof(ListBoxItem.IsSelected)) {
Converter = new AnonymousValueConverter<bool, Brush>(val => val ? background_overlay : null),
@@ -22,8 +22,10 @@ namespace mbts.ui.controls {
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
public override FrameworkElement view_factory() {
var v = new TextCell { FontFamily = font_family, FontSize = font_size, Foreground = font_brush, HorizontalAlignment = horizontal_alignment, VerticalAlignment = vertical_alignment };
var v = new TextCell { Foreground = font_brush, HorizontalAlignment = horizontal_alignment, VerticalAlignment = vertical_alignment };
v.SetBinding(TextCell.TextProperty, _binding);
v.SetBinding(TextCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
v.SetBinding(TextCell.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
try_set_binding(TextCell.BackgroundProperty, background_binding);
try_set_binding(TextCell.ForegroundProperty, foreground_binding);
try_set_binding(TextCell.FontWeightProperty, font_weight_binding);
@@ -1,21 +1,26 @@
using mbts.spark;
using bts.shell;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui.spark;
using mbts.ui.utility.text;
using System;
using System.Windows.Media;
namespace mbts.ui.controls {
partial class DIYGrid {
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row> {
readonly Func<TimeAndSalesVM.Row, string> _get_text;
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row>, IKeyedColumn<TimeAndSalesView.Columns> {
readonly Func<TimeAndSalesVM.Row, Brush> _get_brush;
public TimeAndSalesColumn(Func<TimeAndSalesVM.Row, string> get_text, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null, GlyphContext glyph_context = null) : base(get_text, filter, filter_template, glyph_context) {
_get_text = get_text;
public TimeAndSalesColumn(TimeAndSalesView.Columns key, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null)
: base(r => r[key], filter, filter_template) {
this.key = key;
// id is used for settings (another case of ProtoBufs vs Generics - strings seemed like the easiest way to provide an identifier for the common GridColumnSettings proto)
id = TimeAndSalesWindowFactory.col_id(key);
_get_brush = get_brush;
}
public TimeAndSalesView.Columns key { get; }
public override void draw(TimeAndSalesVM.Row row, DrawingContext dc, ColumnGeometry position, double row_height) {
CellRender.draw_text(dc
, glyph_context
@@ -20,18 +20,20 @@ namespace mbts.ui.controls {
}
public class RenderTextColumn<TRowVM> : RenderColumnBase<TRowVM>, IRenderColumn<TRowVM>, IFilterableColumn {
readonly Func<TRowVM, string> _get_text;
protected readonly Func<TRowVM, string> _get_text;
public RenderTextColumn(
Func<TRowVM, string> get_text,
IDataGridColumnFilter filter = null,
string filter_template = null,
GlyphContext glyph_context = null)
string filter_template = null)
: base() {
_get_text = get_text;
this.filter = filter;
this.filter_template = filter_template;
this.glyph_context = glyph_context ?? TextUtils.StandardGlyphContext;
font_params_changed += refresh_glyph_context;
refresh_glyph_context();
void refresh_glyph_context() =>
glyph_context = GlyphContext.create(font_family, font_weight);
}
public IDataGridColumnFilter filter { get; }
@@ -39,7 +41,7 @@ namespace mbts.ui.controls {
public Brush background { get; set; }
public VerticalAlignment vertical_alignment { get; set; } = VerticalAlignment.Center;
public Thickness padding { get; set; } = DefaultCellPadding;
public GlyphContext glyph_context { get; set; } = TextUtils.StandardGlyphContext;
public GlyphContext glyph_context { get; private set; }
public override void draw(TRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(
@@ -335,6 +335,8 @@
<ScrollBar Grid.Row="3" Grid.Column="1" Orientation="Horizontal" LargeChange="10" x:Name="_scroll_horiz" />
<!-- todo: All edit controls go in a canvas - edit api is called with an edit type. Hide all controls -->
<!-- This stole hours of my life. The Text binding down there used to be 'value' and I was encountering -->
<!-- another bug, but this thing sent me down a rabbit hole. F you TickUpDown -->
<bts:TickUpDown Grid.RowSpan="3" Grid.ColumnSpan="2" Width="0" Height="0" HorizontalAlignment="Left" VerticalAlignment="Top"
TickUpCommand="{Binding tick_up}" TickDownCommand="{Binding tick_down}"
x:Name="_edit" Visibility="Collapsed">
@@ -92,7 +92,7 @@ namespace mbts.ui.settings {
public GridColumnSettings with(
string display_name = null, bool? is_visible = null, bool? is_collapsed = null, NullableFontWeightProto? font_weight_override = null, NullableColorProto? font_color_override = null,
double? width = _default_width, HorizontalAlignment? horizontal_alignment = _default_horizontal_alignment, double? horizontal_offset = null) =>
double? width = null, HorizontalAlignment? horizontal_alignment = null, double? horizontal_offset = null) =>
//
new GridColumnSettings(_id,
display_name ?? this.display_name,
@@ -4,7 +4,6 @@ using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utilities;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
@@ -28,22 +27,15 @@ namespace mbts.spark {
ImmutableFontWeight
}
partial class GridSettingsVM<TRowData, TPredicateBuilder> {
public interface IColumnVM : IValueFactory<(GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations)>, IDisposable {
partial class GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> {
public interface IColumnVM : ISimpleColumnVM, IValueFactory<(GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations)>, IDisposable {
ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
string id { get; }
string display_name { get; set; }
double width { get; set; }
bool is_visible { get; set; }
bool is_font_color_overridden { get; set; }
ColorPickerPopupVM font_color_override { get; }
bool is_font_weight_overridden { get; set; }
FontWeight font_weight_override { get; set; }
void apply_flags(GridColumnSettingsFeatureFlags flags);
new (GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) create();
new void load_from((GridColumnSettings settings, ILookup<TPredicateBuilder, CellAnimation> animations) args);
}
public class ColumnVM : DisposableViewModelBase, IColumnVM {
public readonly GridSettingsVM<TRowData, TPredicateBuilder> owner;
public readonly GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> owner;
readonly Property<string> _id, _display_name;
readonly Property<double> _width;
@@ -52,7 +44,7 @@ namespace mbts.spark {
readonly Property<FontWeight> _font_weight_override;
GridColumnSettings _settings;
public ColumnVM(GridSettingsVM<TRowData, TPredicateBuilder> owner) {
public ColumnVM(GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> owner) {
this.owner = owner;
_id = backing(nameof(id), string.Empty);
_display_name = backing(nameof(display_name), string.Empty);
@@ -107,6 +99,7 @@ namespace mbts.spark {
_horizontal_alignment.set(_settings.horizontal_alignment);
_is_visible.set(_settings.is_visible);
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
if (is_font_weight_overridden) {
_font_weight_override.set(_settings.font_weight_override.Value);
}
@@ -133,6 +126,12 @@ namespace mbts.spark {
}
public override string ToString() => $"{display_name} ({id})";
GridColumnSettings IValueFactory<GridColumnSettings>.create() =>
create().settings;
void IValueFactory<GridColumnSettings>.load_from(GridColumnSettings value) =>
load_from((value, new Dictionary<TPredicateBuilder, CellAnimation>().ToLookup(kv => kv.Key, kv => kv.Value)));
}
public class PredicateCellAnimationVM : DisposableViewModelBase, IValueFactory<(TPredicateBuilder predicate, IEnumerable<CellAnimation> animations)> {
@@ -1,7 +1,9 @@
using bts.proto;
using bts.utils;
using mbts.ui.controls;
using mbts.ui.converters;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
@@ -10,7 +12,7 @@ using System.Windows.Media;
namespace mbts.ui.settings {
[ProtoContract]
class GridSettings {
class GridSettings : IEquatable<GridSettings> {
const string _default_font_family = "Segoe UI";
const double _default_font_size = 11;
const uint _default_font_color = 0;
@@ -94,6 +96,7 @@ namespace mbts.ui.settings {
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
public ColorOpacityProto selection_highlight => _selection_highlight;
public IReadOnlyList<GridColumnSettings> columns => _columns;
[Obsolete] // TODO: remove sorting_criterion from GridSettings entirely. This should be part of IShellWindow's StartupData, since it is an instance-specific setting, and prob not part of the shared defaults
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion;
public static GridSettings get_default() =>
@@ -110,5 +113,32 @@ namespace mbts.ui.settings {
sorting_criterion ?? _sorting_criterion);
public GridSettings clone() => with();
public bool Equals(GridSettings other) =>
other != null &&
font_family == other.font_family &&
font_size == other.font_size &&
font_weight == other.font_weight &&
font_color == other.font_color &&
selection_highlight == other.selection_highlight &&
EqualityComparer.safe_sequence_equals(columns, other.columns) &&
#pragma warning disable CS0612 // Type or member is obsolete
EqualityComparer.safe_sequence_equals(sorting_criterion, other.sorting_criterion);
#pragma warning restore CS0612 // Type or member is obsolete
public override bool Equals(object obj) =>
Equals(obj as GridSettings);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
font_family.GetHashCode(),
font_size.GetHashCode(),
font_weight.GetHashCode(),
font_color.GetHashCode(),
selection_highlight.GetHashCode(),
EqualityComparer.safe_aggregate_hash(columns),
#pragma warning disable CS0612 // Type or member is obsolete
EqualityComparer.safe_aggregate_hash(sorting_criterion));
#pragma warning restore CS0612 // Type or member is obsolete
}
}
@@ -1,4 +1,5 @@
using bts.api;
using bts;
using bts.api;
using bts.shell;
using mbts.collections;
using mbts.spark;
@@ -17,10 +18,10 @@ using System.Windows.Media;
using static mbts.ui.controls.DIYGrid;
namespace mbts.ui {
class GridSettingsManager<TRow, TRowData, TPredicateBuilderArg, TPredicateBuilder> :
ViewModelBase, IValueFactory<(GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data)>, IDisposable
class GridSettingsManager<TRow, TRowData, TPredicateBuilderArg, TPredicateBuilder, TSettingsData> : ViewModelBase, IValueFactory<TSettingsData>, IObserver<TSettingsData>, IDisposable
where TRow : IRowVM<TRowData>
where TPredicateBuilder : IPredicateBuilder<TRowData, TPredicateBuilderArg>, new() {
where TPredicateBuilder : IPredicateBuilder<TRowData, TPredicateBuilderArg>, new()
where TSettingsData : class, IGridSettingsWithNotifications<TRowData, TPredicateBuilder> {
//
public readonly ObservableCollection<string> live_sorting_props;
@@ -39,12 +40,18 @@ namespace mbts.ui {
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight;
readonly SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _animation_defs = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
readonly TPredicateBuilderArg _arg;
readonly Func<IPropertyWrapperVM<TPredicateBuilder>> _create_predicate_vm;
readonly Func<string, IPropertyWrapperVM<TPredicateBuilder>> _create_column_predicate_vm;
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> _create_settings_obj;
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> _predicate_animations_data;
public GridSettingsManager(
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel orders_model, ObservableCollection<TRow> rows,
ICollectionView rows_view, ObservableCollection<ColumnBase> columns, Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm,
Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm, TPredicateBuilderArg arg, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> create_settings_obj,
TPredicateBuilderArg arg, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
//
_bus = bus;
_notification_service = notification_service;
@@ -52,7 +59,11 @@ namespace mbts.ui {
_rows = rows;
_rows_view = rows_view;
_columns = columns;
_create_predicate_vm = create_predicate_vm;
_create_column_predicate_vm = create_column_predicate_vm;
_create_settings_obj = create_settings_obj;
_arg = arg;
_column_flags = column_flags.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
@@ -72,10 +83,10 @@ namespace mbts.ui {
// return pa;
//}
Window create_settings_view() {
var vm = new GridSettingsVM<TRowData, TPredicateBuilder>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm);
var vm = new GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm, _create_settings_obj);
// need to refresh before launching the settings-view bc users can chanve some grid-column-settings properties directly from the grid (e.g. width, is_collapsed, etc.)
_grid_settings.set(grid_settings.with(columns.Select(c => c.create())));
vm.load_from((grid_settings, _predicate_animations_data));
vm.load(_create_settings_obj(grid_settings, _predicate_animations_data));
if (column_flags != null) {
foreach (var col in vm.columns) {
if (column_flags.TryGetValue(col.id, out GridColumnSettingsFeatureFlags f))
@@ -84,10 +95,10 @@ namespace mbts.ui {
}
var window = new GridSettingsDialog { DataContext = vm };
vm.committed += () => {
var (grid, predicate_animations_data) = vm.create();
TSettingsData sd = vm.get_result();
// IMPORTANT: don't overwrite current sorting
grid = grid.with(sorting_criterion: rows_view.get_sorting_criteria(columns));
load_from((grid, predicate_animations_data));
sd = _create_settings_obj(sd.general.with(sorting_criterion: rows_view.get_sorting_criteria(columns)), sd.predicate_animations);
load_from(sd);
};
vm.RequestClose += (s, e) => WindowUtilities.force_close(window);
return window;
@@ -107,20 +118,37 @@ namespace mbts.ui {
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public IDialogCommand launch_settings_editor { get; }
public (GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) create() {
// IMPORTANT: don't overwrite current sorting
_grid_settings.set(grid_settings.with(_columns.Select(c => c.create()), _rows_view.get_sorting_criteria(_columns)));
return (grid_settings, _predicate_animations_data);
public GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> create_vm(GridSettings settings, IObservable<string> title) {
var vm = new GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData>(title, _bus, _orders_model, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
vm.load(_create_settings_obj(settings, _predicate_animations_data));
return vm;
}
public void load_from((GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) value) {
public TSettingsData create() {
// IMPORTANT: don't overwrite current sorting
_grid_settings.set(grid_settings.with(_columns.Select(c => {
var col_settings = c.create();
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
col_settings = store.apply(col_settings);
return col_settings;
}), _rows_view.get_sorting_criteria(_columns)));
return _create_settings_obj(grid_settings, _predicate_animations_data);
}
public void load_from(TSettingsData value) => _load_from_core(value, true);
void _load_from_core(TSettingsData value, bool reset_immutables) {
Disposable.dispose(_notification_subs);
var grid = value.grid ?? GridSettings.Default;
var grid = value.general ?? GridSettings.Default;
_grid_settings.set(grid);
foreach (ColumnBase col in _columns)
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight);
_rows_view.set_sort_descriptions(grid.sorting_criterion, _columns);
_predicate_animations_data = value.predicate_animations_data;
if (reset_immutables) {
#pragma warning disable CS0612 // Type or member is obsolete
_rows_view.set_sort_descriptions(grid.sorting_criterion, _columns);
#pragma warning restore CS0612 // Type or member is obsolete
}
_predicate_animations_data = value.predicate_animations;
_animation_defs.clear();
foreach (var pa in _predicate_animations_data) {
var predicate = pa.predicate_builder.create_predicate(_arg);
@@ -140,6 +168,12 @@ namespace mbts.ui {
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
// don't add insult to injury by crashing **
if (grid_settings.columns != null && _columns != null) {
if (reset_immutables) {
foreach (var kv in _column_flags) {
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
kv.Value.store(cs);
}
}
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => id_key(cs.id));
var columns_id_map = _columns.ToDictionary(c => id_key(c.id));
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count) {
@@ -159,5 +193,26 @@ namespace mbts.ui {
IGridColumn<TRow> lookup_column(string col_id) =>
_columns.OfType<IGridColumn<TRow>>().FirstOrDefault(c => string.Equals(col_id, c.id, StringComparison.CurrentCultureIgnoreCase));
}
void IObserver<TSettingsData>.OnNext(TSettingsData sd) {
if (sd == null) return;
try {
var grid = sd.general;
if (grid.columns != null) {
var cols_with_applied_flags = grid.columns.ToList();
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
}
}
sd = _create_settings_obj(grid.with(cols_with_applied_flags), sd.predicate_animations);
}
_load_from_core(sd, false);
} catch (Exception ex) {
Log.Ui.error($"Error loading GridSettings (as observer): {ex}");
}
}
void IObserver<TSettingsData>.OnError(Exception error) { }
void IObserver<TSettingsData>.OnCompleted() { }
}
}
@@ -1,5 +1,6 @@
using bts.api;
using bts.shell;
using bts.utils;
using mbts.collections;
using mbts.collections.extensions;
using mbts.ui;
@@ -18,14 +19,38 @@ using CellAnimation = mbts.ui.CellAnimation;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.spark {
interface IGridSettingsFactory<TRowData, TPredicateBuilder> : IValueFactory<(GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data)>
where TPredicateBuilder : IPredicateBuilder<TRowData>, new() { }
interface IGridSettingsWithNotifications<TRowData, TPredicateBuilder> where TPredicateBuilder : IPredicateBuilder<TRowData> {
GridSettings general { get; }
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations { get; }
}
partial class GridSettingsVM<TRowData, TPredicateBuilder> : DisposableViewModelBase, IGridSettingsFactory<TRowData, TPredicateBuilder>, IRequestViewClose
where TPredicateBuilder : IPredicateBuilder<TRowData>, new() {
class GridSettingsWithNotifications<TRowData, TPredicateBuilder> : IEquatable<GridSettingsWithNotifications<TRowData, TPredicateBuilder>> where TPredicateBuilder : IPredicateBuilder<TRowData> {
public readonly GridSettings grid;
public readonly IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data;
public GridSettingsWithNotifications(GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) {
this.grid = grid;
this.predicate_animations_data = predicate_animations_data;
}
public bool Equals(GridSettingsWithNotifications<TRowData, TPredicateBuilder> other) =>
other != null &&
EqualityComparer.null_check_equals(grid, other.grid) &&
EqualityComparer.safe_sequence_equals(predicate_animations_data, other.predicate_animations_data);
public override bool Equals(object obj) => Equals(obj as GridSettingsWithNotifications<TRowData, TPredicateBuilder>);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
EqualityComparer.safe_get_hash_code(grid),
EqualityComparer.safe_aggregate_hash(predicate_animations_data));
}
partial class GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData> : DisposableViewModelBase, IRequestViewClose, IWindowSettingsVM<TSettingsData>, ITemplateSelectorKey
where TPredicateBuilder : IPredicateBuilder<TRowData>, new()
where TSettingsData :class, IGridSettingsWithNotifications<TRowData, TPredicateBuilder> {
//
protected readonly Func<IPropertyWrapperVM<TPredicateBuilder>> _create_predicate_vm;
protected readonly Func<string, IPropertyWrapperVM<TPredicateBuilder>> _create_column_predicate_vm;
readonly Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> _create_settings_obj;
readonly Property<IColumnVM> _current_column;
readonly Property<FontFamily> _font_family;
readonly Property<FontWeight> _font_weight;
@@ -33,16 +58,22 @@ namespace mbts.spark {
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
readonly OrdersModel _orders_model;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
public GridSettingsVM(
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm, Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm) {
Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm,
Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
//
_owner_title = backing(nameof(owner_title), owner_title);
this.bus = bus;
_orders_model = orders_model;
_create_predicate_vm = create_predicate_vm;
_create_column_predicate_vm = create_column_predicate_vm;
_create_settings_obj = create_settings_obj;
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
_font_size = backing(nameof(font_size), FontSizes.Small);
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
@@ -68,6 +99,7 @@ namespace mbts.spark {
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
public bool enable_sound_triggers => true;
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
public bool show_cell_predicate_animations { get => _show_cell_predicate_animations.Value; set => _show_cell_predicate_animations.set(value); }
public ObservableEditableCollection<PredicateRowAnimationVM> predicate_animations { get; }
@@ -80,7 +112,9 @@ namespace mbts.spark {
new DialogSkirtItem("Apply", new DelegateCommand(() => committed?.Invoke()))
};
public (GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) create() {
public string key => ui.spark.GridOrLadderSettingsSelectorKeys.SimpleGrid;
public TSettingsData get_result() {
var animation_map = predicate_animations
.Select(p => p.create())
.GroupBy(d => d.predicate)
@@ -94,22 +128,22 @@ namespace mbts.spark {
foreach (IGrouping<TPredicateBuilder, CellAnimation> g in animations)
animation_map.AddOrUpdate(g.Key, p => animation_tuple(c: g), (p, t) => t.cell_animations.AddRange(g));
}
return (new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings),
return _create_settings_obj(new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), col_settings),
animation_map.Select(kv => PredicateAnimations.create_data<TRowData, TPredicateBuilder>(kv.Key, kv.Value.cell_animations, kv.Value.row_animations, kv.Value.sounds)));
//
(List<RowAnimation> row_animations, List<SoundNotification> sounds, List<CellAnimation> cell_animations) animation_tuple(IEnumerable<RowAnimation> r = null, IEnumerable<SoundNotification> s = null, IGrouping<TPredicateBuilder, CellAnimation> c = null) =>
(r?.ToList() ?? new List<RowAnimation>(), s?.ToList() ?? new List<SoundNotification>(), c?.ToList() ?? new List<CellAnimation>());
}
public void load_from((GridSettings grid, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> predicate_animations_data) value) {
_font_family.set(new FontFamily(value.grid.font_family));
_font_weight.set(value.grid.font_weight);
font_color.load_from(value.grid.font_color.rgb_solid_brush());
selection_highlight.load_from(value.grid.selection_highlight.to_brush());
_font_size.set(value.grid.font_size);
public void load(TSettingsData sd) {
_font_family.set(new FontFamily(sd.general.font_family));
_font_weight.set(sd.general.font_weight);
font_color.load_from(sd.general.font_color.rgb_solid_brush());
selection_highlight.load_from(sd.general.selection_highlight.to_brush());
_font_size.set(sd.general.font_size);
var col_animations = new Dictionary<string, Dictionary<TPredicateBuilder, List<CellAnimation>>>();
predicate_animations.Clear();
foreach (var pa in value.predicate_animations_data) {
foreach (var pa in sd.predicate_animations) {
if (pa.row_animations.Count > 0 || pa.sound_notifications.Count > 0) {
var vm = new PredicateRowAnimationVM(_create_predicate_vm());
vm.load_from((pa.predicate_builder, pa.row_animations, pa.sound_notifications));
@@ -122,9 +156,14 @@ namespace mbts.spark {
}
}
var curr_col_id = current_column?.id;
columns.SetItems(value.grid.columns.Select(col => {
columns.SetItems(sd.general.columns.Select(col => {
var vm = new ColumnVM(this);
vm.load_from((col, col_animations.GetValueOrDefault(col.id)?.ToFlatLookup(kv => kv.Key, kv => kv.Value)));
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out IColumnVM curr_column))
@@ -342,9 +342,8 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSplit}}"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--TODO: enable when FontFamilies are ready-->
<TextBlock Foreground="{u:BTSColor disabled_text}">Font</TextBlock>
<Grid Grid.Column="2" IsEnabled="False" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}">
<TextBlock>Font</TextBlock>
<Grid Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}">
<c:PopupControl HorizontalAlignment="Stretch" Header="{Binding}" HeaderTemplate="{StaticResource FontPickerPopupHeaderTemplate}" Background="{StaticResource ComboBox.Static.Background}">
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
</c:PopupControl>
@@ -359,6 +358,9 @@
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource {x:Type GroupBox}}">
<Style.Triggers>
<DataTrigger Binding="{Binding enable_sound_triggers}" Value="{u:False}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding show_predicate_animations}" Value="{u:False}">
<Setter Property="Height" Value="{StaticResource CollapsedAnimationItemsHeight}"/>
<Setter Property="BorderThickness" Value="0"/>
@@ -16,6 +16,8 @@ namespace mbts.ui.spark {
public static IMultiValueConverter ColumnDisplayNameVisibilityConverter =>
ValueConverter.create<string, string, Visibility>((id, header) => id == header ? Visibility.Collapsed : Visibility.Visible);
public bool EnableSoundTriggers { get; set; }
}
static class PredicateTemplateKeys {
@@ -0,0 +1,72 @@
using bts.proto;
using mbts.spark;
using mbts.ui.settings;
using System.Collections.Generic;
namespace mbts.ui {
abstract class ImmutableColumnSettingStore {
public readonly GridColumnSettingsFeatureFlags flags;
protected ImmutableColumnSettingStore(GridColumnSettingsFeatureFlags flags) =>
this.flags = flags;
public static ImmutableColumnSettingStore create(GridColumnSettingsFeatureFlags flags) {
var stores_per_flag = new List<ImmutableColumnSettingStore>();
if ((flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) != 0) {
stores_per_flag.Add(new ImmutableFontColorOverrideStore());
}
if ((flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) != 0) {
stores_per_flag.Add(new ImmutableFontWeightOverrideStore());
}
return new CompositeImmutableSettingsStore(stores_per_flag, flags);
}
public abstract GridColumnSettings apply(GridColumnSettings settings);
public abstract void store(GridColumnSettings settings);
class ImmutableFontColorOverrideStore : ImmutableColumnSettingStore {
NullableColorProto _value;
public ImmutableFontColorOverrideStore()
: base(GridColumnSettingsFeatureFlags.ImmutableFontColor) { }
public override GridColumnSettings apply(GridColumnSettings settings) =>
settings.with(font_color_override: _value);
public override void store(GridColumnSettings settings) =>
_value = new NullableColorProto(settings.font_color_override);
}
class ImmutableFontWeightOverrideStore : ImmutableColumnSettingStore {
NullableFontWeightProto _value;
public ImmutableFontWeightOverrideStore()
: base(GridColumnSettingsFeatureFlags.ImmutableFontWeight) { }
public override GridColumnSettings apply(GridColumnSettings settings) =>
settings.with(font_weight_override: _value);
public override void store(GridColumnSettings settings) =>
_value = new NullableFontWeightProto(settings.font_weight_override);
}
class CompositeImmutableSettingsStore : ImmutableColumnSettingStore {
readonly IReadOnlyList<ImmutableColumnSettingStore> _stores;
public CompositeImmutableSettingsStore(IReadOnlyList<ImmutableColumnSettingStore> stores, GridColumnSettingsFeatureFlags flags)
: base(flags) => _stores = stores;
public override GridColumnSettings apply(GridColumnSettings settings) {
foreach (var s in _stores) {
settings = s.apply(settings);
}
return settings;
}
public override void store(GridColumnSettings settings) {
foreach (var s in _stores) {
s.store(settings);
}
}
}
}
}
@@ -0,0 +1,92 @@
using bts.proto;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
namespace mbts.spark {
partial class SimpleGridSettingsVM {
public class SimpleColumnVM : ViewModelBase, ISimpleColumnVM {
public readonly SimpleGridSettingsVM owner;
readonly Property<string> _id, _display_name;
readonly Property<double> _width;
readonly Property<HorizontalAlignment> _horizontal_alignment;
readonly Property<bool> _is_visible, _can_override_font_color, _is_font_color_overridden, _can_override_font_weight, _is_font_weight_overridden;
readonly Property<FontWeight> _font_weight_override;
GridColumnSettings _settings;
public SimpleColumnVM(SimpleGridSettingsVM owner) {
this.owner = owner;
_id = backing(nameof(id), string.Empty);
_display_name = backing(nameof(display_name), string.Empty);
_width = backing(nameof(width), 70.0);
_horizontal_alignment = backing(nameof(horizontal_alignment), HorizontalAlignment.Left);
_is_visible = backing(nameof(is_visible), false);
// font color override
_can_override_font_color = backing(nameof(can_override_font_color), true);
_is_font_color_overridden = backing(nameof(is_font_color_overridden), false);
// font weight override
_can_override_font_weight = backing(nameof(can_override_font_weight), true);
_is_font_weight_overridden = backing(nameof(is_font_weight_overridden), false);
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
}
public string id => _id.Value;
public string display_name { get => _display_name.Value; set => _display_name.set(value); }
public double width { get => _width.Value; set => _width.set(value); }
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment.Value; set => _horizontal_alignment.set(value); }
public bool is_visible { get => _is_visible.Value; set => _is_visible.set(value); }
// font color override
public bool can_override_font_color => _can_override_font_color.Value;
public bool is_font_color_overridden { get => _is_font_color_overridden.Value; set => _is_font_color_overridden.set(value); }
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
// font weight override
public bool can_override_font_weight => _can_override_font_weight.Value;
public bool is_font_weight_overridden { get => _is_font_weight_overridden.Value; set => _is_font_weight_overridden.set(value); }
public FontWeight font_weight_override { get => _font_weight_override.Value; set => _font_weight_override.set(value); }
public GridColumnSettings create() =>
_settings.with(display_name, is_visible, null,
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : (NullableFontWeightProto?)null,
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : (NullableColorProto?)null,
width,
horizontal_alignment);
public void load_from(GridColumnSettings value) {
_settings = value;
_id.set(_settings.id);
_display_name.set(_settings.display_name);
_width.set(_settings.width);
_horizontal_alignment.set(_settings.horizontal_alignment);
_is_visible.set(_settings.is_visible);
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
if (is_font_weight_overridden) {
_font_weight_override.set(_settings.font_weight_override.Value);
}
if (is_font_color_overridden) {
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
}
}
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
var split_flags = EnumHelper.split_flags(flags).ToList();
// font color
_can_override_font_color.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontColor));
if (!can_override_font_color)
_is_font_color_overridden.set(false);
// font weight
_can_override_font_weight.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontWeight));
if (!can_override_font_weight)
_is_font_weight_overridden.set(false);
}
public override string ToString() => $"{display_name} ({id})";
}
}
}
@@ -0,0 +1,113 @@
<Window
x:Class="mbts.ui.spark.SimpleGridSettingsDialog"
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:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
SizeToContent="WidthAndHeight"
Style="{StaticResource BTSDialog}"
>
<Window.Resources>
<cv:BooleanToVisibilityConverter x:Key="DefaultTabVisibilityConverter" VisibilityForTrue="Visible" VisibilityForFalse="Hidden"/>
<cv:BooleanToVisibilityConverter x:Key="CustomTabVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
<cv:ValueMap x:Key="TabHeaderFontWeightConverter" Default="{u:FontWeight Normal}">
<cv:KeyValue Key="{u:True}" Value="{u:FontWeight Bold}"/>
</cv:ValueMap>
<Style TargetType="RadioButton">
<Setter Property="BorderBrush" Value="{StaticResource ComboBox.Static.Border}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
<Setter Property="Padding" Value="6,3"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
>
<ContentPresenter/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="6,4,6,6"/>
<Setter Property="BorderThickness" Value="1,1,1,0"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="{u:BTSColor UI.Button.mouseover_background}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{u:BTSColor UI.Button.disabled_foreground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<u:KeyedStyleSelector x:Key="LeftButtonStyleSelector">
<u:StyleSelectorKeyValue Key="{x:Static usp:SimpleGridSettingsDialog.ResetToFactoryKey}">
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
<Setter Property="Width" Value="94"/>
</Style>
</u:StyleSelectorKeyValue>
<u:StyleSelectorKeyValue Key="{x:Static usp:SimpleGridSettingsDialog.CopyFromOtherKey}">
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
<Setter Property="Width" Value="108"/>
</Style>
</u:StyleSelectorKeyValue>
</u:KeyedStyleSelector>
<u:KeyedTemplateSelector x:Key="GridOrLadderSettingsSelector">
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridOrLadderSettingsSelectorKeys.SimpleGrid}">
<DataTemplate>
<usp:GridSettingsView />
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridOrLadderSettingsSelectorKeys.Ladder}">
<DataTemplate>
<usp:LadderSettingsView />
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<c:ActionMenu HorizontalAlignment="Right" ItemsSource="{Binding action_menu_items}"/>
<ContentControl Grid.Row="2"
Margin="8"
VerticalAlignment="Top"
Content="{Binding instance_vm}"
ContentTemplateSelector="{StaticResource GridOrLadderSettingsSelector}"
/>
<c:DialogSkirt
Grid.Row="3"
BorderThickness="1"
BorderBrush="DarkGray"
LeftItems="{Binding left_skirt_items}"
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
RightItems="{Binding right_skirt_items}"
>
<c:DialogSkirt.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Transparent"/>
<GradientStop Color="{x:Static u:BTSColors.dialog_skirt}" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</c:DialogSkirt.Background>
</c:DialogSkirt>
</Grid>
</Window>
@@ -0,0 +1,19 @@
using System.Windows;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for SimpleGridSettingsDialog.xaml
/// </summary>
public partial class SimpleGridSettingsDialog : Window {
public SimpleGridSettingsDialog() =>
InitializeComponent();
public const string ResetToFactoryKey = "Reset", CopyFromOtherKey = "Copy";
}
static class GridOrLadderSettingsSelectorKeys {
public const string
SimpleGrid = nameof(SimpleGrid),
Ladder = nameof(Ladder);
}
}
@@ -0,0 +1,121 @@
using mbts.spark;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using static mbts.ui.controls.DIYGrid;
namespace mbts.ui {
class SimpleGridSettingsManager<TRow> : ViewModelBase, IObserver<GridSettings> {
readonly ObservableCollection<ColumnBase> _columns;
readonly IReadOnlyCollection<IRowVM> _row_vms;
readonly Property<GridSettings> _grid_settings;
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<double> _font_size;
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight;
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
public SimpleGridSettingsManager(ObservableCollection<ColumnBase> columns)
: this(columns, new Dictionary<string, GridColumnSettingsFeatureFlags>(), null) { }
public SimpleGridSettingsManager(ObservableCollection<ColumnBase> columns, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags, IReadOnlyCollection<IRowVM> row_vms) {
_columns = columns;
_column_flags = column_flags.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_row_vms = row_vms;
}
public GridSettings grid_settings => _grid_settings.Value;
public FontFamily font_family => _font_family.Value;
public FontWeight font_weight => _font_weight.Value;
public double font_size => _font_size.Value;
public SolidColorBrush font_color => _font_color.Value;
public SolidColorBrush selection_highlight => _selection_highlight.Value;
public SimpleGridSettingsVM create_vm(GridSettings settings, IObservable<string> title) {
var vm = new SimpleGridSettingsVM(title, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
vm.load_from(settings);
return vm;
}
public GridSettings create() {
// IMPORTANT: don't overwrite current sorting
_grid_settings.set(grid_settings.with(_columns.Select(c => {
var col_settings = c.create();
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
col_settings = store.apply(col_settings);
return col_settings;
})));
return grid_settings;
}
/// <summary>
/// This will overwrite the columns' immutable-value store (unlike the IObserver&lt;<see cref="GridSettings"/>&gt; 'OnNext' implementation)
/// </summary>
public void load_from(GridSettings grid) => _load_from_core(grid, true);
void IObserver<GridSettings>.OnCompleted() { }
void IObserver<GridSettings>.OnError(Exception error) { }
void IObserver<GridSettings>.OnNext(GridSettings grid) {
if (grid.columns != null) {
var cols_with_applied_flags = grid.columns.ToList();
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
}
}
grid = grid.with(cols_with_applied_flags);
}
_load_from_core(grid, false);
}
void _load_from_core(GridSettings grid, bool reset_immutable_flag_stores) {
grid = grid ?? GridSettings.Default;
_grid_settings.set(grid);
foreach (ColumnBase col in _columns)
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight);
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
// don't add insult to injury by crashing **
if (grid_settings.columns != null) {
if (reset_immutable_flag_stores) {
foreach (var kv in _column_flags) {
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
kv.Value.store(cs);
}
}
if (_columns != null) {
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => id_key(cs.id));
var columns_id_map = _columns.ToDictionary(c => id_key(c.id));
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count) {
CollectionHelper.sync_with_never_duplicate(_columns, grid_settings.columns, cs => {
throw new InvalidOperationException();
}, c => id_key(c.id), cs => id_key(cs.id));
}
// ** don't assume that columns:column_settings have a perfect 1:1 relationship (they should, but it is possible we accidentally break the settings as grids/uis change) **
foreach (var col in _columns) {
if (col_settings_id_map.TryGetValue(id_key(col.id), out GridColumnSettings cs))
col.load_from(cs);
}
}
// invalidate all the rows to draw them again to reflect the latest settings
if (_row_vms != null)
foreach (var r in _row_vms) r.raise_invalidated();
//
string id_key(string id) => id.ToLower();
}
}
}
}
@@ -0,0 +1,102 @@
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.utility.text;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.spark {
interface ISimpleColumnVM : IValueFactory<GridColumnSettings> {
string id { get; }
string display_name { get; set; }
double width { get; set; }
bool is_visible { get; set; }
bool is_font_color_overridden { get; set; }
ColorPickerPopupVM font_color_override { get; }
bool is_font_weight_overridden { get; set; }
FontWeight font_weight_override { get; set; }
void apply_flags(GridColumnSettingsFeatureFlags flags);
}
partial class SimpleGridSettingsVM : DisposableViewModelBase, IValueFactory<GridSettings>, IWindowSettingsVM<GridSettings>, ITemplateSelectorKey {
readonly Property<ISimpleColumnVM> _current_column;
readonly Property<FontFamily> _font_family;
readonly Property<FontWeight> _font_weight;
readonly Property<string> _font_display_name, _owner_title;
readonly Property<double> _font_size;
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
public SimpleGridSettingsVM(IObservable<string> owner_title, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
_column_flags = column_flags ?? new Dictionary<string, GridColumnSettingsFeatureFlags>();
_owner_title = backing(nameof(owner_title), owner_title);
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
_font_size = backing(nameof(font_size), FontSizes.Small);
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), nameof(font_family), nameof(font_size));
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
_current_column = backing<ISimpleColumnVM>(nameof(current_column));
}
public event EventHandler RequestClose;
public event Action committed;
public string owner_title => _owner_title.Value;
public ObservableCollection<ISimpleColumnVM> columns { get; } = new ObservableCollection<ISimpleColumnVM>();
public ISimpleColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
public string font_display_name => _font_display_name.Value;
public FontFamily font_family { get => _font_family.Value; set => _font_family.set(value); }
public FontWeight font_weight { get => _font_weight.Value; set => _font_weight.set(value); }
public ColorPickerPopupVM font_color { get; } = new ColorPickerPopupVM();
public ColorPickerPopupVM selection_highlight { get; } = new ColorPickerPopupVM();
public double font_size { get => _font_size.Value; set => _font_size.set(value); }
public bool enable_sound_triggers => false;
public bool show_predicate_animations { get => _show_predicate_animations.Value; set => _show_predicate_animations.set(value); }
public bool show_cell_predicate_animations { get => _show_cell_predicate_animations.Value; set => _show_cell_predicate_animations.set(value); }
public IReadOnlyList<DialogSkirtItem> dialog_skirt_items => new[] {
new DialogSkirtItem("OK", new DelegateCommand(() => {
committed?.Invoke();
RequestClose?.Invoke(this, new EventArgs());
})) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()))) { is_cancel = true },
new DialogSkirtItem("Apply", new DelegateCommand(() => committed?.Invoke()))
};
public string key => ui.spark.GridOrLadderSettingsSelectorKeys.SimpleGrid;
public GridSettings create() =>
new GridSettings(font_family.name(), font_size, font_weight, font_color.color.rgb_color(), selection_highlight.to_proto(), columns.Select(c => c.create()));
public void load_from(GridSettings value) {
_font_family.set(new FontFamily(value.font_family));
_font_weight.set(value.font_weight);
font_color.load_from(value.font_color.rgb_solid_brush());
selection_highlight.load_from(value.selection_highlight.to_brush());
_font_size.set(value.font_size);
var curr_col_id = current_column?.id;
columns.SetItems(value.columns.Select(col => {
var vm = new SimpleColumnVM(this);
vm.load_from(col);
// reapply our favorite column flags
// this comes in handy when performing Load From Factory - the editor will contain
// a consistent view of what can be overridden and what cannot
if (_column_flags.TryGetValue(vm.id, out GridColumnSettingsFeatureFlags flags))
vm.apply_flags(flags);
return vm;
}));
if (curr_col_id == null || !columns.TryGetFirst(c => c.id == curr_col_id, out ISimpleColumnVM curr_column))
curr_column = columns.FirstOrDefault();
_current_column.set(curr_column);
}
void IWindowSettingsVM<GridSettings>.load(GridSettings value) => load_from(value);
GridSettings IWindowSettingsVM<GridSettings>.get_result() => create();
}
}
@@ -47,7 +47,7 @@
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem OrderType="{Binding order_type}" Metadata="{Binding meta}"/>
<usp:OrderTypeItem OrderType="{Binding order_type}" DisplayName="{Binding display_name}" Metadata="{Binding meta}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
@@ -33,7 +33,7 @@
Width="15"
Height="10"
/>
<TextBlock
<TextBlock x:Name="text"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="20,0,0,0"
Loaded 100 of 261 files, more files were not shown because too many files have changed in this diff. Show more