Compare commits

..
Author SHA1 Message Date
Pedro Pinto e998edc20f Merged from master, note that SpreaderOrderVM.cs was not merged yet, so this client build will fail 2019-03-15 14:41:02 -04:00
Pedro Pinto 1c1be2b334 Idempotent cancels for Join,Iceberg and Native orders. 2019-03-15 11:20:47 -04:00
John Colvin 5d89257643 OrderBook - make attach-order panel pop out less 2019-03-15 00:57:09 -05:00
John Colvin 897e134714 C# style cop changes 2019-03-14 21:15:29 -05:00
John Colvin e6cb97028b OrderBook - subscribe_common_sec_md notifies observer on subscribe 2019-03-14 21:13:31 -05:00
John Colvin 6c0dc80d43 OrderBook - sync attached-order-params with common values from selected orders 2019-03-14 21:13:31 -05:00
John Colvin ce3a4562d9 OrderBook - attach/detach logging 2019-03-14 21:13:31 -05:00
Pedro Pinto 217f0cd4cc Merged 2019-03-14 21:18:35 -04:00
Pedro Pinto 1e649ccce6 Made NativeOrder able to process full updates. 2019-03-14 21:17:15 -04:00
John Colvin cbc69ec5fd OrderBook - don't lose right-panel-width (when hidden) during attach-op 2019-03-14 20:12:22 -05:00
John Colvin 0d1eedc34a OrderBook - wrap 'send_attached_orders_async' in try/catch 2019-03-14 20:12:22 -05:00
John Colvin 4e85bf4520 OrderBook - refactor 'property-update-throttle' code to use CommonValueMonitor 2019-03-14 20:12:22 -05:00
Pedro Pinto 5d0023a80b Fixed a bug in detach handling in Join. 2019-03-14 21:09:16 -04:00
Pedro Pinto e300de2425 Fixed a few bugs in new Join 2019-03-14 20:13:18 -04:00
Pedro Pinto 569877752e Merged 2019-03-14 19:08:48 -04:00
Pedro Pinto 4846cf7693 Join with attach/detach and full update is code complete. 2019-03-14 19:04:51 -04:00
John Colvin 16a46955f4 OrderBook - attach buttons become disabled when selected order transitions from working -> non-working 2019-03-14 16:43:16 -05:00
John Colvin 13a6d68b60 OrderBook - change 'Modify Order' keyboard shortcut Ctrl+Alt+M -> Ctrl+Shift+M 2019-03-14 13:05:15 -05:00
John Colvin 1affd992de OrderBook - reduce attach-ui panel width 2019-03-14 12:39:27 -05:00
John Colvin d951258891 OrderBook - bottom of attach-ui was getting chopped off 2019-03-14 12:32:40 -05:00
John Colvin f5dfb0264b OrderBook - change default fills slave grid height 400 -> 138 2019-03-14 11:19:36 -05:00
John Colvin 3b8f5c5093 OrderBook - minor style changes to right-panel 2019-03-14 00:04:34 -05:00
John Colvin 253d213818 OrderBook - unify attach/modify order param controls/vms 2019-03-13 21:41:49 -05:00
John Colvin f89cb3d61e OrderBook - prevent 'Modify Algos' button from hiding below visible bounds 2019-03-13 17:29:35 -05:00
John Colvin eb6573399b OrderBook - add 'Cancel' button to attach-orders-panel 2019-03-13 15:10:05 -05:00
John Colvin 8ba8ce76ce CancelButton Style - add 'Cancel (Esc)' tooltip 2019-03-13 15:07:33 -05:00
John Colvin e65b8e9d4d OrderBook - disable 'send attached orders' button when selection is empty or has non-working orders 2019-03-13 14:27:33 -05:00
John Colvin 2fc379fbef OrderBookView - ensure fills slave grid min height in xaml (64) 2019-03-13 13:52:10 -05:00
John Colvin fb96a75a8e OrderBook - change attach-menu-item text from 'Attach Selected Orders' -> 'Attach To Selected Orders' 2019-03-13 13:46:03 -05:00
John Colvin 240d15af47 OrderBookView - remove attach-instructions text xaml 2019-03-13 13:46:03 -05:00
John Colvin 2530e43797 OrderBook - hide attach-instruction text, animate right panel 2019-03-13 13:09:28 -05:00
John Colvin b05d85d6c3 OrderBook - change 'Attach' prompt style 2019-03-13 12:22:04 -05:00
John Colvin 52f2e77310 OrderBook - change Attach/Detach icons 2019-03-13 12:16:57 -05:00
John Colvin bd00c6de3e SPARK-2093 Attach/Detach ui 2019-03-13 11:48:08 -05:00
Jake Norbut 02ed327ec7 clarifying tests 2019-03-12 16:20:46 -05:00
Jake Norbut e9162263d6 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-03-12 16:14:03 -05:00
Jake Norbut 3b3cc9a51c clarifying test 2019-03-12 16:13:51 -05:00
John Colvin 54f03c4a03 Merged in SPARK-2107-sound-triggers (pull request #193)
SPARK-2107 QA: settings/SoundAndNotificationsTest.txt
2019-03-12 17:58:36 +00:00
Pedro Pinto c4d297969b Improved tests. 2019-03-12 13:54:12 -04:00
John Colvin 1f86456437 SPARK-2107 QA: settings/SoundAndNotificationsTest.txt 2019-03-12 12:50:32 -05:00
Pedro Pinto e24e04ca4f Fixed OrderManager reentrancy problem reported by Taha 2019-03-12 10:01:57 -04:00
John Colvin 87aea4812f OrderBook algo editor would stop loading non-spreader algos once a spreader order was selected 2019-03-11 21:27:22 -05:00
John Colvin 21ae3b14c8 OrderBook attach/detach ui - use 'Pen' cursor when attachment is in progress 2019-03-11 20:20:29 -05:00
John Colvin 708ae185f1 rename OrderBookAttachTargetAdorner -> OrderBookAttachmentTargetAdorner (matches file) 2019-03-11 20:10:50 -05:00
John Colvin 5c6dbd7bd1 OrderBook - hook up attach/detach api calls 2019-03-11 20:07:29 -05:00
John Colvin 08a1e07ab6 Merged in SPARK-2098-spreader-orders-missing-security (pull request #192)
RTM SPARK-2098 security is not assigned to SpreaderOrderDefinition when sending orders
2019-03-11 23:57:58 +00:00
John Colvin 038e6d16af SPARK-2098 security is not assigned to SpreaderOrderDefinition when sending orders 2019-03-11 16:59:18 -05:00
Eric Harding fe2e1e9945 Merge branch 'AdditionalTracing-MinimumInterval' of bitbucket.org:bluetradesys/spark 2019-03-11 17:00:30 -04:00
Eric Harding 20d5276874 Remove trace::log_scope function and just use constructor 2019-03-11 16:53:12 -04:00
John Colvin f19a280b7f DIYGridRowHighlighterAdorner derives from new generic Adorner<T> class 2019-03-11 15:39:57 -05:00
Eric Harding b2f3d093d2 Merged in AdditionalTracing-MinimumInterval (pull request #191)
RTM: cut down on tracing load by supplying a minimum interval
2019-03-11 20:28:05 +00:00
John Colvin 4860f706ad Merged in SPARK-2097-change-cover/lean-defaults (pull request #190)
SPARK-2097 change Lean/Cover defaults - Lean.MaxChaseTicks = 2, Cover.HedgeTicks = 1

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-03-11 20:24:06 +00:00
Eric Harding 29ddd6621c Move NativeOrder::on_activate trace message so it doesn't get triggered by warmup 2019-03-11 16:11:21 -04:00
John Colvin 47cf9e2fe6 OrderBook - add 'Attached Target Order Key' column 2019-03-11 14:14:24 -05:00
John Colvin df75518389 OrderBook - add parent-key-regex property to FilterStates proto (guess we accidentally omitted it) 2019-03-11 14:01:20 -05:00
John Colvin 90c371c72f OrderBookColumns - remove 'sort_member_path' (not needed bc orderbook handles sorting events internally) 2019-03-11 13:48:46 -05:00
John Colvin 9609f7e6cf OrderBook FilterStates proto - override obj Equals/GetHashCode 2019-03-11 13:35:26 -05:00
Eric Harding e8e0f01fb7 Merged master into AdditionalTracing-MinimumInterval 2019-03-11 14:16:09 -04:00
Eric Harding 41a04de31b Update threshhold with help from compiler 2019-03-11 14:14:51 -04:00
John Colvin 30c83b7c72 OrderBookGridView - stack 'ColumnKeys' enum fields vertically (easier to read) 2019-03-11 13:02:17 -05:00
John Colvin 382d8b468c ObservableNode - assign child_node.parent when adding child 2019-03-11 12:58:43 -05:00
John Colvin b3b1af68d0 SPARK-2097 change Lean/Cover defaults - Lean.MaxChaseTicks = 2, Cover.HedgeTicks = 1 2019-03-11 12:23:49 -05:00
John Colvin cbe6d07a86 OrderBookStartupData - set default values for algo_editor_width/height/is_visible 2019-03-11 11:03:51 -05:00
John Colvin 6d67700ee6 OrderBookView - reduce 'attach-algo-instructions' text fadeout duration from 1 sec to 0.5 2019-03-11 10:49:12 -05:00
Pedro Pinto 7cbf729607 Cleaned up release notes further. 2019-03-11 08:52:35 -04:00
Pedro Pinto 6e8851d92c Updated subrepo pointers 2019-03-11 08:51:31 -04:00
Pedro Pinto 460e747f55 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-03-11 08:49:24 -04:00
Pedro Pinto 20978b10d4 Updated release notes 2019-03-11 08:49:19 -04:00
John Colvin 42e0cfba70 Merged in SPARK-2079-trigger-cleanup (pull request #183)
RTM - SPARK-2079 trigger cleanup
2019-03-11 12:48:44 +00:00
Michael Harding 728c485013 Merged in doc_temp_build (pull request #174)
RTM: Doc temp build
2019-03-11 12:47:40 +00:00
Eric Harding b83f88e098 Merged in AlmostNeverRecordSim (pull request #185)
RTM: Almost never want to record sim market data
2019-03-11 12:46:55 +00:00
Eric Harding 32b1a38655 Merged in DenyCopy (pull request #177)
RTM: Explicitly deny copying a service.  make_*_publisher points back to the owning service
2019-03-11 12:44:49 +00:00
Taha Afzal 24934f6607 Merged in SPARK-2058-netchange (pull request #184)
RTM SPARK-2058 netchange
2019-03-11 12:43:56 +00:00
Brian Muhr 90b56277ca Merged in Documentation (pull request #179)
RTM: Documentation - added Algo Studio, new images, freshened up quite a bit

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
2019-03-11 12:42:51 +00:00
Eric Harding 20ca8a7ffe Merged in AdditionalTracing (pull request #189)
RTM: Additional Tracing

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-03-11 12:40:57 +00:00
Pedro Pinto 6ed4adbc14 Merged in fix_warmup_limit_polution (pull request #187)
RTM. Fixed order warm up code to clear errors detected during warmup
2019-03-11 12:38:05 +00:00
Eric Harding 9117e3e238 Add a minimum tracing interval for scoped logs 2019-03-11 07:58:43 -04:00
Eric Harding c0f079b7f6 Code review feedback 2019-03-11 07:31:03 -04:00
John Colvin e9cf1db184 OrderBookView - Fills/AlgoEditor GridSplitter needs min height to prevent it from vanishing when fills grid is collapsed 2019-03-10 23:37:28 -05:00
John Colvin e4c98d947e Merge remote-tracking branch 'origin/SPARK-2079-trigger-cleanup' into SPARK-1944-attach/detach/update 2019-03-10 23:10:59 -05:00
John Colvin ab9de97d6e remove file that was accidentally checked in 2019-03-10 22:59:55 -05:00
John Colvin 4e425a3380 SPARK-2093 Attach/Detach ui 2019-03-10 22:57:52 -05:00
John Colvin 751f22645d SPARK-2089 [cherry-picked] Strange null ref exception in OrderBook row-selection changed callback during application shutdown 2019-03-10 22:21:53 -05:00
John Colvin 47daf439d0 OrderPropertiesEditor - observe_order_type() check if templates are enabled 2019-03-10 21:41:44 -05:00
Eric Harding 19ce3a7dd4 Additional spreader tracing 2019-03-10 16:19:15 -04:00
John Colvin ac67c84ce7 OrderBookWindow - remove individual SparkCommand fields (reduce scope to be local to thread_init()) 2019-03-10 15:04:03 -05:00
John Colvin 79120ea7a5 OrderBook 'fills slave grid' - init predicate hides all rows 2019-03-10 14:45:43 -05:00
John Colvin 5686b57d47 ObservableAutoWrapper - fix typeo (change 'Reset' to 'Remove') 2019-03-10 14:38:06 -05:00
John Colvin cb456a439c fix typeo in LazySubscription 2019-03-10 14:33:42 -05:00
John Colvin a85a7f624a refactor [TableUpdate]LeftJoin\Filter extensions to use AggregateLatest (share common code) 2019-03-10 14:27:39 -05:00
John Colvin 19e4da9936 StringBuilderEx - remove implicit 'StringBuilder' cast operators (seems sketch with reference types) 2019-03-09 22:09:27 -06:00
John Colvin 1494c37cc7 OrderBook - add 'fills-slave-grid' above the algo editor (shows fills for current row-selection) 2019-03-09 22:09:27 -06:00
John Colvin af1fbaf3ed implement TableUpdate filter with IObservable predicate 2019-03-09 22:09:27 -06:00
John Colvin 82a93e39f8 DIYGrid - use max 'visible' column's font size to determine min row height 2019-03-09 22:09:27 -06:00
John Colvin 36684f8ee5 ObservableFilterCollection - in 'reset(),' clear the target collection if source is empty 2019-03-09 22:09:27 -06:00
John Colvin c8cd2ae1ad fix 'sync_with_never_duplicate()' - should remove items from 'dest' that aren't in 'src' 2019-03-09 22:09:27 -06:00
John Colvin 93afb5b602 introduce FillsVMContainer 2019-03-09 22:09:27 -06:00
John Colvin 429b0bb8f8 ObservableAggregateCollection.Aggregator implements IList (not new functionality, just refactored some methods) 2019-03-09 22:09:27 -06:00
Pedro Pinto 80ce5447e5 Validate that IcebergOrders must have child definitions. 2019-03-08 17:56:27 -05:00
Pedro Pinto 609b0dceb1 Native orders now reflect new FIX tags when attached to. 2019-03-08 17:49:51 -05:00
Pedro Pinto ea47042816 Merge branch 'SPARK-1944-attach/detach/update' of bitbucket.org:bluetradesys/spark into SPARK-1944-attach/detach/update 2019-03-08 17:25:30 -05:00
Pedro Pinto 4b23e17534 Updating native order FIX tags when attached. 2019-03-08 17:25:00 -05:00
John Colvin f69301611a OrdersModel - for non-algo updates, set Update.Custom to null instead of populating with existing order's CustomDef 2019-03-08 15:52:38 -06:00
John Colvin c04ac5cd48 OrderDataHelper - fix get_metadata() 'Spreader' switch-case 2019-03-08 14:36:39 -06:00
John Colvin aa39349fc4 SPARK-2092 Orderbook update ui supports spreader algos 2019-03-08 14:36:39 -06:00
John Colvin d1c915fef3 ObservableAutoWrapper - fix typeo (change 'Reset' to 'Remove') 2019-03-08 14:14:08 -06:00
John Colvin 69bdf07423 SPARK-2089 Strange null ref exception in OrderBook row-selection changed callback during application shutdown 2019-03-08 14:14:08 -06:00
John Colvin 4115a6c790 AlgoStructureKey - structure-serialization supports DynamicOrderDefinition 2019-03-08 14:14:08 -06:00
John Colvin 8f8aae57de remove IAlgoVM.can_edit_blah props; view binds to permission flags w EnumHasFlagsConverter; view uses VisualStates 2019-03-08 14:14:08 -06:00
John Colvin 362f75c8b2 OrderBookVM - null check an event, fix initial algo editing permissions 2019-03-08 14:14:07 -06:00
Pedro Pinto 39e855e691 Merged 2019-03-08 14:08:46 -05:00
Pedro Pinto 73303ccd33 Merge branch 'fix_warmup_limit_polution' into SPARK-1944-attach/detach/update 2019-03-08 14:02:23 -05:00
John Colvin 6826494898 EnumHelper - don't initialize 'ThreadStatic' fields 2019-03-07 13:37:02 -06:00
Pedro Pinto 69fcfbd6ce Removed over-zealous assertion. 2019-03-07 14:28:49 -05:00
Pedro Pinto c1d62a2ff6 Fixed OrderManager reentrancy problem reported by Taha 2019-03-07 13:27:05 -05:00
Pedro Pinto c1d4b9cd9f Fixed order warm up code to clear errors detected during warmup
This could cause later orders to fail even if the error condition no
longer obained.
2019-03-07 11:23:57 -05:00
Pedro Pinto dce856e4af Fixed duplication of entries in event_log_archive in perf_log 2019-03-07 11:04:11 -05:00
Pedro Pinto 1fa419eae2 Merge branch 'SPARK-1944-attach/detach/update' of bitbucket.org:bluetradesys/spark into SPARK-1944-attach/detach/update 2019-03-06 14:42:16 -05:00
Pedro Pinto e3a3eb6d85 Redirected Iceberg factory to create Iceberg2. 2019-03-06 14:41:57 -05:00
Pedro Pinto 18a32f9a05 White space. 2019-03-06 14:41:26 -05:00
John Colvin b7c127b2d8 EnumHasFlagsConverter - ContentProperty = 'Flags' 2019-03-06 12:59:16 -06:00
John Colvin f83f94fb07 rename [class]EnumFlagsToBooleanConverter -> EnumHasFlagsConverter (matches filename) 2019-03-06 12:55:28 -06:00
John Colvin 23f76a1c80 remove some redundant\unused code 2019-03-06 12:49:08 -06:00
John Colvin cc0f6ea448 OrderBook Algo Panel - show inactive order algos, but disable controls 2019-03-06 12:48:58 -06:00
John Colvin cbb6936553 OrderBookView - animate GridSplitter background on mouse over 2019-03-05 23:30:28 -06:00
John Colvin 633433cab9 Colors.xaml - fix line endings 2019-03-05 23:24:35 -06:00
John Colvin 3ab7ed0b71 AlgoEditorView - remove ugly ToString() text when Limit, Market etc., are selected 2019-03-05 23:10:54 -06:00
John Colvin fc1adf8c28 OrderBookWindow - include 'show/hide algo editor command' in toolbar (not just context menu) 2019-03-05 20:31:24 -06:00
John Colvin 79cf313b5b SPARK-2014 Add OB support for full updates 2019-03-05 20:18:24 -06:00
John Colvin 00834a668c ObservableDistinctCollection - actually use equality comparer arg 2019-03-05 20:18:23 -06:00
John Colvin 32c9c23d7e fix typeo in MapAggregate() extension (actually use key_comparer arg) 2019-03-05 20:18:23 -06:00
John Colvin 5ac8d2ada6 AlgoSelector - set selected_algo_changed hook in constructor (not 'load_from' method) 2019-03-05 20:18:23 -06:00
John Colvin 54eb6ceaa9 fix typeo in LazySubscription 2019-03-05 20:18:23 -06:00
John Colvin 540db91311 add config to enable\disable multi-threading for ISharedWindows 2019-03-05 20:18:05 -06:00
John Colvin 1ee2193c88 ObservableAutoWrapper.connect_item_actions - invoke action for existing items 2019-03-05 20:16:48 -06:00
John Colvin 84877af947 SPARK-2079 [pr edit]child algo 'name' overlapped expander button 2019-03-05 20:16:48 -06:00
John Colvin 388cf92a70 SPARK-2079 [pr edit]remove #define ALLOW_FIXED_PRICE_TRIGGER 2019-03-05 20:16:48 -06:00
John Colvin 840441a749 SPARK-2079 Trigger Clean up (simplify) 2019-03-05 20:16:48 -06:00
John Colvin c39a510d09 refactor\cleanup OrderTypeVMBase and impls 2019-03-05 20:16:48 -06:00
Pedro Pinto 299d46aa2c Attach/Detach passes initial tests. 2019-03-05 15:44:01 -05:00
Jake Norbut f006d4ffdb Merge branch 'SPARK-2054' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-03-05 14:21:35 -06:00
John Colvin 31dd7849b9 SPARK-2086 SyntheticEditor - user can't type '.' in price multiplier cell 2019-03-05 14:17:19 -06:00
Jake Norbut bcf9b97420 Merge branch 'SPARK-2054' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-03-05 12:56:08 -06:00
John Colvin 697ffd71eb ObservableAutoWrapper.connect_item_actions - invoke action for existing items 2019-03-04 23:52:22 -06:00
Jake Norbut b56fc2445c != instead of !(); John leaves no stone unturned 2019-03-04 17:05:13 -06:00
Jake Norbut a4d162c3a2 EnumHelper equality be gone 2019-03-04 16:53:08 -06:00
Jake Norbut 527bc05b6f removing comment cruft 2019-03-04 15:49:17 -06:00
Jake Norbut a52d65859b 3 bools to 1 Enum in the protobuf 2019-03-04 15:45:30 -06:00
Jake Norbut 363b9e3e7a I missed an early return branch in _format_tick 2019-03-04 15:31:21 -06:00
Eric Harding 3a06684146 Almost never want to record sim market data 2019-03-04 16:15:53 -05:00
John Colvin 8643feabf2 SPARK-2079 [pr edit]child algo 'name' overlapped expander button 2019-03-04 13:44:50 -06:00
Pedro Pinto 345f6ed9fe Added detach handling to iceberg 2019-03-04 14:44:42 -05:00
Pedro Pinto 31aa9d9bf6 Merge remote-tracking branch 'origin/master' into SPARK-1944-attach/detach/update 2019-03-04 14:30:32 -05:00
John Colvin e7f1c59210 SPARK-2079 [pr edit]remove #define ALLOW_FIXED_PRICE_TRIGGER 2019-03-04 13:27:44 -06:00
Pedro Pinto 0c063a3876 Iceberg with attach is code complete. 2019-03-04 14:13:39 -05:00
Jake Norbut c9f403973b Adding a ColSpan on the groupbox so it doesn't push over the merged combo box 2019-03-04 10:53:49 -06:00
Jake Norbut d6bc6fa289 simplifying logic regarding decision to format with decimal part 2019-03-04 10:02:15 -06:00
Taha Afzal 7557ca38cb Merge branch 'SPARK-2054' into SPARK-2058-netchange 2019-03-04 09:03:16 -05:00
Pedro Pinto af253198d2 Iceberg that can attach. 2019-03-04 08:55:36 -05:00
Pedro Pinto cc381d605c Merged with master 2019-03-04 08:55:06 -05:00
Jake Norbut ef59511a01 woops, fixing bug 2019-03-04 07:50:10 -06:00
Jake Norbut 8dfbb106a6 hide decimals when in ticks mode 2019-03-04 07:46:59 -06:00
Taha Afzal 3e9a931c5f removing an unused function 2019-03-03 23:15:06 -05:00
Taha Afzal 71fac664e8 the net change test now tests for quoting prices on both legs 2019-03-03 16:52:29 -05:00
Taha Afzal 1b39ffc323 cleaning up the test 2019-03-03 16:30:19 -05:00
John Colvin bc69c669ae SPARK-2079 Trigger Clean up (simplify) 2019-03-02 21:50:17 -06:00
Taha Afzal 170f7426aa for net change, the reported price with filled_qty==0 was incorrect 2019-03-02 22:43:35 -05:00
John Colvin 16d30e450f refactor\cleanup OrderTypeVMBase and impls 2019-03-02 19:36:19 -06:00
Taha Afzal a3787ee65a Merge branch 'SPARK-2054' into SPARK-2058-netchange 2019-03-01 16:59:42 -05:00
Taha Afzal 14d5d36067 merging master 2019-03-01 16:57:26 -05:00
Jake Norbut 1af4b97b97 ui feedback from taha 2019-03-01 12:56:41 -06:00
Michael Harding faf60af9c3 Merge branch 'master' into doc_temp_build 2019-03-01 13:52:48 -05:00
John Colvin 109fee099f Merged in SPARK-2082 (pull request #181)
SPARK-2082 mview - canceled orders appear to be working

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-03-01 18:01:31 +00:00
John Colvin 747b44c48d fix strange line ending change in last commit to _index.md 2019-03-01 11:59:01 -06:00
John Colvin 4b091a1259 SPARK-2082 release note 2019-03-01 11:50:56 -06:00
Jake Norbut 9626bee38e resetting this width I inadvertently changed 2019-03-01 10:55:52 -06:00
Jake Norbut 776bc71c22 ladder prices in net change terms 2019-03-01 10:51:16 -06:00
John Colvin 7c0e310f6a SPARK-2082 mview - canceled orders appear to be working 2019-03-01 10:13:49 -06:00
Michael Harding 761657b56b Fix typos 2019-03-01 10:30:41 -05:00
Michael Harding 2988967f1f Merge branch 'master' into doc_temp_build 2019-03-01 09:29:36 -05:00
Michael Harding 09436473ed Merged master into Documentation 2019-02-28 13:27:25 -05:00
Michael Harding 5524e89a10 Add images 2019-02-28 13:26:02 -05:00
Pedro Pinto c2f954a79d More changes to attach/detach API 2019-02-28 13:07:41 -05:00
Taha Afzal d615dcc7d6 tests were failing because of direct vs merged 2019-02-28 11:10:56 -05:00
Taha Afzal d582ed8436 more tests 2019-02-27 19:29:18 -05:00
Taha Afzal c5768c4022 randomized the prices and markets in the net change test 2019-02-27 19:08:29 -05:00
Brian Muhr 6c652df3eb index.md edited online with Bitbucket 2019-02-27 20:34:00 +00:00
Pedro Pinto e79a73147a Merged with master 2019-02-27 15:15:06 -05:00
Michael Harding c4e684f934 SPARK-2047: change temp dir path 2019-02-27 14:41:43 -05:00
Taha Afzal 51ac39c166 test comments 2019-02-27 14:21:41 -05:00
Eric Harding f04a48d4f8 Explicitly deny copying a service. make_*_publisher points back to the owning service 2019-02-27 14:00:04 -05:00
Pedro Pinto 6a921ae900 Merge remote-tracking branch 'origin/master' into SPARK-1944-attach/detach/update 2019-02-27 13:48:22 -05:00
Michael Harding f7baf6c05a Merge branch 'master' into doc_temp_build 2019-02-27 13:39:11 -05:00
Taha Afzal 1eaf3d8629 taming the net_change tests 2019-02-27 13:16:31 -05:00
Brian Muhr 62ecd1a721 index.md edited online with Bitbucket 2019-02-27 16:35:51 +00:00
Pedro Pinto 0f13d27efe Improvements on attach/detach API 2019-02-27 10:08:55 -05:00
Taha Afzal cabc84a577 tests for net change transformation 2019-02-26 17:04:59 -05:00
Pedro Pinto d52347cd71 Trigger state update on new definition in OM. 2019-02-26 11:57:11 -05:00
Pedro Pinto 0ab628fb6c Allow orders to change their full definition. 2019-02-26 11:43:10 -05:00
Taha Afzal 394c7fd954 Merge branch 'SPARK-2058-netchange' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-02-26 10:52:00 -05:00
Pedro Pinto 1a8a492ef7 Attach/detach tweaks. 2019-02-26 09:56:24 -05:00
Taha Afzal b2d1969e9f ticks in net change working now, by transforming the spread 2019-02-25 17:24:31 -05:00
Eric Harding 744797b91c Add compute_synthetic_book function 2019-02-25 14:26:23 -05:00
Pedro Pinto 74066c0ffe Refactored OrderServices 2019-02-25 13:55:13 -05:00
Pedro Pinto 0269d5fa86 Merged with master 2019-02-25 12:50:06 -05:00
Pedro Pinto 84a5a11a9c attach/detach code complete perhaps. 2019-02-25 12:49:23 -05:00
Michael Harding 6446e38aea Merge branch 'master' into doc_temp_build 2019-02-25 11:48:36 -05:00
Michael Harding 32df88914b Merge branch 'master' into doc_temp_build 2019-02-25 11:29:21 -05:00
Michael Harding 628490ac2d Hide JS errors in IE 2019-02-25 11:10:19 -05:00
Michael Harding ef475f7003 Merge branch 'master' into doc_temp_build 2019-02-25 11:09:41 -05:00
Pedro Pinto a01e6df7fa Merged with master 2019-02-25 09:06:55 -05:00
Taha Afzal 76a1367510 Price calc (standard vs net change) is a property on LegPropertyData 2019-02-24 20:21:21 -05:00
Taha Afzal fef5ae4aff Adhering spreader to bts::api::SyntheticContract::PriceCalc (from the old one) 2019-02-23 23:59:09 -05:00
Taha Afzal 2c9e83c6f0 Merge branch 'SPARK-2058-netchange' of bitbucket.org:bluetradesys/spark into SPARK-2058-netchange 2019-02-22 17:50:10 -05:00
Taha Afzal 35738c25f1 dividing by tick_size for net change 2019-02-22 17:50:02 -05:00
Eric Harding e64567f8ad SPARK-2062 Unify NetChange enums in UI 2019-02-22 17:15:52 -05:00
Michael Harding 5865ab21bc Downgrade js to es3 2019-02-22 17:05:11 -05:00
Pedro Pinto d4caccdbfb Build. 2019-02-22 14:50:25 -05:00
Eric Harding 78b6169750 Update SyntheticContract.PriceCalc enum values 2019-02-22 12:24:01 -05:00
Eric Harding e2c0d81f19 Update NetChange enums to be per security rather than per leg. Also non mutable 2019-02-22 11:59:51 -05:00
Pedro Pinto 4ede8a62ea Merged with master 2019-02-22 11:31:05 -05:00
Eric Harding b811b960ec SPARK-2063 Ladder day change doesn't update as often as I'd like 2019-02-22 09:39:15 -05:00
Eric Harding bf0507e434 Fix synthetic_net_change unit test 2019-02-22 08:37:21 -05:00
John Colvin 1205a35b69 SyntheticEditorVM - add 'MarketDataType' param-row back (accidentally removed) 2019-02-21 19:53:18 -06:00
John Colvin 2f4d2a9c0d SPARK-2058 / SPARK-2062 Synthetic NetChange should be set per Synthetic not per Leg (UI Only) 2019-02-21 19:43:40 -06:00
John Colvin 08e5088d72 SPARK-2058 / SPARK-2059 Allow 100/100 tick size 2019-02-21 18:40:08 -06:00
Eric Harding e7b371aa6f SPARK-2060 NetChange in Ticks 2019-02-21 12:49:55 -05:00
Eric Harding 3094eee327 Add double division operator to IntPrice 2019-02-21 12:00:01 -05:00
Pedro Pinto ba11248ac9 attach/detach 2019-02-21 09:03:26 -05:00
Michael Harding 80ff61c9b2 Docs: change URL in config.toml 2019-02-20 09:40:27 -05:00
Pedro Pinto 22cde8aa0d Merge branch 'master' into SPARK-1944-attach/detach/update 2019-02-19 17:02:02 -05:00
Michael Harding 2a42c1fab0 SPARK-2047: Build user docs in a temp dir 2019-02-19 16:38:17 -05:00
Pedro Pinto 3dba49e899 More attach/detach design docs. 2019-02-19 10:18:27 -05:00
Pedro Pinto 23d7a9e1ad Improved error handling in native order. 2019-02-15 11:27:25 -05:00
Pedro Pinto 7bd9d3f4ad Merge remote-tracking branch 'origin/master' into SPARK-1944-attach/detach/update 2019-02-15 09:07:28 -05:00
Pedro Pinto 7740f64bfd Iceberg2 is working. 2019-02-14 12:40:15 -05:00
Pedro Pinto 7ccbb69f5c Made native order ignore any bad updates/cancels after it terminates. 2019-02-14 12:37:19 -05:00
Pedro Pinto b4ba527c12 Generalized dynamic order mechanism to support any order. 2019-02-14 12:34:08 -05:00
Pedro Pinto a62a41a0aa IB2 code complete. 2019-02-12 17:13:53 -05:00
Pedro Pinto 904023c5fa Added a few comments. 2019-02-12 09:50:32 -05:00
Pedro Pinto a9fc3d43a0 Fixed merge. 2019-02-12 09:36:57 -05:00
Pedro Pinto 4febc07aa0 Merged with master 2019-02-11 18:35:29 -05:00
Pedro Pinto 198e76f722 Renamed crack detach. 2019-02-11 17:52:26 -05:00
Pedro Pinto 836301e75f Iceberg with update 2019-02-11 16:50:35 -05:00
Pedro Pinto ad37a652c5 Refactor 2019-02-11 16:50:35 -05:00
Pedro Pinto 5bed54c953 More SDK work. 2019-02-11 16:50:35 -05:00
Pedro Pinto 5675c4e117 Renamed crack to detach. 2019-02-11 16:50:35 -05:00
Jake Norbut 16f1e568a5 release notes - words 2019-02-11 16:50:35 -05:00
Jake Norbut e375274cbe Updating release notes describing Floating Order Book 2019-02-11 16:50:35 -05:00
Pedro Pinto cf3ef9323a dev_process.md edited online with Bitbucket 2019-02-11 16:50:35 -05:00
Pedro Pinto 7e2e2bf1b0 Dev doc 2019-02-11 16:50:35 -05:00
Kevin Darby 781c9dcc86 moved that using std::vector out of header 2019-02-11 16:50:35 -05:00
Kevin Darby a8a922160d move externals pointer to proto36, small changes in spark to support that 2019-02-11 16:50:35 -05:00
Pedro Pinto 367bd0e3b9 Added release notes 2019-02-11 16:50:35 -05:00
Eric Harding 4215d96ba9 Wait to give obvm obvm a chance to load rows and not flash 2019-02-11 16:50:35 -05:00
Jake Norbut 50d18eb10d PR edits - Style cop 2019-02-11 16:50:35 -05:00
Jake Norbut b65fedc781 PR edit - simplify LINQ expressions when using Where and FirstOrDefault 2019-02-11 16:50:35 -05:00
Jake Norbut 88c8663d29 PR - naming 2019-02-11 16:50:35 -05:00
Jake Norbut 8c7dd2460d PR edit - changing orderbook_vm property to method to better reflect the nature of the beast 2019-02-11 16:50:35 -05:00
Jake Norbut f20ee21ce7 PR edits - Simplifying XAML since UserControl is a ContentControl 2019-02-11 16:50:35 -05:00
Jake Norbut a61e21bd87 PR edits - Hashset for the win over List for Contains 2019-02-11 16:50:35 -05:00
Jake Norbut 39014696f3 PR edit; Set is your friend 2019-02-11 16:50:35 -05:00
Jake Norbut af278d2d87 PR edits - devolving qty/dirty and price/dirty 2019-02-11 16:50:35 -05:00
Jake Norbut 636e170f44 PR edits 2019-02-11 16:50:35 -05:00
Jake Norbut 3d4fe8f832 PR edit; columns setter is now private 2019-02-11 16:50:35 -05:00
Pedro Pinto 94285918bf Trying to fix the linux/win war on CMAkeDeps.cmake 2019-02-11 16:50:35 -05:00
Jake Norbut 2ba7f949cc MinWidth so that the vertical scrollbar does not cover up the content in the last column 2019-02-11 16:50:35 -05:00
Jake Norbut fc82ccf221 enforce max height on popop size 2019-02-11 16:50:35 -05:00
Jake Norbut e71b1518f0 move edit price immediately after edit qty 2019-02-11 16:50:35 -05:00
Jake Norbut 7738da7d16 remove ladder tooltip and backing property 2019-02-11 16:50:35 -05:00
Jake Norbut c91eedcca0 minor refactoring, some notes after talking w/ john 2019-02-11 16:50:35 -05:00
Jake Norbut 4f5f4c61f3 inline price ticker 2019-02-11 16:50:33 -05:00
Jake Norbut 2f31fb6284 qty ticker layout changes 2019-02-11 16:48:53 -05:00
Jake Norbut d18caa7ba1 auto close popup when no orders remain 2019-02-11 16:48:53 -05:00
Jake Norbut ac88586c8f darken popup border 2019-02-11 16:48:53 -05:00
Eric Harding 968dfbac2a Need spark_test.dll for system tests 2019-02-11 16:48:53 -05:00
Eric Harding 0501418da6 provide_command_line for system_tests -configserver 2019-02-11 16:48:53 -05:00
Eric Harding 4efd853c96 Simplify getting a security 2019-02-11 16:48:53 -05:00
Michael Harding a384eb9f73 Return int from sparkcmd 2019-02-11 16:48:53 -05:00
Eric Harding 4747fe5535 Make sure every sparkcmd command respects -configserver 2019-02-11 16:48:53 -05:00
Eric Harding 94eb1e37cb Add netcore publish to jenkinsbuild.cmd 2019-02-11 16:48:53 -05:00
Jake Norbut fd1ae60e49 remove checkin cruft 2019-02-11 16:48:53 -05:00
Jake Norbut 62d06180ea making row stay in edit mode when a change has not yet been committed but the selection has been moved off the row 2019-02-11 16:48:53 -05:00
Jake Norbut dba3b9371c hopefully use the right Dispatcher this time 2019-02-11 16:48:53 -05:00
Jake Norbut aadfe2a677 mini ob - inline qty tick up/down; formatting 2019-02-11 16:48:53 -05:00
Jake Norbut c0c34efc47 adding comment back in 2019-02-11 16:48:53 -05:00
Jake Norbut b66fb02174 adding single-click delete/cancel functionality 2019-02-11 16:48:53 -05:00
Jake Norbut 3cf026ff47 Properly Dispose of the OrderBookVM when the popup closes 2019-02-11 16:48:53 -05:00
Jake Norbut 8619084fa7 adding a border around the popup window 2019-02-11 16:48:53 -05:00
Jake Norbut f6df644ab1 MiniOB will have parent-child rows expanded by default 2019-02-11 16:48:53 -05:00
Jake Norbut 1183dee418 Create a new OrderbookVM each time; fixes crash when launching mini-ob from same badge multiple times 2019-02-11 16:48:53 -05:00
Jake Norbut ffaba90c96 Remove ContextMenu from Badge and setup new Popup 2019-02-11 16:48:53 -05:00
Jake Norbut 9eb760b3c7 Adding a Popup dep prop to the Badge. Note - I don't think Ineed AffectsRender here 2019-02-11 16:48:53 -05:00
Jake Norbut 70fcb30c67 DIYGrid - adding dep prop to allow rows horizontal scrollbar to be strictly collapsed 2019-02-11 16:48:53 -05:00
Jake Norbut 553ab7b858 in case my machine starts on fire 2019-02-11 16:48:53 -05:00
Pedro Pinto 8dcf41e9cf Added attach/detach interface proposal. 2019-02-01 10:08:08 -05:00
Pedro Pinto a0bf0e93f4 Fixed comment. 2019-01-30 14:48:00 -05:00
Pedro Pinto 5529e80e17 Tentative new order services interface. 2019-01-29 18:07:27 -05:00
Pedro Pinto 5df7cec55a Merge branch 'master' into SPARK-1944-attach/detach/update 2019-01-29 16:25:03 -05:00
Pedro Pinto 8f705d42b3 Merge branch 'master' into SPARK-1944-attach/detach/update 2019-01-29 10:44:50 -05:00
Pedro Pinto fc2f85b842 Refactored a bit. 2019-01-28 08:52:34 -05:00
Pedro Pinto ffe5eee584 Merged with ticker update branch 2019-01-18 10:21:48 -05:00
Pedro Pinto b7897fc7ac Documenting the iceberg design. 2019-01-16 14:00:16 -05:00
Pedro Pinto b4d18a62c9 Allowed async return out of order operations. 2019-01-16 09:41:06 -05:00
Pedro Pinto c6b450f45c Fixed broken perf traces. 2019-01-15 13:58:15 -05:00
Pedro Pinto 6a46049610 Merged 2019-01-15 09:21:20 -05:00
Pedro Pinto 21712711f7 Added Order API support for return result of order ops. 2019-01-14 12:48:14 -05:00
Pedro Pinto 9f1bb177b8 Fixed build. 2019-01-12 15:56:01 -05:00
Pedro Pinto dcd2cc22e1 Removed trace::CorID from most of the order code. 2019-01-12 15:45:08 -05:00
Pedro Pinto af91ee1512 Merge remote-tracking branch 'origin/master' into SPARK-1944-attach/detach/update 2019-01-11 12:50:12 -05:00
Pedro Pinto b4d63aa3cc Merge branch 'master' into SPARK-1944-attach/detach/update 2019-01-10 14:26:34 -05:00
Pedro Pinto d0e32911ad Made recovery of lost order use case insensitive op id and account.
Turns out CME upper cases operator id and clearing account when it
sends them back.
2019-01-10 11:23:20 -05:00
Pedro Pinto 75fa582774 Merged master 2019-01-09 17:51:21 -05:00
Pedro Pinto 69155162ca Improved replay of fix messages to be more robust. 2019-01-09 17:44:17 -05:00
Pedro Pinto 2c865c123a Fixed broken persistence of orders. 2019-01-09 17:43:41 -05:00
Pedro Pinto 0ec955cc90 Possibly fixed persistence failure when trying to duplicate fill. 2019-01-09 15:21:07 -05:00
Pedro Pinto 0c4e22537d Upgraded SDK example to support op result of order.activate() 2019-01-09 15:19:37 -05:00
Pedro Pinto 601eb4ece1 key needs to be setup before we can call on_init_from_exe_report 2019-01-09 15:18:34 -05:00
Pedro Pinto b84a638199 Fixed life time bug when retuerning a def from an execution report. 2019-01-09 15:17:50 -05:00
Pedro Pinto 34350fd469 Fixed utterly broken recovery of orders from execution reports. 2019-01-09 15:11:53 -05:00
Pedro Pinto a469da5cc6 Fixed race condition when obtaining thread name of perf log thread. 2019-01-09 14:45:35 -05:00
Pedro Pinto 8be8a67f18 Adding results to order operations. 2019-01-07 18:06:46 -05:00
Pedro Pinto abc2e55610 Added assert. 2019-01-02 14:37:23 +00:00
Pedro Pinto cfd15d3371 Allowed full order definition updates. 2018-12-31 17:46:00 +00:00
Pedro Pinto 44369bae43 Some work on supporting full order updates. 2018-12-28 22:13:38 +00:00
341 changed files with 10236 additions and 3505 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;/home/ppinto/Dropbox/bts/spark/doc/user/mkdocs.yml;bts-help-theme/404.html;bts-help-theme/base.html;bts-help-theme/content.html;bts-help-theme/css/base.css;bts-help-theme/css/bts.css;bts-help-theme/css/font-awesome.min.css;bts-help-theme/css/highlight.css;bts-help-theme/css/local.css;bts-help-theme/fonts/fontawesome-webfont.eot;bts-help-theme/fonts/glyphicons-halflings-regular.eot;bts-help-theme/img/favicon.ico;bts-help-theme/js/base.js;bts-help-theme/js/bootstrap-3.0.3.min.js;bts-help-theme/js/highlight.pack.js;bts-help-theme/js/jquery-1.10.2.min.js;bts-help-theme/keyboard-modal.html;bts-help-theme/main.html;bts-help-theme/nav-sub.html;bts-help-theme/nav.html;bts-help-theme/search-modal.html;bts-help-theme/toc.html;docs/Help.md;docs/Launcher.md;docs/Logging-In-to-the-System.md;docs/Market.md;docs/My-Windows.md;docs/Settings.md;docs/Spreader.md;docs/Trading.md;docs/images/image1.png;docs/images/image10.png;docs/images/image11.png;docs/images/image12.png;docs/images/image13.png;docs/images/image14.png;docs/images/image15.png;docs/images/image16.png;docs/images/image17.png;docs/images/image18.png;docs/images/image19.png;docs/images/image2.png;docs/images/image20.png;docs/images/image21.png;docs/images/image22.png;docs/images/image23.png;docs/images/image24.png;docs/images/image25.png;docs/images/image26.png;docs/images/image27.png;docs/images/image28.png;docs/images/image29.png;docs/images/image3.png;docs/images/image30.png;docs/images/image31.png;docs/images/image32.png;docs/images/image33.png;docs/images/image34.png;docs/images/image35.png;docs/images/image36.png;docs/images/image37.png;docs/images/image38.png;docs/images/image39.png;docs/images/image4.png;docs/images/image40.png;docs/images/image41.png;docs/images/image42.png;docs/images/image43.png;docs/images/image44.png;docs/images/image45.png;docs/images/image46.png;docs/images/image47.png;docs/images/image48.png;docs/images/image49.png;docs/images/image5.png;docs/images/image50.png;docs/images/image51.png;docs/images/image52.png;docs/images/image53.png;docs/images/image54.png;docs/images/image55.png;docs/images/image56.png;docs/images/image57.png;docs/images/image58.png;docs/images/image59.png;docs/images/image6.png;docs/images/image60.png;docs/images/image61.png;docs/images/image62.png;docs/images/image63.png;docs/images/image64.png;docs/images/image65.png;docs/images/image66.png;docs/images/image67.png;docs/images/image68.png;docs/images/image69.png;docs/images/image7.png;docs/images/image70.png;docs/images/image71.png;docs/images/image72.png;docs/images/image73.png;docs/images/image74.png;docs/images/image76.png;docs/images/image8.png;docs/images/image9.png;docs/index.md;docs/release_notes.md)
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/bts_spark_sdk/Client/Overview.md;content/bts_spark_sdk/Client/Threading.md;content/bts_spark_sdk/Client/_index.md;content/bts_spark_sdk/Server/_index.md;content/bts_spark_sdk/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+1 -1
View File
@@ -22,7 +22,7 @@ update_deps_file("${All_Deps}")
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/web/docs/index.html
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build.sh
ARGS ${CMAKE_BINARY_DIR}/web/docs/
ARGS ${CMAKE_BINARY_DIR}/web/docs/ ${CMAKE_BINARY_DIR}
COMMENT "Building Manual UI"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${All_Deps})
+15 -3
View File
@@ -1,8 +1,20 @@
#!/bin/bash
tmpfile=$(mktemp /tmp/quiet_build_log.XXXX)
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
./run_zola.sh build -u '/docs/' -o $1 >$tmpfile 2>&1
rm ./content/bts_spark_sdk/Server/algos.md
cur_dir=$(pwd)
temp_path="$2/web_temp"
zola_path=$(readlink -f "../../externals/linux/bin/zola/zola")
mkdir -p $temp_path
cp -r ./* $temp_path
cp -r ../../src/sdk/doc/* $temp_path/content/bts_spark_sdk/Server/
cd $temp_path
$zola_path build -u '/docs/' -o $1 >$tmpfile 2>&1
cd $cur_dir
rm -r $temp_path
if [ $? -eq 0 ]
then
rm $tmpfile
+2 -2
View File
@@ -1,5 +1,5 @@
# The URL the site will be built for
base_url = "https://www.bluetradingsystems.com/docs"
base_url = "https://www.bluetradingsystems.com/documentation"
title = "BTS Spark Documentation"
@@ -15,4 +15,4 @@ build_search_index = true
[extra]
# Put all your custom variables here
book_numbered_chapters = false
book_numbered_chapters = false
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

+1 -1
View File
@@ -25,7 +25,7 @@ insert_anchor_links = "right"
which will list what version you are running as well as who is
currently logged in.
![](image32.png)
![](about_spark.png)
## Activity log
This is an overall log viewer which will show
Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

+20 -16
View File
@@ -51,7 +51,7 @@ insert_anchor_links = "right"
securities you have chosen, in this example, it appears like the
following:
![](image63.png)
![](market_view.png)
- Once a market view is launched and you are viewing data, you can
customize the view further by the following:
@@ -88,31 +88,34 @@ insert_anchor_links = "right"
- Change securities
- Click on the settings icon ![](image12.png)which will launch the market view security selection.  You can add or remove Securities you have already selected and are shown in the market view.
- Click on the settings icon ![](change_securities.png) which will launch the market view security selection.  You can add or remove Securities you have already selected and are shown in the market view.
- Launch an order ticket
- This is done 1 of 2 ways:
- This is done 1 of 3 ways:
- Single left clicking on either the Bid Qty, Bid Price, Ask Price,
or Ask Qty which will bring up an order ticket pre-populated with
either the Bid or Ask price and volume.
- Highlighting a single security and clicking on the order ticket
![](image23.png) icon
on the left-hand browser window of the market view
- Highlighting a single security and right clicking bringing up the
menu options to launch an order ticket.
- Launch a ladder for 1 security
- You can double-click on any security in the market view, besides
the Bid Qty, Bid Price, Ask Price, or Ask Qty columns to launch a
ladder for that Security.
- This is done 1 of 2 ways:
- You can double-click on any security in the market view, besides
the Bid Qty, Bid Price, Ask Price, or Ask Qty columns to launch a
ladder for that Security.
- Highlighting a single security and right clicking bringing up the
menu options to launch a ladder.
- Launching multiple ladders
- If you hold down the Ctrl key and select multiple securities, you
can then click on the ladder icon
![](image69.png) on
the left-hand side to bring up the selected ladders all at
once.
- This is done 1 of 2 ways:
- If you hold down the Ctrl key and select multiple securities, you
can then click on the ladder icon on the left hand side to bring
up the selected ladders all at once.
- Highlighting multiple securities and right clicking to bring up
the menu options to launch a ladder.
- Launching a Fills window for 1 or many securities
@@ -158,8 +161,9 @@ insert_anchor_links = "right"
- On launching a security browser will appear, allowing you to choose
the products you would like to view.
![](image30.png)
![](time_and_sales.png)
- From time and sales, you can launch the same items as the market
view with the exception of a launching the synthetic window to
create a synthetic product
create a synthetic product
- From the settings icon ![](settings_icon.png) you can customize the time and sales modifying grid; the colors of the aggressor, status of trades coming in, as well as font sizes and which columns to see.
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

+137 -40
View File
@@ -59,7 +59,7 @@ insert_anchor_links = "right"
functionality to quickly perform various tasks with live orders as
well as launch other modules.  It appears like the below:
- ![](image4.png)
![](spark_menu_order_book.png)
- ![](image48.png)Fills -
clicking on this will launch a fills window
@@ -79,6 +79,18 @@ insert_anchor_links = "right"
- ![](image2.png)Cancel
all - clicking on this will cancel all working orders you have the
order book
- ![](recreate_icon.png) Recreate - clicking this will recreate already cancelled orders, sending them back to market with the same price and quantity as they were previously at before being cancelled.
- ![](collapse_all_icon.png) Collapse all - This will collapse all trees inside the order book to show only the top level of the order.
- ![](expand_all_icon.png) Expand all - This will expand to show everything inside the order book.
- ![](settings_icon.png) Settings - This launches the settings for the orderbook.
- Furthermore, there is a “floating” or “mini orderbook” feature as well
integrated within the ladder itself.
- To access this, you need to center click with your mouse wheel over
working orders on the ladder.
- From here, you can change the price and quantity of any working order
within the mini orderbook straight from the window
- This looks like below:
![](mini_orderbook.png)
## Fills
@@ -89,6 +101,8 @@ insert_anchor_links = "right"
like
- All fill windows are brought up as tabs and can be combined with
all other windows within BTS
- There is also a settings button in the left hand tab which can be
launched to customize the fills window
## P&amp;L
@@ -104,10 +118,20 @@ insert_anchor_links = "right"
other windows within BTS
- All columns are sortable and either searchable or can be further
filtered by entering in min and max values.
- Below is an example P&L window showing a user’s multiple accounts
and various positions in each:
- There is also a settings button in the left hand tab which can be
launched to customize the P&amp;L window
- In regards to the PnL specifically, you can choose how you want the
PnL calculated based on Last, MidPoint, or Settle price.
- This is accomplished on the top bar, which when clicked on, displays
the choices for calculation, this appears like below:
![](image67.png)
![](pnl_calculation.png)
- Below is an example P&L window showing a user’s multiple accounts and
various positions in each:
![](pnl_window.png)
## Ladder
@@ -125,7 +149,7 @@ insert_anchor_links = "right"
market with a single click
- Below is an example ladder:
![](image36.png)
![](ladder.png)
- The ladder consists of 2 major parts, the Ladder itself and the
information and control section on the left-hand side
@@ -213,37 +237,59 @@ insert_anchor_links = "right"
         
![](image50.png)
- Lean
- This is a specialized algo order which allows the user to
- Lean
- This is a specialized algo order which allows the user to
essentially watch the opposite-inside market size and cross when
that size goes below the threshold defined.
- “Crossing” means to take an order working on the bid and 'cross' by
buying into the asks.  This is often referred to as “paying up” as
well
- “Cross” means to take an order working on the bid and 'cross' by
buying into the asks.  This is often referred to as “paying up” as well
![](image22.png?v2)
![](lean_algo_control.png)
- Join
- This is a specialized algo order which allows the user to enter an
- Join
- This is a specialized algo order which allows the user to enter an
order which will watch the opposite-inside market and join when the
market flips and has at least threshold size as defined.
- “Flip” refers to the event where the best offer becomes the best
bid or the market trades away from you, i.e. the line moves up in
the GUI.  The reverse is true if selling.
![](image41.png)
![](join_algo_control.png)
- Top
- This is a specialized algo order which allows the user to
- Top
- This is a specialized algo order which allows the user to
essentially not trade by themselves
- If the order is on the market (current bid price) and the quantity
- If the order is on the market (current bid price) and the quantity
on that bid price is below the top threshold defined the order will
cancel and do nothing else
![](top_algo_control.png)
![](image26.png)
- Offset
- The goal of the offset order is to send your order X amount of ticks
from where you actually place it on the ladder. This is sometimes
known as a "fat finger" safety.
![](offset_algo_control.png)
- OCO
- An OCO order creates a relationship between a child order and a 'stop'
or 'other' order such that when one of these is cancelled, both cancel,
or when one is filled, the other order's size is debited in the amount
of the fill. Composing this order with Cover simulates scalping
functionality.
![](oco_algo_control.png)
- Cover
- A Cover order’s purpose is to fire a hedge order (of any algo-type) in response to fills from it’s child order on the opposite side of the market N ticks away.
![](cover_algo_control.png)
- Trigger
- The Trigger order is exactly as it sounds, where it will trigger an order based on conditions defined.
- This order also has exit strategy built in where a user can define when the trigger order can auto-cancel based on time as well as price conditional.
![](trigger_algo_control.png)
- The middle of the control section is where you can click on
predefined values to quickly populate the volume at which you wish
@@ -264,20 +310,36 @@ insert_anchor_links = "right"
-  The bottom section of the control area of a ladder has 4 aspects
to it
- Cancel Buys, which will cancel all buys currently working on the
ladder
- Cancel Sells, which will cancel all sells currently working on the
ladder
- Cancel All, which will cancel all orders on the ladder
- Settings, which will launch the ladder settings window which
appears like below
- Cancel Buys, which will cancel all buys currently working on the
ladder
- Cancel Sells, which will cancel all sells currently working on the
ladder
- Cancel All, which will cancel all orders on the ladder
- Settings, which will launch the ladder settings window which
appears like below
![](ladder_settings.png)
- Fonts and Colors - This is where you can customize the overall main
aspects of the ladder.
- Market Break Lines - These are where can control the viewing of what
has been traded through various shading.
- Market Size Display - This is where you can define if you wish
to see implieds or not and how exactly you would like to see them.
- Volume Display - Where you can choose to show the volume traded at price
graph and what details you would like to see.
- Size Presets - Here you can define what volume of predefined quick buttons
you would like to see or any at all. You can remove these by clicking on the “X” next to the volume number to remove one or many.
- Algo Favorites - Here you can set up which “quick button” or user defined
algos to see. You can customize the amount of columns to be
displayed as well.
- Left Panel - This is where you can remove predefined information on the
left panel to save room and get rid of data that you might not require
to see.
![](image71.png)
- The other, main part of the ladder, is the ladder itself as
displayed below
![](image65.png)
![](ladder_main_part.png)
- There are 5 main columns
@@ -291,14 +353,10 @@ insert_anchor_links = "right"
where the last trade occurred
- Functionality of the ladder
- To enter an order directly by clicking in the ladder, left click on either the bid or ask volume with a volume greater than 0 on the predefined value box on the left-hand section
- A badge will be displayed within the orders column of the value you have chosen and the quantity filled as displayed below
- To enter an order directly by clicking in the ladder, left click on
either the bid or ask volume with a volume greater than 0 on the
predefined value box on the left-hand section
- A badge will be displayed within the orders column of the value you
have chosen and the quantity filled as displayed below
![](image46.png)
![](ladder_badges.png)
- To change the price level you wish to work the order, you can right-click and hold on the badge to move it up or down the ladder for the
desired price
@@ -344,3 +402,42 @@ insert_anchor_links = "right"
- Please note you can also launch the required parameters for algo or
synthetic orders by clicking on the right arrow next to the algo or
synthetic
## Algo Studio
- The BTS Algo Studio was built for users to experience algo composability
never seen before in a professional futures trading system.
- There is no limit for putting both “canned algos” already present in the
system as well as user defined algos fed into the system via the API.
- This is all in place in a very user friendly GUI as shown below:
![](algo_studio.png)
- There are 3 main areas of the Algo Studio:
- Left Pane (from top to bottom)
- Add Algo
- Clone Algo
- Save Algo
- Remove Algo
- Middle Pane
- This is where your customized algo will be displayed in a running list
- Right Pane
- This is where your complete customization happens of combining various
algos together and saving them to fit your exact needs
- On the very top are the name and abbreviation boxes
- For the name you can simply name the algo whatever you would like
- For the abbreviation section (which is optional), you can put in an
abbreviated name for the algo which will be displayed on the Algo
buttons on the ladder. (Example - Algo name = PROFIT_OCO_LEAN,
Abbrev. = POL)
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+14
View File
@@ -4,6 +4,20 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 1.5.6
### User Visible Changes
- Fixed a bug that could cause limits to be erroneously enforced
- Improved algo documentation
- Changed how the spreader uses net change
- Simplified trigger algo user interface
### Internal changes
- Defaulted to not recording sim market data
- Improved documentation build
- Added additional performance tracing
## 1.5.5
- Fixed Market View 'stale working orders' bug
## 1.5.4
- Avoided logging when warming up cache
+197 -223
View File
@@ -1,223 +1,197 @@
function initToggleMenu() {
var $menu = document.querySelector(".menu");
var $menuIcon = document.querySelector(".menu-icon");
var $page = document.querySelector(".page");
$menuIcon.addEventListener("click", function() {
$menu.classList.toggle("menu-hidden");
$page.classList.toggle("page-without-menu");
});
}
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args);
}, wait);
};
}
// Taken from mdbook
// The strategy is as follows:
// First, assign a value to each word in the document:
// Words that correspond to search terms (stemmer aware): 40
// Normal words: 2
// First word in a sentence: 8
// Then use a sliding window with a constant number of words and count the
// sum of the values of the words within the window. Then use the window that got the
// maximum sum. If there are multiple maximas, then get the last one.
// Enclose the terms in <b>.
function makeTeaser(body, terms) {
var TERM_WEIGHT = 40;
var NORMAL_WORD_WEIGHT = 2;
var FIRST_WORD_WEIGHT = 8;
var TEASER_MAX_WORDS = 30;
var stemmedTerms = terms.map(function (w) {
return elasticlunr.stemmer(w.toLowerCase());
});
var termFound = false;
var index = 0;
var weighted = []; // contains elements of ["word", weight, index_in_document]
// split in sentences, then words
var sentences = body.toLowerCase().split(". ");
for (var i in sentences) {
var words = sentences[i].split(" ");
var value = FIRST_WORD_WEIGHT;
for (var j in words) {
var word = words[j];
if (word.length > 0) {
for (var k in stemmedTerms) {
if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
value = TERM_WEIGHT;
termFound = true;
}
}
weighted.push([word, value, index]);
value = NORMAL_WORD_WEIGHT;
}
index += word.length;
index += 1; // ' ' or '.' if last word in sentence
}
index += 1; // because we split at a two-char boundary '. '
}
if (weighted.length === 0) {
return body;
}
var windowWeights = [];
var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
// We add a window with all the weights first
var curSum = 0;
for (var i = 0; i < windowSize; i++) {
curSum += weighted[i][1];
}
windowWeights.push(curSum);
for (var i = 0; i < weighted.length - windowSize; i++) {
curSum -= weighted[i][1];
curSum += weighted[i + windowSize][1];
windowWeights.push(curSum);
}
// If we didn't find the term, just pick the first window
var maxSumIndex = 0;
if (termFound) {
var maxFound = 0;
// backwards
for (var i = windowWeights.length - 1; i >= 0; i--) {
if (windowWeights[i] > maxFound) {
maxFound = windowWeights[i];
maxSumIndex = i;
}
}
}
var teaser = [];
var startIndex = weighted[maxSumIndex][2];
for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
var word = weighted[i];
if (startIndex < word[2]) {
// missing text from index to start of `word`
teaser.push(body.substring(startIndex, word[2]));
startIndex = word[2];
}
// add <em/> around search terms
if (word[1] === TERM_WEIGHT) {
teaser.push("<b>");
}
startIndex = word[2] + word[0].length;
teaser.push(body.substring(word[2], startIndex));
if (word[1] === TERM_WEIGHT) {
teaser.push("</b>");
}
}
teaser.push("…");
return teaser.join("");
}
function formatSearchResultItem(item, terms) {
var li = document.createElement("li");
li.classList.add("search-results__item");
li.innerHTML = `<a href="${item.ref}">${item.doc.title}</a>`;
li.innerHTML += `<div class="search-results__teaser">${makeTeaser(item.doc.body, terms)}</div>`;
return li;
}
// Go from the book view to the search view
function toggleSearchMode() {
var $bookContent = document.querySelector(".book-content");
var $searchContainer = document.querySelector(".search-container");
if ($searchContainer.classList.contains("search-container--is-visible")) {
$searchContainer.classList.remove("search-container--is-visible");
document.body.classList.remove("search-mode");
$bookContent.style.display = "block";
} else {
$searchContainer.classList.add("search-container--is-visible");
document.body.classList.add("search-mode");
$bookContent.style.display = "none";
document.getElementById("search").focus();
}
}
function initSearch() {
var $searchInput = document.getElementById("search");
if (!$searchInput) {
return;
}
var $searchIcon = document.querySelector(".search-icon");
$searchIcon.addEventListener("click", toggleSearchMode);
var $searchResults = document.querySelector(".search-results");
var $searchResultsHeader = document.querySelector(".search-results__header");
var $searchResultsItems = document.querySelector(".search-results__items");
var MAX_ITEMS = 10;
var options = {
bool: "AND",
fields: {
title: {boost: 2},
body: {boost: 1},
}
};
var currentTerm = "";
var index = elasticlunr.Index.load(window.searchIndex);
$searchInput.addEventListener("keyup", debounce(function() {
var term = $searchInput.value.trim();
if (term === currentTerm || !index) {
return;
}
$searchResults.style.display = term === "" ? "none" : "block";
$searchResultsItems.innerHTML = "";
if (term === "") {
return;
}
var results = index.search(term, options).filter(function (r) {
return r.doc.body !== "";
});
if (results.length === 0) {
$searchResultsHeader.innerText = `No search results for '${term}'.`;
return;
}
currentTerm = term;
$searchResultsHeader.innerText = `${results.length} search results for '${term}':`;
for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
if (!results[i].doc.body) {
continue;
}
// var item = document.createElement("li");
// item.innerHTML = formatSearchResultItem(results[i], term.split(" "));
console.log(results[i]);
$searchResultsItems.appendChild(formatSearchResultItem(results[i], term.split(" ")));
}
}, 150));
}
if (document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)
) {
initToggleMenu();
} else {
document.addEventListener("DOMContentLoaded", function () {
initToggleMenu();
initSearch();
});
}
function initToggleMenu() {
var $menu = document.querySelector(".menu");
var $menuIcon = document.querySelector(".menu-icon");
var $page = document.querySelector(".page");
$menuIcon.addEventListener("click", function () {
$menu.classList.toggle("menu-hidden");
$page.classList.toggle("page-without-menu");
});
}
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args);
}, wait);
};
}
// Taken from mdbook
// The strategy is as follows:
// First, assign a value to each word in the document:
// Words that correspond to search terms (stemmer aware): 40
// Normal words: 2
// First word in a sentence: 8
// Then use a sliding window with a constant number of words and count the
// sum of the values of the words within the window. Then use the window that got the
// maximum sum. If there are multiple maximas, then get the last one.
// Enclose the terms in <b>.
function makeTeaser(body, terms) {
var TERM_WEIGHT = 40;
var NORMAL_WORD_WEIGHT = 2;
var FIRST_WORD_WEIGHT = 8;
var TEASER_MAX_WORDS = 30;
var stemmedTerms = terms.map(function (w) {
return elasticlunr.stemmer(w.toLowerCase());
});
var termFound = false;
var index = 0;
var weighted = []; // contains elements of ["word", weight, index_in_document]
// split in sentences, then words
var sentences = body.toLowerCase().split(". ");
for (var i in sentences) {
var words = sentences[i].split(" ");
var value = FIRST_WORD_WEIGHT;
for (var j in words) {
var word = words[j];
if (word.length > 0) {
for (var k in stemmedTerms) {
if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
value = TERM_WEIGHT;
termFound = true;
}
}
weighted.push([word, value, index]);
value = NORMAL_WORD_WEIGHT;
}
index += word.length;
index += 1; // ' ' or '.' if last word in sentence
}
index += 1; // because we split at a two-char boundary '. '
}
if (weighted.length === 0) {
return body;
}
var windowWeights = [];
var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
// We add a window with all the weights first
var curSum = 0;
for (var i = 0; i < windowSize; i++) {
curSum += weighted[i][1];
}
windowWeights.push(curSum);
for (var i = 0; i < weighted.length - windowSize; i++) {
curSum -= weighted[i][1];
curSum += weighted[i + windowSize][1];
windowWeights.push(curSum);
}
// If we didn't find the term, just pick the first window
var maxSumIndex = 0;
if (termFound) {
var maxFound = 0;
// backwards
for (var i = windowWeights.length - 1; i >= 0; i--) {
if (windowWeights[i] > maxFound) {
maxFound = windowWeights[i];
maxSumIndex = i;
}
}
}
var teaser = [];
var startIndex = weighted[maxSumIndex][2];
for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
var word = weighted[i];
if (startIndex < word[2]) {
// missing text from index to start of `word`
teaser.push(body.substring(startIndex, word[2]));
startIndex = word[2];
}
// add <em/> around search terms
if (word[1] === TERM_WEIGHT) {
teaser.push("<b>");
}
startIndex = word[2] + word[0].length;
teaser.push(body.substring(word[2], startIndex));
if (word[1] === TERM_WEIGHT) {
teaser.push("</b>");
}
}
teaser.push("…");
return teaser.join("");
}
function formatSearchResultItem(item, terms) {
var li = document.createElement("li");
li.classList.add("search-results__item");
li.innerHTML = "<a href=\"" + item.ref + "\">" + item.doc.title + "</a>";
li.innerHTML += "<div class=\"search-results__teaser\">" + makeTeaser(item.doc.body, terms) + "</div>";
return li;
}
// Go from the book view to the search view
function toggleSearchMode() {
var $bookContent = document.querySelector(".book-content");
var $searchContainer = document.querySelector(".search-container");
if ($searchContainer.classList.contains("search-container--is-visible")) {
$searchContainer.classList.remove("search-container--is-visible");
document.body.classList.remove("search-mode");
$bookContent.style.display = "block";
}
else {
$searchContainer.classList.add("search-container--is-visible");
document.body.classList.add("search-mode");
$bookContent.style.display = "none";
document.getElementById("search").focus();
}
}
function initSearch() {
var $searchInput = document.getElementById("search");
if (!$searchInput) {
return;
}
var $searchIcon = document.querySelector(".search-icon");
$searchIcon.addEventListener("click", toggleSearchMode);
var $searchResults = document.querySelector(".search-results");
var $searchResultsHeader = document.querySelector(".search-results__header");
var $searchResultsItems = document.querySelector(".search-results__items");
var MAX_ITEMS = 10;
var options = {
bool: "AND",
fields: {
title: { boost: 2 },
body: { boost: 1 }
}
};
var currentTerm = "";
var index = elasticlunr.Index.load(window.searchIndex);
$searchInput.addEventListener("keyup", debounce(function () {
var term = $searchInput.value.trim();
if (term === currentTerm || !index) {
return;
}
$searchResults.style.display = term === "" ? "none" : "block";
$searchResultsItems.innerHTML = "";
if (term === "") {
return;
}
var results = index.search(term, options).filter(function (r) {
return r.doc.body !== "";
});
if (results.length === 0) {
$searchResultsHeader.innerText = "No search results for '" + term + "'.";
return;
}
currentTerm = term;
$searchResultsHeader.innerText = results.length + " search results for '" + term + "':";
for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
if (!results[i].doc.body) {
continue;
}
// var item = document.createElement("li");
// item.innerHTML = formatSearchResultItem(results[i], term.split(" "));
console.log(results[i]);
$searchResultsItems.appendChild(formatSearchResultItem(results[i], term.split(" ")));
}
}, 150));
}
if (document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)) {
initToggleMenu();
}
else {
document.addEventListener("DOMContentLoaded", function () {
initToggleMenu();
initSearch();
});
}
+6 -1
View File
@@ -8,5 +8,10 @@ $(document).ready(function() {
$(".back-button")
.html("Go Back")
.click(_ => window.history.back());
.click(function(_) { window.history.back()});
if(navigator.userAgent.indexOf("Trident") > -1) {
console.log(navigator.userAgent);
$(".search-icon").css("display", "none");
}
});
+6
View File
@@ -22,7 +22,9 @@ namespace mbts.ui {
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
DiskGeometryKey = nameof(DiskGeometryKey),
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
DocumentGeometryKey = nameof(DocumentGeometryKey),
@@ -39,6 +41,7 @@ namespace mbts.ui {
PaymentGeometryKey = nameof(PaymentGeometryKey),
PencilGeometryKey = nameof(PencilGeometryKey),
PlayGeometryKey = nameof(PlayGeometryKey),
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
StarGeometryKey = nameof(StarGeometryKey),
ToolsGeometryKey = nameof(ToolsGeometryKey),
TrashGeometryKey = nameof(TrashGeometryKey),
@@ -115,6 +118,7 @@ namespace mbts.ui {
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
public static ImageSource AttachAlgos => create_icon(AttachAlgosGeometryKey);
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
@@ -139,6 +143,7 @@ namespace mbts.ui {
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
public static ImageSource Disk => create_icon(DiskGeometryKey);
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
@@ -158,6 +163,7 @@ namespace mbts.ui {
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
public static ImageSource Star => create_icon(StarGeometryKey);
+63
View File
@@ -40,6 +40,69 @@
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Unlink free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
</sys:String>
<sys:String>
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Disconnected chains free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
</sys:String>
<sys:String>
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
C164.311,139.575,168.644,142.574,173.171,141.769z
</sys:String>
<sys:String>
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
C34.717,189.582,39.058,192.589,43.561,191.776z
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/chanut
Ttile: Cancel free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
C59.518,47.514,47.514,59.518,32.759,59.518z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/smashicons
Ttile: File free icon
@@ -16,7 +16,7 @@ using System.Windows;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
const string _window_type = "MarketView", _window_type_display_name = "Market View";
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -32,6 +32,7 @@ namespace mbts.spark {
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
}
event Action<MarketViewWindowFactory.StartupData> _state_changed;
@@ -47,7 +48,7 @@ namespace mbts.spark {
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id => _thread_id;
public string thread_id { get; }
public void thread_init() {
_init_guard.check_access();
@@ -19,7 +19,7 @@ using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
@@ -48,7 +48,7 @@ namespace mbts.spark {
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public override StartupData SaveState => _state = _vm.value.create();
public override void Dispose() =>
@@ -20,6 +20,8 @@
<sys:Double x:Key="RowHeight">16</sys:Double>
<sys:Double x:Key="NegativeRowHeight">-16</sys:Double>
<sys:Double x:Key="ItemsHostCanBeDisabledCollapsedHeight">15</sys:Double>
<sys:Double x:Key="HeaderPanelItemSeparation">6</sys:Double>
<Thickness x:Key="Margin.LHeaderPanelItem" Left="{StaticResource HeaderPanelItemSeparation}"/>
<Thickness
x:Key="GroupLevelMargin"
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
@@ -100,7 +102,7 @@
</usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
<usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
<DataTemplate>
<c:TickUpDownDouble Value="{Binding value}" IsEnabled="{Binding enabled}" Increment="{Binding tick_increment, FallbackValue={u:Double 1}}"/>
<c:TickUpDownDouble Value="{Binding value}" IsEnabled="{Binding enabled}" Increment="{Binding tick_increment, FallbackValue={u:Double 1}}" FlushTextImmediately="False"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.DoubleTemplate>
<usp:DataGridParameterCellTemplateConverter.IntTemplate>
@@ -369,16 +371,21 @@
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
<TextBox
x:Name="PART_Name_TextBox" Grid.Column="1" Margin="6,0,0,0" HorizontalAlignment="Stretch" IsEnabled="{TemplateBinding IsNew}"
x:Name="PART_Name_TextBox"
Grid.Column="1"
Margin="{StaticResource Margin.LHeaderPanelItem}"
MinWidth="80"
HorizontalAlignment="Stretch"
IsEnabled="{TemplateBinding IsNew}"
Text="{Binding SyntheticName, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}"
>
<u:Behavior.BindingUpdateGroups>
@@ -390,18 +397,22 @@
</u:Behavior.BindingUpdateGroups>
</TextBox>
<TextBlock
Grid.Column="2" VerticalAlignment="Center" Margin="6,0,0,0" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
Grid.Column="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:TickFactorsPicker
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="6,0,0,0">Pricing</TextBlock>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" >Pricing</TextBlock>
<ComboBox
Grid.Column="5" Margin="6,0,0,0" IsEnabled="False"
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}" SelectedItem="{Binding PriceCalc, RelativeSource={RelativeSource TemplatedParent}}"
Grid.Column="5"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Width="90"
IsEnabled="{TemplateBinding IsNew}"
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}"
SelectedItem="{Binding price_calc}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -409,7 +420,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<c:HelpButton Grid.Column="6" Margin="3,0,0,0"/>
<c:HelpButton Grid.Column="8" Margin="3,0,0,0" ManPage="bts_spark/spreader/#creating-a-synthetic" />
</Grid>
<c:DataGridEx
x:Name="PART_DataGrid"
@@ -99,7 +99,7 @@ namespace mbts.ui.spark {
#region PriceCalc
public static readonly DependencyProperty PriceCalcProperty = DependencyProperty.Register("PriceCalc", typeof(SyntheticContract.Types.PriceCalc), typeof(ExecutionStrategyEditor),
new FrameworkPropertyMetadata(SyntheticContract.Types.PriceCalc.Diff, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
new FrameworkPropertyMetadata(SyntheticContract.Types.PriceCalc.Standard, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public SyntheticContract.Types.PriceCalc PriceCalc { get => (SyntheticContract.Types.PriceCalc)GetValue(PriceCalcProperty); set => SetValue(PriceCalcProperty, value); }
public static readonly IEnumerable<SyntheticContract.Types.PriceCalc> PriceCalcItems = EnumHelper.get_values<SyntheticContract.Types.PriceCalc>();
@@ -315,7 +315,6 @@ namespace mbts.ui.spark {
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 if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
else return null;
}
@@ -16,7 +16,7 @@ using System.Windows;
using static bts.shell.SyntheticManagementWindowFactory;
namespace mbts.spark {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
const string _window_type = "SyntheticManagement";
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
StartupData _state;
@@ -33,8 +33,20 @@ namespace mbts.spark {
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public void thread_init() {
Log.notnull(_state);
@@ -101,16 +113,6 @@ namespace mbts.spark {
else _view.DataContext = _vm.value;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
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);
Commands.OfType<IDisposable>().DisposeAll();
@@ -1,18 +0,0 @@
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;
}
}
}
@@ -26,6 +26,7 @@ namespace mbts.spark {
readonly PriceMultiplierModel _price_mult_model;
readonly List<IDisposable> _resources = new List<IDisposable>();
readonly Property<bool> _has_working_orders, _can_add_leg;
Security _synthetic;
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
TickFactors _tick_factors;
@@ -207,7 +208,6 @@ namespace mbts.spark {
//new MinLeanQtyModel(), //TAHA_SPREADER
//new QuoteHiddenModel(),
new MarketDataTypeModel(),
new MarketDataCalculationModel(),
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
quote_throttle_outside_ticks,
@@ -291,7 +291,9 @@ namespace mbts.spark {
public ExecutionStrategyImmutable get_strategy() {
var mutable_strategy = _strategy.CloneToMutable();
mutable_strategy.LegParamsMap.Clear();
foreach (var lt in leg_tuples) mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
foreach (var lt in leg_tuples) {
mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
}
return new ExecutionStrategyImmutable(mutable_strategy);
}
@@ -19,8 +19,6 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
@@ -559,71 +557,14 @@
</Grid>
</RadioButton>
</Grid>
<GroupBox
x:Name="content_host"
<usp:AlgoEditorView
Grid.Row="2"
DataContext="{Binding content_source}"
Background="Transparent"
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
Grid.Column="3"
ToolTipService.ShowOnDisabled="True"
SelectedItem="{Binding selected_definition.algo_root}"
ItemsSource="{Binding selected_definition.algos_view}"
Margin="{StaticResource Margin.LP1}"
Width="140"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding selected_definition.algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
/>
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
<c:DialogSkirt.RightItems>
<x:ArrayExtension Type="{x:Type Button}">
@@ -1,8 +1,6 @@
using mbts.spark;
using mbts.ui.converters;
using mbts.ui.extensions;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
@@ -18,26 +16,11 @@ namespace mbts.ui.spark {
public AlgoStudioView() {
InitializeComponent();
DataContextChanged += (s, e) => set_controller();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
set_controller();
// local methods
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
public new AlgoStudioVM DataContext {
get => base.DataContext as AlgoStudioVM;
set => base.DataContext = value;
@@ -13,7 +13,7 @@ using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
namespace mbts.spark {
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
public const string
NAME = "Algo Studio",
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
@@ -37,7 +37,7 @@ namespace mbts.spark {
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
_model = model;
_state = state;
thread_init();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
}
public override StartupData SaveState => _state = _vm.create();
@@ -45,12 +45,12 @@ namespace mbts.spark {
public override string window_type => NAME;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public string thread_id => NAME;
public override double MinWidth =>
SPARK_MENU_COLLAPSED_WIDTH +
LAUNCHER_BUTTON_BORDER_WIDTH +
DEFAULT_CONTENT_WIDTH;
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
public string thread_id { get; }
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
@@ -10,7 +10,7 @@ namespace mbts.spark {
public readonly AlgoKey key;
public AlgoData(CustomOrderDefinitionImmutable value)
: this(value, value.CustomCase.display_name(), new AlgoKey(value.CustomCase)) { }
: this(value, value.display_name(), OrderDataHelper.get_shallow_factory_key(value)) { }
public AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
this.value = value;
@@ -1,8 +1,9 @@
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using ProtoBuf;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
@@ -10,12 +11,6 @@ namespace mbts.spark {
public readonly string name;
public readonly AlgoStructureKey structure;
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
: this(definition.CustomCase.type_id(), definition) { }
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
: this(name, new AlgoStructureKey(definition)) { }
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
@@ -31,31 +26,22 @@ namespace mbts.spark {
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
}
[ProtoContract]
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
[ProtoMember(1)]
readonly int[] _structure;
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(new[] { (int)base_type }) { }
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
int[] s = new[] { (int)definition.CustomCase };
_build_structure(definition, ref s, 1);
_structure = s;
}
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
public AlgoStructureKey(int[] structure) =>
_structure = structure;
public CustomOrderDefinitionImmutable build_def() {
int pos = 0;
var mdef = new CustomOrderDefinition();
_build_def(_structure, ref pos, ref mdef);
return new CustomOrderDefinitionImmutable(mdef);
}
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
public bool Equals(AlgoStructureKey other) =>
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
_structure.Length == other._structure.Length &&
_structure.SequenceEqual(other._structure);
@@ -67,38 +53,108 @@ namespace mbts.spark {
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
}
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
_build_structure(definition.CloneToMutable(), ref structure, pos);
static class AlgoStructureSerializer {
const int
_dynamic_mask = 1 << 31,
_child_mask_offset = 30;
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
var cad = OrderDataHelper.get_child_algo_data(algo.CustomCase);
for (int di = 0; di < cad.Count; di++) {
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
int del_mask = 1 << (31 - di);
structure[pos++] = (int)child_algo.CustomCase | del_mask;
_build_structure(child_algo, ref structure, pos);
break;
}
}
ArrayHelper.trim(ref structure, pos);
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
to_array(algo.CloneToMutable(), dynamic_type_index);
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
int[] s = new[] { (int)algo.CustomCase };
_build_structure(algo, ref s, dynamic_type_index);
return s;
}
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
for (int i = pos; i < structure.Length; i++) {
var cad = OrderDataHelper.get_child_algo_data(algo.CustomCase);
for (int di = 0; di < cad.Count; di++) {
int mask = 1 << (31 - di);
if ((structure[i] & mask) == mask) {
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
pos++;
_build_def(structure, ref pos, ref child_algo);
cad[di].set_child(algo, child_algo);
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
parsei(structure, dynamic_helper);
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
if (structure == null || structure.Count == 0)
return new CustomOrderDefinition();
int pos = 1;
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
_build_def(structure, ref pos, ref mdef, dynamic_helper);
return mdef;
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
if (structure == null || structure.Length == 0)
structure = new int[1];
structure[0] = (int)algo.CustomCase;
int p = 1;
_build_structure(algo, ref structure, ref p, dynamic_type_index);
ArrayHelper.trim(ref structure, p);
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
} else {
var cad = OrderDataHelper.get_child_algo_data(algo);
children = cad.Select((c, i) => (c.get_child(algo), i));
children = children.Where(c => c.child_algo != null);
}
foreach ((var child, int field_pos) in children) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
_build_structure(child, ref structure, ref pos, dynamic_type_index);
}
}
/// <summary>
/// Gets a mask for a particular child-algo field of a particular algo definition.
/// </summary>
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
var local_algo = algo;
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
var dyn_def = algo.Dynamic;
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
dynamic_helper.add_dynamic_params(ref dyn_def);
children = dyn_def.OrderParams
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
} else {
children = OrderDataHelper.get_child_algo_data(algo)
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
}
using (var children_en = children.GetEnumerator()) {
while (pos < structure.Count && children_en.MoveNext()) {
(var assign_child, int field_pos) = children_en.Current;
int mask = _child_algo_mask(field_pos);
if ((structure[pos] & mask) == mask) {
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
var child_algo = OrderDataHelper.create_algo(type);
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
assign_child(child_algo);
}
}
}
}
}
}
}
@@ -46,7 +46,8 @@ namespace mbts.spark {
readonly DeferrableActionWrapper _refresh_on_local_changes;
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
readonly Property<AlgoFormats> _selected_format;
AlgoEditingPermissions _editing_permissions;
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
int? _favorite_index;
Brush _badge_fill;
Snapshot? _committed;
@@ -54,29 +55,43 @@ namespace mbts.spark {
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
bool _connected;
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
var rv = new AlgoDefinitionVM(model, data, init_format);
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
if (connect)
rv.connect();
return rv;
}
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
// init core data
_model = model;
_permissions_mask.mask = init_permissions;
_permissions_mask.raw = init_permissions;
var resources = new CompositeDisposable();
if (!model.algo_model.ui_configuration.can_modify_definition_values)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
else if (model.algo_model.templates_enabled()) {
resources.Add(_templates.Subscribe((s, e) => {
if (_templates.Count == 0) {
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
} else {
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
}
}));
}
key = def_data.key;
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
_editing_permissions = _get_permissions();
_version = new VersionContainer<Snapshot>(_snapshot);
var conf = _model.algo_model.ui_configuration;
var resources = new CompositeDisposable();
// init backing props
_name = backing(nameof(name), def_data.name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
@@ -87,7 +102,7 @@ namespace mbts.spark {
_is_selected = backing(nameof(is_selected), true);
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
@@ -113,7 +128,6 @@ namespace mbts.spark {
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
if (vm == null)
return Disposable.empty;
@@ -152,16 +166,22 @@ namespace mbts.spark {
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
break;
}
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(f);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}));
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeHookLatest(t => {
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(t.fmt);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}
return Disposable.empty;
}));
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
resources.Add(this.connect_data(permission_src));
resources.Add(this.connect_data(Observable.Return(model)));
_dispose = resources;
on_editing_permissions(_get_permissions());
await algo_root.load_from_async(def_data.value);
_version.increment_version();
_edit_stack.reset(_version.value);
@@ -169,11 +189,6 @@ namespace mbts.spark {
}
}
// local methods
void on_editing_permissions(AlgoEditingPermissions eps) {
_editing_permissions = eps;
foreach (var vm in algo_roots)
vm.set_permissions(eps);
}
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
void on_local_changed() {
@@ -230,6 +245,9 @@ namespace mbts.spark {
// visual states
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
// editing permissions
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
#region IAlgoSelector
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
@@ -248,7 +266,7 @@ namespace mbts.spark {
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
var orig_algo = algo_root?.id;
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
if (!try_select(orig_algo) && !try_select(default_algo))
algo_root = algo_roots.FirstOrDefault();
//
@@ -298,6 +316,19 @@ namespace mbts.spark {
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
public void load_def(CustomOrderDefinitionImmutable def) {
var key = OrderDataHelper.get_shallow_factory_key(def);
if (algo_root?.id == key) {
algo_root.load_from(def);
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
vm.load_from(def);
algo_root = vm;
} else {
algo_roots.Add(vm = _mk_algo_root(def));
algo_root = vm;
}
}
public void load(AlgoData data) {
_assert_can_load(data.key);
if (algo_root != null)
@@ -309,7 +340,7 @@ namespace mbts.spark {
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
public AlgoDefinitionVM to_persisted(AlgoData def) =>
new AlgoDefinitionVM(_model, def);
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
public AlgoData create_algo_def() =>
new AlgoData(algo_root.definition, name, key);
@@ -322,16 +353,6 @@ namespace mbts.spark {
public override int GetHashCode() => key.GetHashCode();
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
AlgoEditingPermissions _get_permissions() {
var permissions = AlgoEditingPermissions.None;
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
}
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
return permissions;
}
void _assert_can_load(AlgoKey data_key) {
if (key.mode != data_key.mode)
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
@@ -362,7 +383,7 @@ namespace mbts.spark {
}
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
void _on_metadata(AlgoDefinitionMetadata metadata) {
using (_refresh_on_local_changes.supress_execution()) {
@@ -0,0 +1,119 @@
<UserControl
x:Class="mbts.ui.spark.AlgoEditorView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
<sys:Double x:Key="VerticalPush1">4</sys:Double>
<sys:Double x:Key="VerticalPush2">8</sys:Double>
<sys:Double x:Key="VerticalPush3">12</sys:Double>
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
<!--Converters-->
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
<!--Default Styles-->
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
<Setter Property="Padding" Value="8"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
</Style>
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<GroupBox
x:Name="content_host"
Grid.Row="2"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
ToolTipService.ShowOnDisabled="True"
Width="140"
Margin="{StaticResource Margin.LP1}"
ItemsSource="{Binding algos_view}"
SelectedItem="{Binding algo_root}"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.IsEnabled>
<Binding Path="editing_permissions">
<Binding.Converter>
<cv:EnumHasFlagsConverter>
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
</cv:EnumHasFlagsConverter>
</Binding.Converter>
</Binding>
</ComboBox.IsEnabled>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
</UserControl>
@@ -0,0 +1,28 @@
using mbts.ui.extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorView : UserControl {
public AlgoEditorView() {
InitializeComponent();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
// local methods
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
}
}
@@ -14,10 +14,13 @@ using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
@@ -42,7 +45,7 @@ namespace mbts.spark {
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<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
@@ -98,16 +101,17 @@ namespace mbts.spark {
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
// 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());
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
//
_dispose = Disposable.Create(
_dispose = new CompositeDisposable(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
fills_source.OnDispatcher().Subscribe(t => {
_rows.update(t);
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null) {
if (s.grid != null && _settings_manager != 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;
@@ -120,8 +124,8 @@ namespace mbts.spark {
};
rows_manager.on_settings(fs, s.sorting_criterion);
}),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
fills_source.Connect()
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -153,12 +157,12 @@ namespace mbts.spark {
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
_sort_manager.create();
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
static Columns _col_key(ColumnBase column) =>
((IKeyedColumn<Columns>)column).key;
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
public FilterStates get_filters() =>
_filters_manager.create();
@@ -0,0 +1,49 @@
using mbts.ui;
using mbts.ui.controls;
using System;
namespace mbts.spark {
/// <summary>
/// This class provides a stable DataContext for a fills view, with an internal <see cref="FillsVM"/> that may be disposed and recreated as needed.
/// </summary>
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
bool _disposed;
FillsVM _value;
public event Action<bool> OnDisposed;
public FillsVM value {
get => _value;
set {
if (value != _value) {
if (_disposed)
throw new ObjectDisposedException(nameof(FillsVMContainer));
Disposable.replace(ref _value, value);
if (_value != null) {
rows.add_collection(_value.rows);
columns.add_collection(_value.columns);
} else {
rows.clear_collections();
columns.clear_collections();
}
}
}
}
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
public ObservableAggregateCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableAggregateCollection<DIYGrid.ColumnBase>();
public void Dispose() {
if (_disposed)
throw new ObjectDisposedException(nameof(FillsVMContainer));
value = null;
OnDisposed?.Invoke(true);
_disposed = true;
}
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
if (!_disposed)
_value?.on_sorting(sender, e);
}
}
}
@@ -18,7 +18,6 @@ using StartupData = bts.shell.FillsWindowFactory.StartupData;
namespace mbts.spark {
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
public const string window_type_name = "Fills";
const string _thread_id = "Fills";
readonly AllowOnce _init_guard = new AllowOnce("Fills");
readonly OrdersModel _orders_model;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -33,6 +32,7 @@ namespace mbts.spark {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsWindowFactory.StartupData> _state_changed;
@@ -48,7 +48,7 @@ namespace mbts.spark {
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id => _thread_id;
public string thread_id { get; }
public void thread_init() {
_init_guard.check_access();
@@ -33,7 +33,7 @@ namespace bts.shell {
_settings_hub = settings_hub;
_orders_model = orders_model;
_md_auxiliary_model = md_auxiliary_model;
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled);
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
@@ -95,7 +95,7 @@
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
@@ -20,7 +20,7 @@ namespace mbts.spark {
}
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public MiniOrderBookRowView() : base() { }
}
}
@@ -21,14 +21,13 @@ using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using static bts.shell.OrderBookWindowFactory;
using static mbts.spark.OrderBookView;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
string sort_member_path { get; }
ListSortDirection? SortDirection { get; set; }
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
IComparer<OrderBookRowVM> comparer { get; }
@@ -78,7 +77,6 @@ namespace mbts.spark {
Add(symbol_column = new SimpleTextColumn<string[]>(
Columns.Symbol,
DefaultColumnHeaders.Symbol,
nameof(OrderBookRowVM.symbol),
r => r.symbol,
DefaultColumnWidths.Symbol,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
@@ -92,7 +90,6 @@ namespace mbts.spark {
Add(time_created_column = new SimpleTextColumn<TimeFilterTuple>(
Columns.Time,
"Time",
nameof(OrderBookRowVM.time_created),
r => r.time_created_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
@@ -104,7 +101,6 @@ namespace mbts.spark {
Add(last_change_column = new SimpleTextColumn<TimeFilterTuple>(
Columns.LastChange,
"Last Change",
nameof(OrderBookRowVM.last_change),
r => r.last_change_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
@@ -116,7 +112,6 @@ namespace mbts.spark {
Add(user_column = new SimpleTextColumn<string[]>(
Columns.User,
DefaultColumnHeaders.User,
nameof(OrderBookRowVM.user),
r => r.user,
DefaultColumnWidths.User,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
@@ -129,7 +124,6 @@ namespace mbts.spark {
Add(account_column = new SimpleTextColumn<string[]>(
Columns.Account,
DefaultColumnHeaders.Account,
nameof(OrderBookRowVM.account),
r => r.account,
DefaultColumnWidths.Account,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
@@ -144,7 +138,6 @@ namespace mbts.spark {
Add(exchange_column = new SimpleTextColumn<Venue[]>(
Columns.Exchange,
DefaultColumnHeaders.Exchange,
nameof(OrderBookRowVM.exchange),
r => r.exchange.ToString(),
DefaultColumnWidths.Exchange,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
@@ -157,7 +150,6 @@ namespace mbts.spark {
Add(time_in_force_column = new SimpleTextColumn<TimeInForce[]>(
Columns.TimeInForce,
DefaultColumnHeaders.TimeInForce,
nameof(OrderBookRowVM.time_in_force),
r => r.time_in_force.ToString(),
DefaultColumnWidths.TimeInForce,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
@@ -176,14 +168,13 @@ namespace mbts.spark {
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
Columns.SubType,
"Sub Type",
nameof(OrderBookRowVM.order_sub_type),
r => r.order_sub_type_text,
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
EnumHelper.get_values<OrderSubType>().ToList(),
ort => ort.order.sub_type(),
nameof(OrderBookRowVM.order_sub_type)),
SubTypeFilterTemplateKey,
OrderBookGridView.SubTypeFilterTemplateKey,
_wrap_comparer(r => r.order_sub_type)));
// side
Add(side_column = new SideColumn(
@@ -205,7 +196,6 @@ namespace mbts.spark {
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.Price,
"Price",
nameof(OrderBookRowVM.price),
r => r.price_text,
DefaultColumnWidths.Price,
new DoubleMinMax<OrderSecurityAlgoMeta>(
@@ -221,7 +211,6 @@ namespace mbts.spark {
Add(avg_price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.AvgFilled,
"Avg Price",
nameof(OrderBookRowVM.avg_price),
r => r.avg_price_text,
DefaultColumnWidths.Price,
new DoubleMinMax<OrderSecurityAlgoMeta>(
@@ -233,7 +222,6 @@ namespace mbts.spark {
Add(last_price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.LastPrice,
"Last Filled",
nameof(OrderBookRowVM.last_price),
r => r.last_price_text,
DefaultColumnWidths.Price,
new DoubleMinMax<OrderSecurityAlgoMeta>(
@@ -245,7 +233,6 @@ namespace mbts.spark {
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
Columns.Status,
"Status",
nameof(OrderBookRowVM.status),
r => r.status.ToString(),
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
@@ -263,7 +250,6 @@ namespace mbts.spark {
Add(security_key_column = new SimpleTextColumn<string>(
Columns.SecurityKey,
DefaultColumnHeaders.SecurityKey,
nameof(OrderBookRowVM.security_key),
r => r.security_key.ToString(),
DefaultColumnWidths.SecurityKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -275,7 +261,6 @@ namespace mbts.spark {
Add(order_key_column = new SimpleTextColumn<string>(
Columns.OrderKey,
DefaultColumnHeaders.OrderKey,
nameof(OrderBookRowVM.order_key),
r => r.order_key.ToString(),
DefaultColumnWidths.OrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -287,7 +272,6 @@ namespace mbts.spark {
Add(parent_key_column = new SimpleTextColumn<string>(
Columns.ParentKey,
"Parent Key",
nameof(OrderBookRowVM.parent_order_key),
r => r.parent_order_key.ToString(),
DefaultColumnWidths.ParentKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -295,6 +279,17 @@ namespace mbts.spark {
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
_wrap_comparer(r => r.parent_order_key)));
// attached target order key
Add(attached_target_order_key_column = new SimpleTextColumn<string>(
Columns.AttachedTargetOrderKey,
DefaultColumnHeaders.AttachedTargetOrderKey,
r => r.order.attached_target_order_key().ToString(),
DefaultColumnWidths.AttachedTargetOrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
ort => ort.order.attached_target_order_key().ToString(),
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
_wrap_comparer(r => r.order.attached_target_order_key())));
}
public event Action filter_changed;
@@ -323,10 +318,13 @@ namespace mbts.spark {
public SimpleTextColumn<string> security_key_column { get; }
public SimpleTextColumn<string> order_key_column { get; }
public SimpleTextColumn<string> parent_key_column { get; }
public SimpleTextColumn<string> attached_target_order_key_column { get; }
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() => (
new FilterStates {
order_key_regex = order_key_column.filter.get_state(),
parent_key_regex = parent_key_column.filter.get_state(),
attached_target_order_key_regex = attached_target_order_key_column.filter.get_state(),
time_created = time_created_column.filter.get_state(),
users = user_column.filter.get_state(),
accounts = account_column.filter.get_state(),
@@ -364,6 +362,8 @@ namespace mbts.spark {
time_in_force_column.filter.load_state(fs.tifs);
exchange_column.filter.load_state(fs.exchanges);
order_key_column.filter.load_state(fs.order_key_regex);
parent_key_column.filter.load_state(fs.parent_key_regex);
attached_target_order_key_column.filter.load_state(fs.attached_target_order_key_regex);
avg_price_column.filter.load_state(fs.avg_price);
last_price_column.filter.load_state(fs.last_price);
status_column.filter.load_state(fs.statuses);
@@ -480,13 +480,12 @@ namespace mbts.spark {
protected readonly Func<OrderBookRowVM, string> _get_text;
GlyphContext _root_glyph_context, _child_glyph_context;
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, string sort_prop, double width,
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
id = key.ToString();
Width = width;
Header = header;
_get_text = get_text;
sort_member_path = sort_prop;
this.filter = filter;
_filter_template = filter_template;
_cached_sort_direction = SortDirection;
@@ -515,9 +514,9 @@ namespace mbts.spark {
}
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, string> get_text, double width,
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
: base(key, header, get_text, sort_prop, width, filter, filter_template, comparer) { }
: base(key, header, get_text, width, filter, filter_template, comparer) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
@@ -527,7 +526,7 @@ namespace mbts.spark {
#region Explicit Column Defs
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
: base(Columns.Side, "B/S", r => r.side.ToString(), nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
@@ -536,9 +535,7 @@ namespace mbts.spark {
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
: base(Columns.Size, "Qty", r => r.quantity.ToString(), nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) {
sort_member_path = nameof(OrderBookRowVM.quantity);
}
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
@@ -547,9 +544,7 @@ namespace mbts.spark {
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) {
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
}
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
@@ -562,7 +557,6 @@ namespace mbts.spark {
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
id = key.ToString();
this.filter = filter;
sort_member_path = nameof(OrderBookRowVM.order_type);
font_weight_binding = RenderUtils.get_font_weight_binding();
_cached_sort_direction = SortDirection;
}
@@ -587,8 +581,7 @@ namespace mbts.spark {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = DIYGrid.DefaultColumnWidths.LastMessage;
sort_member_path = nameof(OrderBookRowVM.last_msg);
Width = DefaultColumnWidths.LastMessage;
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
@@ -616,7 +609,6 @@ namespace mbts.spark {
this.filter = filter;
Header = DefaultColumnHeaders.Security;
Width = DefaultColumnWidths.Security;
sort_member_path = nameof(OrderBookRowVM.security);
_cached_sort_direction = SortDirection;
}
@@ -634,7 +626,7 @@ namespace mbts.spark {
toggle_button.SetBinding(ToggleButton.IsCheckedProperty, new Binding(nameof(OrderBookRowVM.is_expanded)));
toggle_button.SetBinding(ToggleButton.MarginProperty, new Binding(nameof(OrderBookRowVM.depth_level)) { Converter = _margin_converter });
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
toggle_button.SetResourceReference(ToggleButton.StyleProperty, OrderBookGridView.ExpandCollapseToggleStyleKey);
//
var security_view = new TextBlock { Foreground = font_brush };
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
@@ -651,6 +643,7 @@ namespace mbts.spark {
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.Children.Add(toggle_button);
grid.Children.Add(security_view);
grid.VerticalAlignment = VerticalAlignment.Center;
//
var border = new Border { Child = grid };
@@ -13,7 +13,7 @@ using System.Windows.Input;
using System.Windows.Media;
namespace mbts.spark {
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM {
const string _unknown_security_price_format_string = "0.#########";
const int _unknown_security_price_num_fractional_digits = 9;
readonly OrderBookVM _owner;
@@ -50,6 +50,7 @@ namespace mbts.spark {
}
public event Action invalidated;
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
// Core
public OrderSecurityAlgoMeta order_tuple { get; private set; }
@@ -127,8 +128,8 @@ namespace mbts.spark {
public ICommand toggle_command => _toggle_expanded_cmd ?? (
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
ICommand DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd ??
(_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
public ICommand show_order_modify_cmd =>
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
public ICommand cancel_command => _cancel_cmd ??
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working));
@@ -170,10 +171,10 @@ namespace mbts.spark {
public void update(OrderSecurityAlgoMeta order_tuple) {
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
bool had_security = _security.HasValue;
var old_meta = this.order_tuple.meta;
var old_order_tuple = this.order_tuple;
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
OrderRecordImmutable old_order = order;
OrderRecordImmutable old_order = old_order_tuple.order;
this.order_tuple = order_tuple;
// most cells are "render-cells," which don't use databindings - the only way to update such cells is to raise the invalidated event
bool invalidate = !(
@@ -200,10 +201,12 @@ namespace mbts.spark {
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
invalidate = true;
}
if (old_meta?.badge_color != order_tuple.meta?.badge_color) {
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
_badge_fill = null;
RaisePropertyChanged(nameof(badge_fill));
}
if (old_order_tuple.display_name() != order_tuple.display_name())
RaisePropertyChanged(nameof(display_name));
if (old_order.status() != order.status()) {
invalidate = true;
RaisePropertyChanged(nameof(status));
@@ -213,6 +216,7 @@ namespace mbts.spark {
}
if (could_modify_execute.HasValue && could_modify_execute.Value != _show_order_modify_cmd.CanExecute(null))
_show_order_modify_cmd.RaiseCanExecuteChanged();
updated?.Invoke((old_order_tuple, order_tuple));
if (invalidate)
raise_invalidated();
}
@@ -1,9 +1,12 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
@@ -11,63 +14,149 @@ using mbts.ui.shell;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using static bts.shell.OrderBookWindowFactory;
using static mbts.spark.OrderBookView;
using _GridSettingsManager =
using _GridSettingsManager =
mbts.ui.GridSettingsManager<
mbts.spark.OrderBookRowVM,
bts.api.orderman.OrderSecurity,
bts.shell.INotificationServiceDataCache,
mbts.OrderRecordTuplePredicate,
mbts.spark.OrderBookRowVM,
bts.api.orderman.OrderSecurity,
bts.shell.INotificationServiceDataCache,
mbts.OrderRecordTuplePredicate,
bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace mbts.spark {
partial class OrderBookVM : ViewModelBase, IDisposable, IHandleRowSelection, IHandleGridSorting, IWillLetYouKnowWhenIDispose {
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting {
const double
_min_visible_right_panel_width = 200,
_min_visible_fills_slave_grid_height = 100;
readonly IBus _bus;
readonly OrdersModel _model;
readonly Property<OrderBookRowVM> _current_row;
readonly Property<bool> _fills_slave_grid_is_visible;
readonly OrderBookRowCollection _row_collection;
readonly ObservableCollection<OrderBookRowVM> _selected_rows = new ObservableCollection<OrderBookRowVM>();
readonly OrderBookColumnCollection _columns;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly List<IDisposable> _resources;
readonly SelectedAlgosMonitor _selected_algo_mon;
double
_raw_right_panel_width = OrderBookStartupData.Default.right_panel_width,
_raw_fills_slave_grid_height = OrderBookStartupData.Default.fills_slave_grid_height;
OrderBookStartupData _state;
bool _is_row_collection_populated;
bool
_algo_editor_is_visible = OrderBookStartupData.Default.algo_editor_is_visible,
_is_attaching_algos,
_is_row_collection_populated;
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false) {
_bus = bus;
_model = model;
_selected_algo_mon = new SelectedAlgosMonitor(model.dynamic_helper);
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, new AlgoData(new CustomOrderDefinitionImmutable(new CustomOrderDefinition { Limit = new LimitOrderDefinition() })), editing_permissions: AlgoEditingPermissions.AllowEditValues));
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
order_mod_vm.algo_vm.algo_root = null;
attach_vm = new OrderParamsVM(this);
_fills_slave_grid_is_visible = backing(nameof(fills_slave_grid_is_visible), false);
_settings_manager = settings_manager;
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
_row_collection = new OrderBookRowCollection(ort => new OrderBookRowVM(this, ort, rows_are_initially_expanded), _columns.get_sorting_data().comparer) { filter = _columns.get_filter() };
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
_current_row = backing<OrderBookRowVM>(nameof(current_row));
_columns.filter_changed += on_filter_changed;
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
var modify_selected_algos_async_cmd = new AsyncCommand(_modify_selected_algos_async, () => _selected_algo_mon.all_active);
modify_selected_algos_command = modify_selected_algos_async_cmd;
var send_attached_orders_async_cmd = new AsyncCommand(send_attached_orders_async, () => _selected_algo_mon.all_active);
send_attached_orders_cmd = send_attached_orders_async_cmd;
cancel_attach_orders_cmd = new DelegateCommand(() => is_attaching_algos = false);
toggle_algo_editor_is_visible_cmd = new DelegateCommand(() => algo_editor_is_visible = !algo_editor_is_visible);
toggle_fills_slave_grid_is_visible_cmd = new DelegateCommand(() => fills_slave_grid_is_visible = !fills_slave_grid_is_visible);
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, rows, core_columns);
_resources = new List<IDisposable> {
IDisposable attach_vm_hooks = null;
begin_attaching += on_begin_attaching;
end_attaching += on_end_attaching;
concat_disposable(
connect_common_selected_params(order_mod_vm),
_selected_algo_mon.subscribe_all_active().Subscribe(on_all_selected_orders_active_changed),
model.on_order_tuples.OnDispatcher().Subscribe(_row_collection.update),
grid_settings_manager,
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(_selected_algo_mon.subscribe_common_spreader_sec()),
order_mod_vm.algo_vm,
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_algo_mon.on_security_md_map),
attach_vm,
_fills_slave_grid_is_visible.observe_current(true)
.Subscribe(is_visible => fills_vm.value = is_visible ?
new FillsVM(
bus, model, Observable.Return(FillsWindowFactory.StartupData.Default()),
model.on_fill_tuples.Filter(mk_fills_slave_predicate_src(), ft => ft.fill.FillKey, _ => false), null
) :
null),
Disposable.Create(() => {
_columns.filter_changed -= on_filter_changed;
_row_collection.CollectionChanged -= _row_collection_CollectionChanged;
begin_attaching -= on_begin_attaching;
end_attaching -= on_end_attaching;
Disposable.dispose(ref attach_vm_hooks);
Disposable.dispose(_row_collection);
}),
_columns
};
_columns);
if (settings_manager != null) {
_resources.Add(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
concat_disposable(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
}
IObservable<Func<FillTuple, bool>> mk_fills_slave_predicate_src() =>
selected_rows.ObserveSelf(true)
.BufferLatest(TimeSpan.FromSeconds(0.2))
.Select(_ => mk_fills_slave_predicate());
Func<FillTuple, bool> mk_fills_slave_predicate() {
var keys = new HashSet<uint>(selected_rows.SelectMany(r => new[] { r }.Concat(_row_collection.get_descendent_rows(r))).Select(r => r.order_key));
return ft => keys.Contains(ft.fill.OrderKey);
}
void on_filter_changed() => _row_collection.filter = _columns.get_filter();
void on_all_selected_orders_active_changed(bool newval) {
order_mod_vm.algo_vm.editing_permissions_mask = newval ? AlgoEditingPermissions.AllowEditValues : AlgoEditingPermissions.None;
modify_selected_algos_async_cmd.RaiseCanExecuteChanged();
send_attached_orders_async_cmd.RaiseCanExecuteChanged();
}
async Task send_attached_orders_async() {
is_attaching_algos = false;
try {
double price = attach_vm.price;
uint size = attach_vm.size_calculator.value ?? 0;
var algo = attach_vm.algo_vm.algo_root.created_assigned();
var order_tups = selected_rows.Select(r => r.order_tuple);
await Task.WhenAll(order_tups.Select(t => _model.send_order(t.order.Def.with(attach_target_order_key: t.order.Key, custom_def: algo), price, size)));
} catch (Exception ex) {
Log.Ui.error($"Error sending attached orders: {ex}.");
}
}
void on_begin_attaching() => Disposable.replace(ref attach_vm_hooks, connect_common_selected_params(attach_vm));
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_algo_mon.subscribe_common_algo().Subscribe(newval => {
if (newval.HasValue) {
order_mod_vm.algo_vm.load_def(newval.Value);
} else {
order_mod_vm.algo_vm.algo_root = null;
}
}),
_selected_algo_mon.subscribe_common_sec_md().Subscribe(newval => vm.security_md = newval),
_selected_algo_mon.subscribe_price().Subscribe(newval => vm.price = newval),
_selected_algo_mon.subscribe_size().Subscribe(newval => vm.size_calculator.value = newval));
}
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
@@ -75,7 +164,7 @@ namespace mbts.spark {
}
public event Action<OrderRecordImmutable?> selected_order_changed;
public event Action<bool> OnDisposed;
public event Action begin_attaching, end_attaching;
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
public ObservableCollection<DIYGrid.ColumnBase> core_columns => _columns;
@@ -94,41 +183,96 @@ namespace mbts.spark {
}
}
public OrderRecordImmutable? current_order => current_row?.order;
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
public AlgoDefinitionVM algo_vm => order_mod_vm.algo_vm;
public OrderParamsVM order_mod_vm { get; }
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
public bool fills_slave_grid_is_visible { get => _fills_slave_grid_is_visible.Value; set => _fills_slave_grid_is_visible.set(value); }
public OrderParamsVM attach_vm { get; }
public ObservableCollection<OrderBookRowVM> selected_rows { get; } = new ObservableCollection<OrderBookRowVM>();
public IEnumerable<OrderRecordImmutable> selected_orders => selected_rows.Select(r => r.order);
public _GridSettingsManager grid_settings_manager { get; }
public bool is_attaching_algos {
get => _is_attaching_algos;
set {
if (Set(ref _is_attaching_algos, value))
(value ? begin_attaching : end_attaching)?.Invoke();
}
}
public ICommand collapse_all_rows_command { get; }
public ICommand expand_all_rows_command { get; }
public ICommand modify_selected_algos_command { get; }
public ICommand send_attached_orders_cmd { get; }
public ICommand cancel_attach_orders_cmd { get; }
public ICommand toggle_algo_editor_is_visible_cmd { get; }
public ICommand toggle_fills_slave_grid_is_visible_cmd { get; }
public bool algo_editor_is_visible {
get => _algo_editor_is_visible;
set {
if (Set(ref _algo_editor_is_visible, value))
RaisePropertyChanged(nameof(right_panel_width));
}
}
public double raw_right_panel_width => _raw_right_panel_width;
public double right_panel_width {
get => algo_editor_is_visible ? Math.Max(raw_right_panel_width, _min_visible_right_panel_width) : 0;
set {
if (_raw_right_panel_width != value) {
_raw_right_panel_width = value;
if (algo_editor_is_visible)
RaisePropertyChanged();
}
}
}
public double fills_slave_grid_height {
get => algo_editor_is_visible ? Math.Max(_raw_fills_slave_grid_height, _min_visible_fills_slave_grid_height) : 0;
set {
if (_raw_fills_slave_grid_height != value) {
_raw_fills_slave_grid_height = value;
if (algo_editor_is_visible)
RaisePropertyChanged();
}
}
}
public bool is_row_collection_populated {
get => _is_row_collection_populated;
set => Set(ref _is_row_collection_populated, value);
}
public void row_selection_changed(SelectionChangedEventArgs e) {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = false;
try {
if (e.RemovedItems != null) {
foreach (var row_vm in e.RemovedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = false;
_selected_algo_mon.on_row_unselected(row_vm);
}
}
}
if (e.AddedItems != null) {
foreach (var row_vm in e.AddedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = true;
if (e.AddedItems != null) {
foreach (var row_vm in e.AddedItems.Cast<OrderBookRowVM>()) {
row_vm.is_selected = true;
_selected_algo_mon.on_row_selected(row_vm);
}
}
RowSelectionHelper.apply_changes(selected_rows, e);
selected_order_changed?.Invoke(current_order);
} catch(Exception ex) {
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
}
RowSelectionHelper.apply_changes(_selected_rows, e);
selected_order_changed?.Invoke(current_order);
}
public OrderBookStartupData create() {
var (filters, sorting) = _columns.create();
var gs = grid_settings_manager.create();
return new OrderBookStartupData(filters, gs.general.with(sorting_criterion: sorting), gs.predicate_animations, use_defaults: _settings_manager.instance_settings == null);
return new OrderBookStartupData(filters, gs.general.with(sorting_criterion: sorting), _algo_editor_is_visible, raw_right_panel_width, _raw_fills_slave_grid_height, fills_slave_grid_is_visible, gs.predicate_animations, use_defaults: _settings_manager.instance_settings == null);
}
public void load_from(OrderBookStartupData state) {
if (state == null) return;
_state = state;
algo_editor_is_visible = state.algo_editor_is_visible;
right_panel_width = state.right_panel_width;
fills_slave_grid_height = state.fills_slave_grid_height;
fills_slave_grid_is_visible = state.fills_slave_grid_is_visible;
var gs = state.grid_settings_wrapper();
if (gs != null) {
grid_settings_manager.load_from(gs);
@@ -173,6 +317,13 @@ namespace mbts.spark {
}
}
public void begin_attach_algos() => is_attaching_algos = true;
public void detach_selected_algos() =>
_detach_orders(selected_rows.Where(r => r.order.can_detach()).ToList()).ignore();
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
internal void show_order_modify(OrderBookRowVM row) =>
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
@@ -208,11 +359,40 @@ namespace mbts.spark {
dr.is_visible = false;
}
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
async Task _attach_orders(IReadOnlyList<OrderBookRowVM> rows, OrderBookRowVM target) {
Log.Ui.info($"{nameof(OrderBookVM)} begin attaching orders ({rows.Count} total)...");
try {
await Task.WhenAll(rows.Select(r => _model.cancel_order(r.order_key)));
} catch (Exception ex) {
Log.Ui.error($"Error canceling orders, in advance of attachment: {ex}.");
}
Log.Ui.info($"{nameof(OrderBookVM)} succesfully canceled orders ({rows.Count} total), in advance of attachment. Begin sending attached orders ({rows.Count} total)...");
try {
await Task.WhenAll(rows.Select(r => _model.send_order(r.order.Def.with(attach_target_order_key: target.order_key), r.order.State.Price, (uint)r.order.working_quantity())));
} catch (Exception ex) {
Log.Ui.error($"Error sending attached orders: {ex}.");
}
Log.Ui.info($"{nameof(OrderBookVM)} succesfully sent attached orders ({rows.Count} total).");
}
public void Dispose() {
Disposable.dispose(_resources);
OnDisposed?.Invoke(true);
async Task _detach_orders(IReadOnlyList<OrderBookRowVM> orders) {
Log.Ui.info($"{nameof(OrderBookVM)} begin detaching orders ({orders.Count} total)...");
try {
await Task.WhenAll(orders.Select(r => _model.detach_order(r.order_key)));
} catch (Exception ex) {
Log.Ui.error($"{nameof(OrderBookVM)} error detaching orders: {ex}.");
}
}
async Task _modify_selected_algos_async() {
var malgo = order_mod_vm.algo_vm.algo_root?.created_assigned();
var algo = malgo == null ? (CustomOrderDefinitionImmutable?)null : new CustomOrderDefinitionImmutable(malgo);
double? price = DoubleHelper.nan_nullable(order_mod_vm.price);
uint? size = order_mod_vm.size_calculator.value;
foreach (var r in selected_rows) {
var ralgo = algo.HasValue && !OrderDataHelper.EqualsHelpers.equals(r.order.Def.CustomDef, algo.Value) ? algo : null;
await _model.update_order(r.order, new_price: price, new_size: size, new_algo: ralgo);
}
}
static _GridSettingsManager _create_grid_settings_manager(
@@ -267,8 +447,225 @@ namespace mbts.spark {
case Columns.SecurityKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.SecurityKey;
case Columns.OrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.OrderKey;
case Columns.ParentKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.ParentKey;
case Columns.AttachedTargetOrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.AttachedTargetOrderKey;
default: return default;
}
}
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
Observable.Create<ISet<uint>>(o => {
bool waiting_for_mouse_up = false;
var sub_container = new DisposableContainer();
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
var selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
if (waiting_for_mouse_up)
return;
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
foreach (OrderBookRowVM row in e.NewItems)
selected_keys = selected_keys.Add(row.order_key);
break;
case NotifyCollectionChangedAction.Remove:
foreach (OrderBookRowVM row in e.OldItems)
selected_keys = selected_keys.Remove(row.order_key);
break;
case NotifyCollectionChangedAction.Reset:
selected_keys = ImmutableHashSet<uint>.Empty;
break;
default: return;
}
if (Mouse.LeftButton == MouseButtonState.Pressed) {
waiting_for_mouse_up = true;
wait_for_mouse_up();
async void wait_for_mouse_up() {
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
foreach (var row in selected_rows)
builder.Add(row.order_key);
selected_keys = builder.ToImmutable();
o.OnNext(selected_keys);
waiting_for_mouse_up = false;
}
} else {
o.OnNext(selected_keys);
}
}
});
public class OrderParamsVM : DisposableViewModelBase {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly OrderBookVM _owner;
SecurityMD? _security_md;
double _price = 0;
public OrderParamsVM(OrderBookVM owner)
: this(owner,
AlgoDefinitionVM.create(
owner._model,
new AlgoData(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg)),
editing_permissions: AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues)) { }
public OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
_owner = owner;
size_calculator = new CalculatorLogic(null, Observable.Return(LadderSettings.Default.calculator_buttons));
this.algo_vm = algo_vm;
_accounts = backing(
nameof(selected_account),
_model.factory.subscribe_account_tuples(),
_model.admin_model.on_account_tuples.Where(ts => ts.Count > 0).Select(ts => new InlineUserProperty<AccountTuple>(ts[0])).Take(1),
a => new AccountTuple?(a),
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
EqualityComparer<AccountTuple?>.Default,
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
set_price_command = new DelegateCommand<double>(p => price = p);
bbo = new BBOVM { security_md = security_md };
_dispose = Disposable.Create( bbo, algo_vm);
}
public ReadOnlyObservableCollection<AccountTuple> accounts => _accounts.items;
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
public SecurityImmutable? security => _owner._selected_algo_mon.common_sec;
public SecurityMD? security_md {
get => _security_md;
set {
if (Set(ref _security_md, value)) {
RaisePropertyChanged(nameof(security), nameof(spec));
bbo.security_md = value;
}
}
}
public ContractSpecificationImmutable? spec => security?.ContractSpec;
public double price { get => _price; set => Set(ref _price, value); }
public CalculatorLogic size_calculator { get; }
public AlgoDefinitionVM algo_vm { get; }
public BBOVM bbo { get; }
public ICommand send_order { get; }
public ICommand set_price_command { get; }
OrdersModel _model => _owner._model;
}
/// <summary>
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
/// </summary>
class SelectedAlgosMonitor {
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
readonly CommonValueMonitor<AlgoSecurityKey> _algo_mon = new CommonValueMonitor<AlgoSecurityKey>();
readonly CommonValueMonitor<SecurityImmutable?> _spreader_sec_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<SecurityImmutable?> _sec_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
readonly CommonValueMonitor<bool> _active_mon = new CommonValueMonitor<bool>();
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta>();
readonly DynamicOrderHelper _dyn_helper;
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
public SelectedAlgosMonitor(DynamicOrderHelper dyn_helper) {
_dyn_helper = dyn_helper;
_group_mon.add_member_mon(_algo_mon, _algo_key);
_group_mon.add_member_mon(_spreader_sec_mon, _get_spreader_sec, s => s.HasValue);
_group_mon.add_member_mon(_sec_mon, osam => osam.security, s => s.HasValue);
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
_group_mon.add_member_mon(_active_mon, osam => osam.order.is_active());
}
event Action _security_md_map_changed;
public CustomOrderDefinitionImmutable? common_algo => _algo_mon.common_value_nullable()?.algo;
public SecurityImmutable? common_sec => _sec_mon.common_value;
public SecurityMD? common_sec_md => common_sec is SecurityImmutable sec && _security_md_map.TryGetValue(sec.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
public SecurityImmutable? common_spreader_sec => _spreader_sec_mon.common_value;
public double common_price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
public uint? common_size => _size_mon.common_value_nullable();
public AccountTuple? common_account => _account_mon.common_value_nullable();
public bool all_active => _active_mon.common_value;
public IObservable<CustomOrderDefinitionImmutable?> subscribe_common_algo() => _algo_mon.SubscribeThrottledNullable(_throttle).Select(ask => ask.HasValue ? ask.Value.algo: (CustomOrderDefinitionImmutable?)null);
public IObservable<SecurityImmutable?> subscribe_common_spreader_sec() => _spreader_sec_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityImmutable?> subscribe_common_sec() => _sec_mon.SubscribeThrottled(_throttle);
public IObservable<SecurityMD?> subscribe_common_sec_md() =>
Observable.Create<SecurityMD?>(o => {
SecurityMD? last_value = common_sec_md;
o.OnNext(last_value);
_sec_mon.common_value_changed += on_common_sec_changed;
_security_md_map_changed += refresh;
return Disposable.Create(() => {
_sec_mon.common_value_changed -= on_common_sec_changed;
_security_md_map_changed -= refresh;
});
void on_common_sec_changed(SecurityImmutable? _) => refresh();
void refresh() {
var curr = common_sec_md;
if (curr != last_value)
o.OnNext(last_value = curr);
}
});
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
public IObservable<bool> subscribe_all_active() => _active_mon.SubscribeThrottled(_throttle);
public void on_row_selected(OrderBookRowVM row) {
_group_mon.add(row.order_tuple);
row.updated += _on_row_updated;
}
public void on_row_unselected(OrderBookRowVM row) {
_group_mon.remove(row.order_tuple);
row.updated -= _on_row_updated;
}
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
_security_md_map = security_md_map;
_security_md_map_changed?.Invoke();
}
void _on_row_updated((OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) ch) {
_group_mon.remove(ch.oldval);
_group_mon.add(ch.newval);
}
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgoMeta osam) =>
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
osam.security : null;
AlgoSecurityKey _algo_key(OrderSecurityAlgoMeta osam) =>
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
SecurityMD? _get_sec_md(OrderSecurityAlgoMeta osam) =>
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
struct AlgoSecurityKey {
public readonly CustomOrderDefinitionImmutable algo;
public readonly SecurityImmutable? security;
public readonly AlgoStructureKey key;
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
this.algo = algo;
this.security = security;
this.key = key;
}
public bool Equals(AlgoSecurityKey other) =>
security == other.security &&
key == other.key;
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
public override int GetHashCode() => key.GetHashCode();
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
}
}
}
}
}
@@ -1,168 +0,0 @@
<UserControl x:Class="mbts.spark.OrderBookView" x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:uc="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1080"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
<DataTemplate x:Key="{x:Static sp:OrderBookView.SubTypeFilterTemplateKey}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
>
<c:ListBoxEx.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
</DataTemplate>
</c:ListBoxEx.ItemTemplate>
</c:ListBoxEx>
</usp:FilterContentControl>
</DataTemplate>
<Style x:Key="{x:Static sp:OrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="{x:Static sp:OrderBookView.RowTemplateKey}">
<sp:OrderBookRowView/>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<c:DIYGrid
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Rows="{Binding rows}"
RowStyle="{StaticResource {x:Static sp:OrderBookView.RowStyleKey}}"
RowTemplate="{StaticResource {x:Static sp:OrderBookView.RowTemplateKey}}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</c:DIYGrid.Resources>
<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>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -2,27 +2,31 @@
using bts.api.orderman;
using bts.shell;
using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.extensions;
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 System.Windows.Data;
using System.Windows.Input;
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<bts.api.orderman.OrderSecurity, 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", _default_title = "Order Book";
const string _window_type = "OrderBook", _default_title = "Order Book";
readonly INotificationService _notification_service;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
IDisposable _attachment_exit_sub;
OrderBookWindowFactory.OrderBookStartupData _state;
OrdersModel _model;
OrderBookVM _vm;
@@ -30,17 +34,6 @@ namespace mbts.spark {
OrderBookWindowFactory _factory;
SparkCommand[] _commands;
SparkCommand _show_fills_command
, _modify_order_command
, _cancel_buy_orders_command
, _cancel_sell_orders_command
, _cancel_all_orders_command
, _cancel_selected_orders_command
, _recreate_selected_orders_command
, _toggle_collapse_all_command
, _toggle_expand_all_command
, _show_grid_settings_command;
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) {
@@ -49,6 +42,7 @@ namespace mbts.spark {
_notification_service = notification_service;
_state = state;
_settings_manager = settings_manager;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm?.create();
@@ -61,10 +55,11 @@ namespace mbts.spark {
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id => ORDERBOOK_THREAD_ID;
public string thread_id { get; }
public override void Dispose() {
_commands.OfType<IDisposable>().DisposeAll();
Disposable.dispose(ref _attachment_exit_sub);
Disposable.dispose(ref _vm);
}
@@ -73,54 +68,62 @@ namespace mbts.spark {
_vm.load_from(_state);
_view = new OrderBookView(_vm);
_show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
var show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
_modify_order_command = SparkCommands.ShowModifyOrder.complete(
() => _vm.current_order.Value.Key,
can_execute: () => {
if (_vm.current_order.HasValue) {
return _vm.current_order.Value.State.Status == bts.api.orderman.OrderStatus.Working;
}
return false;
}
);
var toggle_right_panel_is_visible_command = new SparkCommandCommand(
get_toggle_algo_editor_cmd_title(),
Icons.ModifyOrder,
_vm.toggle_algo_editor_is_visible_cmd,
new KeyGesture(Key.M, ModifierKeys.Control | ModifierKeys.Shift));
_cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
var cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Buy).Select(o => o.order.Key)),
wbids => wbids.Any()
);
_cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
var cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Sell).Select(o => o.order.Key)),
wasks => wasks.Any()
);
_cancel_all_orders_command = SparkCommands.CancelOrders.complete(
var cancel_all_orders_command = SparkCommands.CancelOrders.complete(
_model.working_orders.Select(os => os.Select(o => o.Key)),
worders => worders.Any()
);
_cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
var cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
() => _vm.selected_orders.Where(x => x.status() == OrderStatus.Working).Select(x => x.Key),
can_execute: () => _vm.selected_orders.Any(x => x.status() == OrderStatus.Working)
);
_recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
var recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
() =>
_vm.selected_orders.Where(x => _passes_recreate_search_criteria(x))
_vm.selected_orders.Where(x => passes_recreate_search_criteria(x))
.Select(x => (x.Def, x.State.Price, x.State.Quantity - x.State.FilledQuantity)),
can_execute: () => _vm.selected_orders.Any(x => _passes_recreate_search_criteria(x))
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
);
_toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
var attach_selected_orders_command = new SparkCommandCommand(
"Attach To Selected Orders",
Icons.AttachAlgos,
new DelegateCommand(_vm.begin_attach_algos, () => _vm.selected_rows.Count > 0),
new KeyGesture(Key.A, ModifierKeys.Control | ModifierKeys.Shift));
var detach_selected_orders_command = new SparkCommandCommand(
"Detach Selected Orders",
Icons.DetachAlgos,
new DelegateCommand(_vm.detach_selected_algos, () => _vm.selected_rows.Count > 0),
new KeyGesture(Key.D, ModifierKeys.Control | ModifierKeys.Shift));
var toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
Icons.RightArrow,
_vm.collapse_all_rows_command);
_toggle_expand_all_command = new SparkCommandCommand("Expand All",
var toggle_expand_all_command = new SparkCommandCommand("Expand All",
Icons.DownArrow,
_vm.expand_all_rows_command);
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
_settings_manager,
us => us.order_book_grid,
@@ -145,31 +148,44 @@ namespace mbts.spark {
return dialog;
}));
_commands = new SparkCommand[] {
_show_fills_command,
_modify_order_command,
_cancel_selected_orders_command,
_cancel_buy_orders_command,
_cancel_sell_orders_command,
_cancel_all_orders_command,
_recreate_selected_orders_command,
_toggle_collapse_all_command,
_toggle_expand_all_command,
_show_grid_settings_command
_commands = new SparkCommand[] {
show_fills_command,
toggle_right_panel_is_visible_command,
cancel_selected_orders_command,
cancel_buy_orders_command,
cancel_sell_orders_command,
cancel_all_orders_command,
recreate_selected_orders_command,
attach_selected_orders_command,
detach_selected_orders_command,
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command
};
_vm.selected_order_changed += _vm_selected_order_changed;
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.algo_editor_is_visible))
.Subscribe(_ => toggle_right_panel_is_visible_command.title = get_toggle_algo_editor_cmd_title());
_vm.selected_order_changed += vm_selected_order_changed;
// local methods
string get_toggle_algo_editor_cmd_title() =>
$"{(_vm.algo_editor_is_visible ? "Hide" : "Show")} Order Params";
bool passes_recreate_search_criteria(OrderRecordImmutable record) =>
(record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error) &&
!record.has_parent() &&
record.State.Quantity > record.State.FilledQuantity;
void vm_selected_order_changed(OrderRecordImmutable? e) {
cancel_selected_orders_command.raiseCanExecuteChanged();
recreate_selected_orders_command.raiseCanExecuteChanged();
attach_selected_orders_command.raiseCanExecuteChanged();
detach_selected_orders_command.raiseCanExecuteChanged();
}
}
private bool _passes_recreate_search_criteria(OrderRecordImmutable record) {
return (record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error)
&& !record.has_parent()
&& record.State.Quantity > record.State.FilledQuantity;
}
private void _vm_selected_order_changed(OrderRecordImmutable? e) {
_modify_order_command.raiseCanExecuteChanged();
_cancel_selected_orders_command.raiseCanExecuteChanged();
_recreate_selected_orders_command.raiseCanExecuteChanged();
}
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
_vm.is_attaching_algos = false;
}));
}
}
@@ -12,10 +12,11 @@ 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;
using static mbts.spark.OrderBookView;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace bts.shell {
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookWindowFactory.OrderBookStartupData> {
@@ -49,24 +50,57 @@ namespace bts.shell {
[ProtoContract]
public class OrderBookStartupData {
//[ProtoMember(5)]
//public readonly List<SortingCriteria> sort_criteria;
const bool _default_algo_editor_is_visible = true;
const double
_default_right_panel_width = 400,
_default_fills_slave_grid_height = 138;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid;
// algo editor is visible
[ProtoMember(16)]
[DefaultValue(_default_algo_editor_is_visible)]
public bool algo_editor_is_visible { get; set; }
// right panel width
[ProtoMember(17)]
[DefaultValue(_default_right_panel_width)]
public double right_panel_width { get; set; }
// fills slave grid height
[ProtoMember(18)]
[DefaultValue(_default_fills_slave_grid_height)]
public double fills_slave_grid_height { get; set; }
// fills slave grid is visible
[ProtoMember(19)]
public bool fills_slave_grid_is_visible { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
public OrderBookStartupData() { }
public OrderBookStartupData() {
algo_editor_is_visible = _default_algo_editor_is_visible;
right_panel_width = _default_right_panel_width;
fills_slave_grid_height = _default_fills_slave_grid_height;
}
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, bool algo_editor_is_visible = false, double right_panel_width = 0, double fills_slave_grid_height = 0, bool fills_slave_grid_is_visible = false, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.algo_editor_is_visible = algo_editor_is_visible;
this.right_panel_width = right_panel_width;
this.fills_slave_grid_height = fills_slave_grid_height;
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
#pragma warning disable CS0612 // Type or member is obsolete
this.grid = grid;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
@@ -83,9 +117,12 @@ namespace bts.shell {
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates {
statuses = new[] { OrderStatus.Working }
}, _default_general_grid, use_defaults: true);
new FilterStates { statuses = new[] { OrderStatus.Working } },
_default_general_grid,
_default_algo_editor_is_visible,
_default_right_panel_width,
_default_fills_slave_grid_height,
use_defaults: true);
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
@@ -115,6 +152,7 @@ namespace bts.shell {
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, 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.ParentKey);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
@@ -145,11 +183,12 @@ namespace bts.shell {
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
@@ -208,11 +247,17 @@ namespace bts.shell {
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
@@ -230,6 +275,32 @@ namespace bts.shell {
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols));
}
[ProtoContract]
@@ -240,7 +311,7 @@ namespace bts.shell {
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
public OrderBookGridSettings() { }
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, double right_panel_width = double.NaN) {
this.general = general;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
@@ -0,0 +1,63 @@
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.extensions;
using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
namespace mbts.spark {
partial class OrderBookView {
public class OrderBookAttachmentTargetAdorner : DIYGridRowHighlighterAdorner, IDisposable {
IDisposable _attach_op;
bool _is_active, _is_disposed;
#region IsActive
public static readonly DependencyProperty IsActiveProperty =
WPFHelper.create_dp<bool, OrderBookAttachmentTargetAdorner>(
nameof(IsActive),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => {
o._is_active = v;
Disposable.dispose(ref o._attach_op);
if (v && !o._is_disposed) {
o.IsHitTestVisible = true;
o._attach_op = Disposable.Create(
o.SubscribePreviewMouseDown((s, e) => {
if (e.ChangedButton == MouseButton.Left) {
if (e.ClickCount > 1 && o.TargetRow is OrderBookRowVM r) {
e.Handled = true;
o._raise_target_row_selected(r);
}
}
}),
o.AdornedElement.SubscribeMouseHoverRow<OrderBookRowVM>().Subscribe(row => o.TargetRow = row));
} else {
o.IsHitTestVisible = false;
o.TargetRow = null;
}
},
false);
public bool IsActive {
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
public event Action<OrderBookRowVM> TargetRowSelected;
#endregion
public OrderBookAttachmentTargetAdorner(DIYGrid grid) : base(grid) { }
public void Dispose() {
if (!_is_disposed) {
_is_disposed = true;
Disposable.dispose(ref _attach_op);
}
}
void _raise_target_row_selected(OrderBookRowVM row) => TargetRowSelected?.Invoke(row);
}
}
}
@@ -0,0 +1,132 @@
<c:DIYGrid
x:Class="mbts.spark.OrderBookGridView"
x:ClassModifier="internal"
x:Name="_grid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Resources (Brushes, Colors, etc.)-->
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<!--Converters-->
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
<!--DataTemplates-->
<DataTemplate x:Key="{x:Static sp:OrderBookGridView.SubTypeFilterTemplateKey}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
>
<c:ListBoxEx.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
</DataTemplate>
</c:ListBoxEx.ItemTemplate>
</c:ListBoxEx>
</usp:FilterContentControl>
</DataTemplate>
<!--Styles-->
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</c:DIYGrid.Resources>
<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.RowStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Working, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</c:DIYGrid.RowStyle>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<sp:OrderBookRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
@@ -1,11 +1,7 @@
using mbts.ui.controls;
using System.Windows.Controls;
namespace mbts.spark {
internal partial class OrderBookView : UserControl {
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice }
internal partial class OrderBookGridView : DIYGrid {
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
@@ -14,13 +10,41 @@ namespace mbts.spark {
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum ColumnKeys {
Security,
Symbol,
Time,
LastChange,
User,
Account,
Exchange,
TimeInForce,
OrderType,
SubType,
Side,
Size,
Price,
Filled,
AvgFilled,
LastPrice,
Status,
LastMessage,
SecurityKey,
OrderKey,
ParentKey,
AttachedTargetOrderKey,
Cancel,
EditableQty,
EditablePrice
}
public OrderBookGridView() =>
InitializeComponent();
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public OrderBookRowView() : base() { }
}
}
@@ -0,0 +1,393 @@
<UserControl
x:Class="mbts.spark.OrderBookView"
x:ClassModifier="internal"
x:Name="Root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns: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"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Grid.Row="0"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="GridSplitterWidth">5</sys:Double>
<sys:Double x:Key="FillsSlaveGridMinHeight">64</sys:Double>
<SolidColorBrush x:Key="AlgoAttachmentBackground" Color="#55009500"/>
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<cv:ValueMap x:Key="AlgoAttachmentStateConverter" Default="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
<cv:KeyValue Key="{u:True}" Value="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}"/>
</cv:ValueMap>
<Style TargetType="GridSplitter">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource Control.MediumGrayBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource Control.MediumGrayBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Bid Price ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Bid Size ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Ask Price ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Ask Size ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<Grid x:Name="ContentRoot">
<u:StateManager.Groups>
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding DataContext.is_attaching_algos, Source={u:DirectReference ContentRoot}, Converter={StaticResource AlgoAttachmentStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="AlgoAttachmentStates">
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_AlgoAttachmentRoot_Border" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Orders_DIYGrid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Pen}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsToggle_Hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_algos_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_algo_editor" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.AlgoAttachment}}">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_AlgoAttachmentRoot_Border" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Orders_DIYGrid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Arrow}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsToggle_Hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_algos_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_algo_editor" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding right_panel_width, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}"/>
</Grid.ColumnDefinitions>
<sp:OrderBookGridView
x:Name="PART_Orders_DIYGrid"
Background="Transparent"
Columns="{Binding columns}"
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Rows="{Binding rows}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
Cursor="Arrow"
/>
<Border
x:Name="PART_AlgoAttachmentRoot_Border"
Width="{Binding ActualWidth, ElementName=PART_Orders_DIYGrid}"
Height="{Binding ActualHeight, ElementName=PART_Orders_DIYGrid}"
Background="{StaticResource AlgoAttachmentBackground}"
Opacity="0"
IsHitTestVisible="False"
/>
<GridSplitter
Grid.Column="1"
Panel.ZIndex="1"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="{StaticResource GridSplitterWidth}"
BorderThickness="1,0,0,0"
/>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="RightPanelRoot">
<u:StateManager.Groups>
<u:StateGroupsManager Target="{Binding Source={u:DirectReference RightPanelRoot}}">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding DataContext.fills_slave_grid_is_visible, Source={u:DirectReference RightPanelRoot}, Converter={x:Static sp:OrderBookView.FillsPanelVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Fills_DIYGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsAlgo_GridSplitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Fills_DIYGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_FillsAlgo_GridSplitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Source={StaticResource GridSplitterWidth}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition MinHeight="{StaticResource FillsSlaveGridMinHeight}" Height="{Binding fills_slave_grid_height, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}" MaxHeight="{Binding ActualHeight, ElementName=RightPanelRoot, Converter={x:Static sp:OrderBookView.FillsSlaveGridMaxHeightConverter}}"/>
<RowDefinition MinHeight="{x:Static sp:OrderBookView.GridSplitterHeight}" Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="PART_FillsToggle_Hyperlink" Grid.Column="1">
<TextBlock.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="4"/>
</TextBlock.Margin>
<Hyperlink Command="{Binding toggle_fills_slave_grid_is_visible_cmd}">
<Hyperlink.Inlines>
<Run Text="{Binding current_row, Converter={x:Static sp:OrderBookView.OrderDescriptionConverter}, Mode=OneWay}"/>
</Hyperlink.Inlines>
</Hyperlink>
</TextBlock>
<c:DIYGrid x:Name="PART_Fills_DIYGrid" Grid.Column="1" Grid.Row="1" Columns="{Binding fills_vm.columns}" Rows="{Binding fills_vm.rows}">
<c:DIYGrid.Margin>
<Thickness Bottom="{x:Static sp:OrderBookView.GridSplitterHeight}"/>
</c:DIYGrid.Margin>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<GridSplitter
x:Name="PART_FillsAlgo_GridSplitter"
Grid.Column="1"
Grid.Row="1"
Panel.ZIndex="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
BorderThickness="0,1,0,0"
/>
<GroupBox
x:Name="order_params_group_box"
Grid.Column="1"
Grid.Row="2"
Padding="12"
BorderThickness="1"
Header="Order Parameters"
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
>
<usp:OrderParamsPanel
x:Name="order_params_panel"
DataContext="{Binding order_mod_vm}"
Price="{Binding price}"
Size="{Binding size_calculator.value}"
Accounts="{Binding accounts}"
SelectedAccount="{Binding selected_account}"
BestBidPrice="{Binding bbo.bid}"
BestBidSize="{Binding bbo.bid_size}"
BestAskPrice="{Binding bbo.ask}"
BestAskSize="{Binding bbo.ask_size}"
SizePresets="{Binding size_calculator.buttons}"
Security="{Binding security}"
AlgoVM="{Binding algo_vm}"
IsEnabled="{Binding IsEnabled, ElementName=modify_algos_button}"
>
<usp:OrderParamsPanel.CommandBindings>
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
</usp:OrderParamsPanel.CommandBindings>
</usp:OrderParamsPanel>
</GroupBox>
<Grid
x:Name="attachment_algo_editor"
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Left"
MinWidth="320"
Margin="0,4,0,0"
Visibility="Collapsed"
>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<GroupBox
Padding="12"
Header="Attached Order Parameters"
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
>
<usp:OrderParamsPanel
x:Name="attached_order_params_panel"
DataContext="{Binding attach_vm}"
Price="{Binding price}"
Size="{Binding size_calculator.value}"
Accounts="{Binding accounts}"
SelectedAccount="{Binding selected_account}"
BestBidPrice="{Binding bbo.bid}"
BestBidSize="{Binding bbo.bid_size}"
BestAskPrice="{Binding bbo.ask}"
BestAskSize="{Binding bbo.ask_size}"
SizePresets="{Binding size_calculator.buttons}"
Security="{Binding security}"
AlgoVM="{Binding algo_vm}"
IsEnabled="{Binding IsEnabled, ElementName=send_attached_orders_button}"
>
<usp:OrderParamsPanel.CommandBindings>
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
</usp:OrderParamsPanel.CommandBindings>
</usp:OrderParamsPanel>
</GroupBox>
<StackPanel
x:Name="attach_orders_dialog_skirt"
Grid.Row="1"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="{StaticResource DialogSkirtPadding}"
Orientation="Horizontal"
Visibility="Collapsed"
>
<Button x:Name="send_attached_orders_button" Command="{Binding send_attached_orders_cmd}" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Send Attached Orders ({0})'}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="Send Attached Orders"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
<Button u:Behavior.CloseWindow="False" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding cancel_attach_orders_cmd}" Style="{StaticResource CancelButton}"/>
</StackPanel>
</Grid>
</Grid>
<Button x:Name="modify_algos_button" Grid.Row="1" HorizontalAlignment="Right" Margin="{StaticResource DialogSkirtPadding}" Command="{Binding modify_selected_algos_command}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Modify Algos ({0})'}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="Modify Algos"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
</Grid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -0,0 +1,127 @@
using mbts.ui;
using mbts.ui.converters;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.spark {
internal partial class OrderBookView : UserControl {
public const double
GridSplitterHeight = 5,
MinAlgoEditorHeight = 64,
MinAttachOrderParamsPanelWidth = 340,
AttachOrderParamsPanelWidthRatio = 0.33;
public static readonly IValueConverter
OrderDescriptionConverter = ValueConverter.create<OrderBookRowVM, string>(row => row == null ? string.Empty : row.order.short_description(row.security)),
FillsSlaveGridMaxHeightConverter = ValueConverter.create<double, double>(right_panel_height => right_panel_height - (GridSplitterHeight + MinAlgoEditorHeight)),
FillsPanelVisualStateConverter = ValueConverter.create<bool, string>(show => show ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive),
AttachmentTargetCursorConverter = ValueConverter.create<bool, Cursor>(is_active => is_active ? Cursors.Pen : Cursors.Arrow);
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
const int delay_ms = 25;
AttachOp? attach_op = null; ;
vm.begin_attaching += on_begin_attaching;
vm.end_attaching += on_end_attaching;
vm.SubscribeDisposed(() => {
if (attach_op.HasValue) {
vm.right_panel_width = attach_op.Value.raw_right_panel_width;
vm.fills_slave_grid_is_visible = attach_op.Value.fills_slave_grid_is_visible;
}
vm.begin_attaching -= on_begin_attaching;
vm.end_attaching -= on_end_attaching;
});
async void on_begin_attaching() {
if (attach_op.HasValue) {
// begin-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
return;
}
attach_op = new AttachOp(vm.algo_editor_is_visible, vm.fills_slave_grid_is_visible, vm.raw_right_panel_width);
vm.fills_slave_grid_is_visible = false;
vm.algo_editor_is_visible = true;
double target_width = Math.Max(ActualWidth * AttachOrderParamsPanelWidthRatio, MinAttachOrderParamsPanelWidth);
if (vm.right_panel_width < target_width) {
foreach (double incr in _get_incremental_widths(attach_op.Value.right_panel_is_visible ? attach_op.Value.raw_right_panel_width : 0, target_width)) {
vm.right_panel_width = incr;
await Task.Delay(delay_ms);
if (!attach_op.HasValue)
break;
}
}
}
async void on_end_attaching() {
if (!attach_op.HasValue) {
// end-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
return;
}
var incr_widths = _get_incremental_widths(vm.right_panel_width, attach_op.Value.raw_right_panel_width);
vm.algo_editor_is_visible = attach_op.Value.right_panel_is_visible;
vm.fills_slave_grid_is_visible = attach_op.Value.fills_slave_grid_is_visible;
vm.right_panel_width = attach_op.Value.raw_right_panel_width;
attach_op = null;
foreach (double incr in incr_widths) {
vm.right_panel_width = incr;
await Task.Delay(delay_ms);
if (attach_op.HasValue)
break;
}
}
}
static IEnumerable<double> _get_incremental_widths(double from, double to) {
var list = new List<double> { to };
int incr = 1;
if (from < to) {
do {
list.Add(to - (incr * 0.6666));
list.Add(to - incr);
from += incr;
incr += incr;
} while (from < to);
} else if (from > to) {
do {
list.Add(to + incr);
incr += incr;
to += incr;
} while (from > to);
}
for (int i = list.Count - 1; i >= 0; i--) {
yield return list[i];
}
}
struct AttachOp {
public readonly bool right_panel_is_visible, fills_slave_grid_is_visible;
public readonly double raw_right_panel_width;
public AttachOp(bool right_panel_is_visible, bool fills_slave_grid_is_visible, double raw_right_panel_width) {
this.right_panel_is_visible = right_panel_is_visible;
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
this.raw_right_panel_width = raw_right_panel_width;
}
}
public static class VisualStateNames {
public static class FillsPanel {
public const string GroupName = nameof(FillsPanel);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class AlgoAttachment {
public const string GroupName = nameof(AlgoAttachment);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
}
}
@@ -0,0 +1,142 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<ControlTemplate x:Key="OrderParamsPanelTemplate" TargetType="usp:OrderParamsPanel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<usp:PriceEntryControl
ContractSpec="{Binding ContractSpec, RelativeSource={RelativeSource TemplatedParent}}"
Value="{Binding Price, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ToolTip="Price"
/>
<Button
x:Name="best_bid_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestBidPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestBidPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
<Button
x:Name="best_ask_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestAskPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestAskPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<c:TickUpDownNullableUInt Value="{Binding Size, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Size"/>
<Button
x:Name="best_bid_size_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Content="{Binding BestBidSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}}"
/>
<Button
x:Name="best_ask_size_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Content="{Binding BestAskSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}}"
/>
<ItemsControl Grid.Row="2" Grid.RowSpan="2" Margin="-2,-2,-2,0" ItemsSource="{Binding SizePresets, RelativeSource={RelativeSource TemplatedParent}}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Focusable="False"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
HorizontalAlignment="Stretch"
Margin="2,4,2,0"
Content="{Binding}"
Focusable="False"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Row="2" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Margin="0,2,0,4" ItemsSource="{Binding Accounts, RelativeSource={RelativeSource TemplatedParent}}" SelectedItem="{Binding SelectedAccount, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Choose Account"/>
<ComboBox Grid.Row="1">
<ComboBox.Style>
<Style TargetType="ComboBox" BasedOn="{StaticResource DisabledPlaceholderComboBox}">
<Setter Property="ItemsSource">
<Setter.Value>
<x:ArrayExtension Type="{x:Type sys:String}">
<sys:String>Time In Force (N/A)</sys:String>
</x:ArrayExtension>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</Grid>
<usp:AlgoEditorView x:Name="algo_editor" Grid.Row="4" DataContext="{Binding AlgoVM, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</ControlTemplate>
<Style TargetType="usp:OrderParamsPanel">
<Setter Property="Template" Value="{StaticResource OrderParamsPanelTemplate}"/>
</Style>
</ResourceDictionary>
@@ -0,0 +1,188 @@
using bts.api;
using mbts.spark;
using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.spark {
class OrderParamsPanel : Control {
public const string
BestBidPriceButtonStyleKey = nameof(BestBidPriceButtonStyleKey),
BestBidSizeButtonStyleKey = nameof(BestBidSizeButtonStyleKey),
BestAskPriceButtonStyleKey = nameof(BestAskPriceButtonStyleKey),
BestAskSizeButtonStyleKey = nameof(BestAskSizeButtonStyleKey),
SizeCalculatorButtonStyleKey = nameof(SizeCalculatorButtonStyleKey);
public static readonly RoutedCommand SetPriceCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler SetPriceExecuted =
(s, e) => {
if (s is OrderParamsPanel opp)
opp.SetCurrentValue(PriceProperty, ConvertUtils.try_convert(e.Parameter, out double d) ? d : double.NaN);
};
public static readonly CanExecuteRoutedEventHandler
SetPriceCanExecute = (s, e) => e.CanExecute = e.Parameter is double,
SetSizeCanExecute = (s, e) => e.CanExecute = e.Parameter is uint;
static OrderParamsPanel() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(OrderParamsPanel), new FrameworkPropertyMetadata(typeof(OrderParamsPanel)));
}
#region Price
public static readonly DependencyProperty PriceProperty =
WPFHelper.create_dp<double, OrderParamsPanel>(nameof(Price), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, double.NaN);
public double Price {
get => (double)GetValue(PriceProperty);
set => SetValue(PriceProperty, value);
}
#endregion
#region Size
public static readonly DependencyProperty SizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(Size), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public uint? Size {
get => GetValue(SizeProperty) as uint?;
set => SetValue(SizeProperty, value);
}
#endregion
#region SizePresets
public static readonly DependencyProperty SizePresetsProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(SizePresets), Enumerable.Empty<string>());
public IEnumerable SizePresets {
get => GetValue(SizePresetsProperty) as IEnumerable;
set => SetValue(SizePresetsProperty, value);
}
#endregion
#region BestBidPrice
public static readonly DependencyProperty BestBidPriceProperty =
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestBidPrice));
public double? BestBidPrice {
get => GetValue(BestBidPriceProperty) as double?;
set => SetValue(BestBidPriceProperty, value);
}
#endregion
#region BestBidSize
public static readonly DependencyProperty BestBidSizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestBidSize));
public uint? BestBidSize {
get => GetValue(BestBidSizeProperty) as uint?;
set => SetValue(BestBidSizeProperty, value);
}
#endregion
#region BestAskPrice
public static readonly DependencyProperty BestAskPriceProperty =
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestAskPrice));
public double? BestAskPrice {
get => GetValue(BestAskPriceProperty) as double?;
set => SetValue(BestAskPriceProperty, value);
}
#endregion
#region BestAskSize
public static readonly DependencyProperty BestAskSizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestAskSize));
public uint? BestAskSize {
get => GetValue(BestAskSizeProperty) as uint?;
set => SetValue(BestAskSizeProperty, value);
}
#endregion
#region Accounts
public static readonly DependencyProperty AccountsProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(Accounts), Enumerable.Empty<string>());
public IEnumerable Accounts {
get => GetValue(AccountsProperty) as IEnumerable;
set => SetValue(AccountsProperty, value);
}
#endregion
#region SelectedAccount
public static readonly DependencyProperty SelectedAccountProperty =
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedAccount));
public object SelectedAccount {
get => GetValue(SelectedAccountProperty);
set => SetValue(SelectedAccountProperty, value);
}
#endregion
#region TimeInForces
public static readonly DependencyProperty TimeInForcesProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(TimeInForces), Enumerable.Empty<string>());
public IEnumerable TimeInForces {
get => GetValue(TimeInForcesProperty) as IEnumerable;
set => SetValue(TimeInForcesProperty, value);
}
#endregion
#region SelectedTimeInForce
public static readonly DependencyProperty SelectedTimeInForceProperty =
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedTimeInForce));
public object SelectedTimeInForce {
get => GetValue(SelectedTimeInForceProperty);
set => SetValue(SelectedTimeInForceProperty, value);
}
#endregion
#region Security
public static readonly DependencyProperty SecurityProperty =
WPFHelper.create_dp<SecurityImmutable?, OrderParamsPanel>(
nameof(Security),
(o, v) => o.CoerceValue(ContractSpecProperty));
public SecurityImmutable? Security {
get => GetValue(SecurityProperty) as SecurityImmutable?;
set => SetValue(SecurityProperty, value);
}
#endregion
#region ContractSpec
public static readonly DependencyProperty ContractSpecProperty =
WPFHelper.create_dp<ContractSpecificationImmutable?, OrderParamsPanel>(
nameof(ContractSpec),
null,
(d, base_value) => d.Security?.ContractSpec ?? base_value ?? SecurityDataHelper.Defaults.ContractSpecificationImmutable);
public ContractSpecificationImmutable? ContractSpec {
get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?;
set => SetValue(ContractSpecProperty, value);
}
#endregion
#region AlgoVM
public static readonly DependencyProperty AlgoVMProperty =
WPFHelper.create_dp<AlgoDefinitionVM, OrderParamsPanel>(
nameof(AlgoVM),
(o, v) => o._algo_editor?.SetCurrentValue(VisibilityProperty, v != null ? Visibility.Visible : Visibility.Collapsed));
public AlgoDefinitionVM AlgoVM {
get => GetValue(AlgoVMProperty) as AlgoDefinitionVM;
set => SetValue(AlgoVMProperty, value);
}
#endregion
AlgoEditorView _algo_editor;
public override void OnApplyTemplate() {
_algo_editor = GetTemplateChild("algo_editor") as AlgoEditorView;
_algo_editor?.SetCurrentValue(VisibilityProperty, AlgoVM != null ? Visibility.Visible : Visibility.Collapsed);
}
}
}
@@ -85,7 +85,7 @@ namespace bts.shell {
void _on_orders(TableUpdate<(SecurityImmutable?,OrderRecordImmutable),uint> update) {
// must update notification_service_data_cache first, some of the predicate/triggers (esp. ToastNotifications) will use the notification_service_data_cache to lookup order-history
_data_cache.update(update.as_iincremental());
foreach (var o in update.added)
foreach (var o in update.added.Concat(update.changed))
_order_notifications.process_item(o);
}
@@ -79,10 +79,12 @@ namespace mbts.spark {
RequestClose?.Invoke(this, new EventArgs());
}
});
bbo = new BBOVM();
_dispose = Disposable.Create(
size_calculator,
properties_vm,
bbo = new BBOVM(security_md_source),
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
bbo,
order_source.OnDispatcher().Subscribe(ort => {
key = ort.order.Key;
if (DoubleHelper.IsNaNOrInfinity(price))
+3 -1
View File
@@ -49,7 +49,7 @@ namespace mbts.spark {
) {
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
bbo = new BBOVM(security_md_source);
bbo = new BBOVM();
send_order = new DelegateCommand<Side>(side => {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
@@ -59,6 +59,8 @@ namespace mbts.spark {
});
set_price_command = new DelegateCommand<double>(set_price);
_dispose = Disposable.Create(
bbo,
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
size_calculator = new CalculatorLogic(0, calculator_buttons),
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
);
@@ -2,6 +2,7 @@
using bts.api.core;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui.controls;
@@ -83,25 +84,18 @@ namespace bts.spark {
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
double old_price = old_order.State.Price, new_price = new_order.State.Price;
uint old_size = old_order.State.Quantity, new_size = new_order.State.Quantity;
string modified_size_partial_description(int working_size) {
return $"size from {old_size} to {new_size}, working {working_size}";
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
return StringBuilderEx.join(prefix, change_descs());
//
string prefix() => $"({old_order.Key}) Changed";
IEnumerable<string> change_descs() {
if (old_price != new_price)
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
if (old_size != new_size)
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
if (old_attached_target_key != new_attached_target_key)
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
}
string modified_price_partial_description() {
return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
}
if (old_price != new_price || old_size != new_size) {
StringBuilder sb = new StringBuilder($"({old_order.Key}) Changed ");
if (old_price != new_price) {
sb.Append(modified_price_partial_description());
if (old_size != new_size)
sb.Append($", {modified_size_partial_description(new_order.working_quantity())}");
}
else
sb.Append(modified_size_partial_description(new_order.working_quantity()));
return sb.ToString();
}
else
return string.Empty;
}
static string _header(uint key) => $"({key})";
@@ -184,21 +178,31 @@ namespace bts.spark {
format_cancel_error(order, security?.ContractSpec, error));
}
internal void update_order(OrderRecordImmutable order, double price, uint size) {
internal void update_order(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
_log_order_update(order, price, size, algo_changed, "change from");
internal void update_order_failed(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
_log_order_update(order, price, size, algo_changed, "failed to change from");
void _log_order_update(OrderRecordImmutable order, double price, uint size, bool algo_changed, string change_desc_prefix) {
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
// nothing actually changed
return;
}
const string algo_change_text = "change algo parameters";
var security = try_get_security(order.Def.SecurityKey);
var spec = security?.ContractSpec;
log_status(ActivityLogCategory.modify,
security,
$"({order.Key}) change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
var desc_builder = new StringBuilderEx();
if (price != order.price() || size != order.working_quantity()) {
desc_builder.Add(order.State.Quantity.ToString(), "@", spec.format(order.State.Price), "to ", size.ToString(), "@", spec.format(price));
if (algo_changed)
desc_builder.Add("; ", algo_change_text);
} else if (algo_changed) {
desc_builder.Add(algo_change_text);
}
desc_builder.Insert(0, $"{change_desc_prefix} ");
desc_builder.Insert(0, $"({order.Key}) ");
log_status(ActivityLogCategory.modify, security, desc_builder.ToString());
}
internal void update_order_failed(OrderRecordImmutable order, double price, uint size) {
var security = try_get_security(order.Def.SecurityKey);
var spec = security?.ContractSpec;
log_status(ActivityLogCategory.modify,
security,
$"({order.Key}) failed to change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
}
}
}
@@ -346,7 +346,9 @@ namespace mbts.spark {
_time_in_force.observe_current(start_immediately);
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
_selected_algo_template.observe_current(start_immediately).Select(t => t.algo_root);
_model.algo_model.templates_enabled() ?
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
public OrderDefinition create(Side side) {
if (HasErrors)
@@ -11,7 +11,7 @@ using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel> {
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel>, IObserver<AlgoEditingPermissions> {
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
readonly VersionContainer<AlgoDefi> _definition;
AlgoFormats _format;
@@ -37,10 +37,7 @@ namespace mbts.spark {
impl.is_expanded = value;
}
}
public bool can_edit_values => impl?.can_edit_values ?? false;
public bool can_edit_structure => impl?.can_edit_structure ?? false;
public bool can_save_values => impl?.can_save_values ?? false;
public bool can_save_structure => impl?.can_save_structure ?? false;
public AlgoEditingPermissions editing_permissions => _editing_permissions;
public string display_name => impl?.display_name ?? "(Default)";
public AlgoDefi definition => _definition.value;
public ObservableCollection<IAlgoSelector> child_algo_selectors { get; } = new ObservableCollection<IAlgoSelector>();
@@ -48,10 +45,6 @@ namespace mbts.spark {
public void assign_to(AlgoDef value) => value.MergeFrom(definition.CloneToMutable());
public bool key_match(KeyEventArgs e) => false;
public void load_from(AlgoDefi value) => impl?.load_from(value);
public void set_permissions(AlgoEditingPermissions editing_permissions) {
_editing_permissions = editing_permissions;
impl?.set_permissions(editing_permissions);
}
public Task<AlgoDefi?> load_from_async(AlgoDefi definition) =>
impl?.load_from_async(definition) ?? Task.FromResult<AlgoDefi?>(definition);
@@ -98,6 +91,13 @@ namespace mbts.spark {
_on_impl_definition_changed();
}
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) {
Set(ref _editing_permissions, editing_permissions, nameof(this.editing_permissions));
(impl as IObserver<AlgoEditingPermissions>)?.OnNext(editing_permissions);
}
void _on_impl_definition_changed() {
var orig_def = definition;
_definition.increment_version();
@@ -107,4 +107,4 @@ namespace mbts.spark {
RaisePropertyChanged(nameof(order_type), nameof(meta));
}
}
}
}
@@ -1,5 +1,9 @@
using bts.api;
using bts.api.orderman;
using bts.utils;
using Google.Protobuf.Reflection;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -8,13 +12,18 @@ using System.Reactive.Linq;
namespace mbts.spark {
[Export]
public class DynamicOrderHelper
{
class DynamicOrderHelper {
public readonly IMaskIndexInt32<string> dynamic_type_index;
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
readonly RegisteredDynamicOrderTypes _dynamic_types;
[ImportingConstructor]
private DynamicOrderHelper(OrderService order_service)
{
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
dynamic_type_index = new MaskIndexInt32<string>(_dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType).ToList());
}
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
@@ -28,11 +37,52 @@ namespace mbts.spark {
foreach(var p in d.ParamDesc)
dorder.Dynamic.OrderParams.Add( p.ParamName, new DynamicOrderParam() );
defs.Add(new CustomOrderDefinitionImmutable(dorder));
var de = get_descriptor("");
}
}
internal DynamicOrderDescriptor get_descriptor(string dynamic_type) =>
_dynamic_types.DynamicOrderDesc.FirstOrDefault(d => d.DynamicOrderType == dynamic_type);
_dynamic_types.DynamicOrderDesc.FirstOrDefault(d => d.DynamicOrderType == dynamic_type) ?? new DynamicOrderDescriptor();
internal DynamicOrderDefinition create_def(string dynamic_type) {
var desc = get_descriptor(dynamic_type);
var def = new DynamicOrderDefinition { DynamicOrderType = dynamic_type };
add_dynamic_params(ref def);
return def;
}
internal void add_dynamic_params(ref DynamicOrderDefinition dynamic_def) {
var desc = get_descriptor(dynamic_def.DynamicOrderType);
foreach (var p in desc.ParamDesc) {
if (_is_child_algo_desc(p)) {
dynamic_def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
} else if (_is_value_param_desc(p)) {
var value_case = _get_value_case(p.ParamType.Primitive);
if (_descriptor_case_index.TryGetValue(value_case, out (FieldDescriptor fd, Type type) tup)) {
var dop = new DynamicOrderParam();
tup.fd.Accessor.SetValue(dop, ConvertUtils.get_default(tup.type));
dynamic_def.OrderParams[p.ParamName] = dop;
}
}
}
}
static DynamicOrderParam.ValueOneofCase _get_value_case(DynamicOrderPrimitiveDescriptor primitive_descriptor_case) {
switch (primitive_descriptor_case) {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return DynamicOrderParam.ValueOneofCase.AsBool;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DynamicOrderParam.ValueOneofCase.AsBool;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return DynamicOrderParam.ValueOneofCase.AsInt32;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return DynamicOrderParam.ValueOneofCase.AsUint32;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return DynamicOrderParam.ValueOneofCase.AsString;
default: throw ExceptionBuilder.Argument.unexpected_case(primitive_descriptor_case, nameof(primitive_descriptor_case));
}
}
static bool _is_child_algo_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
desc.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef;
static bool _is_value_param_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
}
}
}
@@ -1,28 +1,36 @@
using bts.api.orderman;
using mbts.collections;
using mbts.ui;
using mbts.ui.shell;
using mbts.spark.utilities;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class DynamicOrderVM : OrderTypeVMBase<DynamicOrderDefinition, DynamicOrderDefinitionImmutable> {
readonly ObservableKeyedCollection<DynamicOrderParamVM, string> _parameters = new ObservableKeyedCollection<DynamicOrderParamVM, string>(vm => vm.descriptor.ParamName);
readonly Property<string> _dyn_type;
OrdersModel _model;
bool _active;
public DynamicOrderVM(AlgoKey key, string dyn_type, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_dyn_type = add_property(nameof(this.dyn_type), DynamicOrderDefinition.DynamicOrderTypeFieldNumber, dyn_type);
_parameters = new ObservableKeyedCollection<DynamicOrderParamVM, string>(vm => vm.descriptor.ParamName);
_resources.AddRange(
_dyn_type.Subscribe(_ => _reset_dynamic_types()),
_dyn_type.Subscribe(_ => {
_sync_dynamic_params();
sync_selectors();
}),
ObservableAutoWrapper.connect_item_hooks(_parameters.AsCollection(), pvm => {
add_property(pvm.descriptor.ParamName, mdef => mdef.OrderParams[pvm.descriptor.ParamName] = pvm.param, mdef => {
if (mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p))
pvm.param = p;
});
return Disposable.Create(() => remove_property(pvm.descriptor.ParamName));
}),
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
p.PropertyChanged += on_param_prop_changed;
return Disposable.Create(() => p.PropertyChanged -= on_param_prop_changed);
@@ -31,103 +39,26 @@ namespace mbts.spark {
}
public string dyn_type => _dyn_type.Value;
public ObservableCollection<DynamicOrderParamVM> parameters {
get {
_ensure_init();
return _parameters;
public ObservableCollection<DynamicOrderParamVM> parameters => _parameters;
protected override void on_model_internal() =>
_sync_dynamic_params();
protected override IEnumerable<ChildAlgoData> get_selector_datum() =>
OrderDataHelper.get_child_algo_data(new CustomOrderDefinition {
Dynamic = model?.dynamic_helper?.create_def(dyn_type) ?? new DynamicOrderDefinition()
});
void _sync_dynamic_params() {
if (model != null) {
var desc = model.dynamic_helper.get_descriptor(dyn_type);
_parameters.SyncWithDictionary(desc.ParamDesc.Where(_is_value_param_desc).ToDictionary(p => p.ParamName), DynamicOrderParamVM.create);
}
}
protected override void assign_to_internal(DynamicOrderDefinition dynamic_def) {
foreach (var vm in _parameters.Values)
dynamic_def.OrderParams[vm.descriptor.ParamName] = vm.param;
}
protected override void load_from_internal(DynamicOrderDefinitionImmutable dyn_defi) {
foreach (var p in _parameters) {
if (dyn_defi.OrderParams.TryGetValue(p.descriptor.ParamName, out DynamicOrderParam dop)) {
p.param = dop;
} else {
p.param.ClearValue();
}
}
}
protected override async Task on_load_from_async(CustomOrderDefinitionImmutable defi) {
await Wait.until_true(() => _model != null, TimeSpan.FromSeconds(60));
_ensure_init();
}
void _ensure_init() {
if (!_active) {
_active = true;
// lazy initialization is required to avoid a stack overflow (otherwise, the algo-hierarchy would grow recursively without stopping).
_reset_dynamic_types();
}
}
protected override void on_model_internal(OrdersModel model) {
_model = model;
_reset_dynamic_types();
}
void _reset_dynamic_types() {
if (!_active)
return;
var value_descs = new Dictionary<string, DynamicOrderParamDescriptor>();
var child_descs = new Dictionary<string, DynamicOrderParamDescriptor>();
if (_model != null) {
var desc = _model.dynamic_helper.get_descriptor(dyn_type);
if (desc != null) {
foreach (var p in desc.ParamDesc) {
if (_is_value_param_desc(p)) {
value_descs[p.ParamName] = p;
} else if (_is_child_algo_desc(p)) {
child_descs[p.ParamName] = p;
}
}
}
}
_parameters.SyncWithDictionary(value_descs, DynamicOrderParamVM.create);
using (reset_selectors()) {
foreach (var kv in child_descs) {
add_selector(kv.Key, get_child, set_child);
// local methods
CustomOrderDefinitionImmutable? get_child(DynamicOrderDefinitionImmutable dyn_defi) {
if (dyn_defi.OrderParams.TryGetValue(kv.Key, out DynamicOrderParam p) && p.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
return new CustomOrderDefinitionImmutable(p.AsOrderDef);
return null;
}
void set_child(DynamicOrderDefinition dyn_def, CustomOrderDefinition child) =>
dyn_def.OrderParams[kv.Key] = new DynamicOrderParam { AsOrderDef = child };
}
}
}
static bool _is_child_algo_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
desc.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef;
static bool _is_value_param_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
public class SelectorVM : ViewModelBase {
readonly IAlgoSelector _selector;
public SelectorVM(IAlgoSelector selector) {
_selector = selector;
_selector.selected_algo_changed += _on_selected_algo_changed;
}
public IAlgoVM selected_algo { get => _selector.selected_algo; set => _selector.selected_algo = value; }
public ObservableCollection<IAlgoVM> algos => _selector.algos;
public void Dispose() =>
_selector.selected_algo_changed -= _on_selected_algo_changed;
void _on_selected_algo_changed(IAlgoVM oldval, IAlgoVM newval) =>
RaisePropertyChanged(nameof(selected_algo));
}
}
}
}
@@ -22,17 +22,13 @@ namespace mbts.spark {
CustomOrderDefinition.CustomOneofCase order_type { get; }
bool is_bts_type { get; }
OrderMetadata meta { get; }
AlgoEditingPermissions editing_permissions { get; }
bool can_expand { get; }
bool is_expanded { get; set; }
bool can_edit_values { get; }
bool can_edit_structure { get; }
bool can_save_values { get; }
bool can_save_structure { get; }
string display_name { get; }
CustomOrderDefinitionImmutable definition { get; }
ObservableCollection<IAlgoSelector> child_algo_selectors { get; }
bool key_match(KeyEventArgs e);
void set_permissions(AlgoEditingPermissions editing_permissions);
Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition);
}
@@ -91,4 +87,4 @@ namespace mbts.spark {
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
}
}
}
}
@@ -95,50 +95,39 @@ namespace mbts.spark {
public static FieldDescriptor find_descriptor<T>() =>
_algo_type_index.TryGetValue(typeof(T), out FieldDescriptor fd) ?
fd : throw new NotSupportedException($"No {nameof(AlgoDef)} {nameof(FieldDescriptor)} exists for type '{typeof(T)}.'");
public static Func<TMutable, TImmutable> immutable_factory<TMutable, TImmutable>() where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
_ImmutableActivator<TMutable, TImmutable>.factory;
static class _ImmutableActivator<TMutable, TImmutable> where TMutable : IMessage, new() where TImmutable : IImmutableMessage {
public static readonly Func<TMutable, TImmutable> factory =
m => (TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
}
}
#region OrderTypeVM Base Classes
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM, IObserver<SecurityMD?>, IObserver<OrdersModel> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
/// TODO: maybe use explicit delegates rather than rely on reflection <see cref="_descriptor"/> <see cref="_create_immutable"/>.
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM,
IObserver<SecurityMD?>,
IObserver<SecurityImmutable?>,
IObserver<OrdersModel>,
IObserver<AlgoEditingPermissions> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
static readonly FieldDescriptor _descriptor = OrderTypeVMUtils.find_descriptor<TMutable>();
static readonly Func<TMutable, TImmutable> _create_immutable = OrderTypeVMUtils.immutable_factory<TMutable, TImmutable>();
readonly IDictionary<string, object> _children_vm = new ExpandoObject();
protected CompositeDisposable _resources = new CompositeDisposable();
readonly List<(Action<TMutable> assign, Action<TMutable> load)> _auto_props = new List<(Action<TMutable> assign, Action<TMutable> load)>();
readonly ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string> _auto_props = new ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string>(t => t.name);
readonly ObservableKeyedCollection<AlgoSelectorData, string> _child_algo_selector_data = new ObservableKeyedCollection<AlgoSelectorData, string>(sd => sd.prop_name);
readonly ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData> _child_algo_selectors;
readonly Property<bool> _is_expanded, _can_save_values, _can_save_structure;
readonly Property<bool> _is_expanded;
readonly DeferrableActionWrapper _def_change_notifier;
SecurityInput _security_input;
AlgoEditingPermissions _editing_permissions;
IDisposable _pending_load;
protected OrderTypeVMBase(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions) {
this.id = id;
this.display_name = display_name;
this.editing_permissions = editing_permissions;
_editing_permissions = editing_permissions;
_child_algo_selectors = new ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData>(_child_algo_selector_data, sd => sd.selector);
_can_save_values = backing(nameof(can_save_values), true);
_can_save_structure = backing(nameof(can_save_structure), true);
_is_expanded = backing<bool>(nameof(is_expanded));
foreach (var c in OrderDataHelper.get_child_algo_data(order_type)) {
_children_vm[c.name] = add_selector(c.name, c.descriptor);
}
meta = OrderDataHelper.get_metadata(order_type);
_def_change_notifier = new DeferrableActionWrapper(() => definition_changed?.Invoke());
PropertyChanged += (s, e) => {
switch (e.PropertyName) {
// ignore props that don't affect the resultant AlgoDef
case nameof(is_expanded):
case nameof(can_edit_values):
case nameof(can_edit_structure):
case nameof(can_save_values):
case nameof(can_save_structure):
return;
default:
_def_change_notifier.execute();
@@ -149,10 +138,7 @@ namespace mbts.spark {
CompositeDisposable def_sub = null;
sd.selector.selected_algo_changed += subscribe_new_algo;
return new CompositeDisposable(
ObservableAutoWrapper.connect_item_hooks(sd.selector.algos, a => {
a.set_permissions(this.editing_permissions);
return a as IDisposable ?? Disposable.empty;
}, true)) {
ObservableAutoWrapper.try_dispose_on_removed(sd.selector.algos, true)) {
() => {
sd.selector.selected_algo_changed -= subscribe_new_algo;
Disposable.dispose(ref def_sub);
@@ -170,45 +156,38 @@ namespace mbts.spark {
}, true));
}
public AlgoEditingPermissions editing_permissions { get; private set; }
public event Action definition_changed;
event Action<SecurityImmutable?> _security_changed;
// core
public AlgoDefi definition => new AlgoDefi(this.created_assigned());
public AlgoType order_type => OrderDataHelper.get_order_type(new TMutable());
public bool is_bts_type => display_name.StartsWith("BTS");
public AlgoKey id { get; }
// display
public bool is_bts_type => display_name.StartsWith("BTS");
public string display_name { get; private set; }
public OrderMetadata meta => OrderDataHelper.get_metadata(order_type);
public virtual bool can_expand => !meta.NoProps;
public virtual bool is_expanded {
get => _is_expanded.Value;
set => _is_expanded.Value = value;
}
public OrderMetadata meta { get; }
// child algos
public dynamic children_vm => _children_vm;
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
public bool can_edit_values => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditValues);
public bool can_edit_structure => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditStructure);
public bool can_save_values => _can_save_values.Value;
public bool can_save_structure => _can_save_structure.Value;
// editing permissions
public AlgoEditingPermissions editing_permissions { get => _editing_permissions; private set => Set(ref _editing_permissions, value); }
// expand
public virtual bool can_expand => !meta.NoProps;
public virtual bool is_expanded { get => _is_expanded.Value; set => _is_expanded.Value = value; }
// protected (observed props)
protected OrdersModel model { get; private set; }
protected SecurityMD? security_md { get; private set; }
protected SecurityImmutable? security => security_md?.security;
protected SecurityMD? security_md => _security_input.security_md;
protected SecurityImmutable? security => _security_input.security;
public void assign_to(AlgoDef value) {
var algo = new TMutable();
foreach (var (assign, load) in _auto_props)
foreach (var (name, assign, load) in _auto_props)
assign(algo);
_descriptor.Accessor.SetValue(value, algo);
foreach (var sd in _child_algo_selector_data) {
if (sd.selector.selected_algo != null)
sd.set_order(value, sd.selector.selected_algo.created_assigned());
}
assign_to_internal(algo);
}
public void set_permissions(AlgoEditingPermissions editing_permissions) {
using (detect_property_changes(nameof(can_edit_values), nameof(can_edit_structure), nameof(can_save_values), nameof(can_save_structure)))
this.editing_permissions = editing_permissions;
on_permissions_changed();
}
public bool key_match(KeyEventArgs key) =>
@@ -217,25 +196,31 @@ namespace mbts.spark {
public void load_from(AlgoDefi value) {
if (value.CustomCase != order_type)
return;
using (_def_change_notifier.defer_execution()) {
foreach (var sd in _child_algo_selector_data) {
var inner_def = sd.get_order(value);
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
continue;
}
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
vm.load_from(inner_def.Value);
sd.selector.selected_algo = vm;
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
var security_src = Observable.Create<SecurityImmutable?>(o => {
o.OnNext(security);
_security_changed += o.OnNext;
return Disposable.Create(() => _security_changed -= o.OnNext);
});
Disposable.replace(ref _pending_load,
Disposable.Create(
ObservableAutoWrapper.connect_item_hooks(
_child_algo_selector_data.AsCollection(),
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
void selector_load_impl(AlgoSelectorData sd) {
var inner_def = sd.get_order(value);
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
return;
}
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
vm.load_from(inner_def.Value);
sd.selector.selected_algo = vm;
}
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
var algoi = _create_immutable(algom);
foreach (var (assign, load) in _auto_props)
load(algom);
load_from_internal(algoi);
}
}
@@ -254,92 +239,67 @@ namespace mbts.spark {
}
}
var algom = _descriptor.Accessor.GetValue(defi.CloneToMutable()) as TMutable ?? new TMutable();
var algoi = _create_immutable(algom);
foreach (var (assign, load) in _auto_props)
foreach (var (name, assign, load) in _auto_props)
load(algom);
load_from_internal(algoi);
return defi;
}
}
protected virtual Task on_load_from_async(AlgoDefi defi) => Task.CompletedTask;
protected virtual void assign_to_internal(TMutable value) { }
protected virtual void load_from_internal(TImmutable value) { }
protected void raise_definition_changed() =>
_def_change_notifier.execute();
protected virtual void validate() { }
protected virtual void on_permissions_changed() {
foreach (var vm in child_algo_selectors.SelectMany(s => s.algos.AsCollection()))
vm.set_permissions(editing_permissions);
}
protected override void Dispose(bool disposing) =>
Disposable.dispose(ref _resources);
#region Chidl Algo Selectors (add/remove/reset)
protected IAlgoSelector add_selector(string vm_prop_name, FieldDescriptor inner_desc) {
return add_selector(vm_prop_name, def => {
if (_descriptor.Accessor.GetValue(def.CloneToMutable()) is TMutable mval) {
if (inner_desc.Accessor.GetValue(mval) is AlgoDef inner_def)
return new AlgoDefi(inner_def);
}
return null;
}, (mdef, inner_def) => {
if (_descriptor.Accessor.GetValue(mdef) is TMutable mval)
inner_desc.Accessor.SetValue(mval, inner_def);
});
}
protected IAlgoSelector add_selector(string prop_name, Func<AlgoDefi, AlgoDefi?> get_order, Action<AlgoDef, AlgoDef> set_order) =>
add_selector(prop_name, get_order, set_order, (s, e) => validate());
protected IAlgoSelector add_selector(string prop_name, Func<AlgoDefi, AlgoDefi?> get_order, Action<AlgoDef, AlgoDef> set_order, EventHandler<DataErrorsChangedEventArgs> on_errors_changed) {
var rv = new AlgoSelector();
_child_algo_selector_data[prop_name] = new AlgoSelectorData(prop_name, rv, get_order, set_order);
return rv;
}
protected IAlgoSelector add_selector(string prop_name, Func<TImmutable, AlgoDefi?> get_algo, Action<TMutable, AlgoDef> set_algo) {
var rv = new AlgoSelector();
_child_algo_selector_data[prop_name] = new AlgoSelectorData(prop_name, rv, def => _descriptor.Accessor.GetValue(def.CloneToMutable()) is TMutable mval ? get_algo(_create_immutable(mval)) : null,
(def, child_def) => {
if (_descriptor.Accessor.GetValue(def) is TMutable mval)
set_algo(mval, child_def);
});
return rv;
}
protected bool remove_selector(string prop_name) =>
_child_algo_selector_data.Remove(prop_name);
/// TODO: maybe <see cref="_child_algo_selector_data"/> should just be a protected field?
/// Conversely, it is nice to hide these sort of implementation-specific details from inheriting classes.
/// Fwiw, as of 2/12/2019, the only class that needs the ability to add/remove selectors dynamically is <see cref="DynamicOrderVM"/>.
/// The other implementations simply add a whatever selectors they need in their constructor.
protected IDisposable reset_selectors() {
var to_remove = _child_algo_selector_data.Keys.ToList();
_child_algo_selector_data.CollectionChanged += on_change;
void on_change(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException($"{nameof(_child_algo_selector_data)} was reset; unable to continue processing incremental changes.");
default:
// catches Replace/Remove/Add ops (Add ops account for cases where the item was effectively replaced by a remove/re-add)
if (e.NewItems != null) {
foreach (AlgoSelectorData sd in e.NewItems)
to_remove.Remove(sd.prop_name);
}
break;
#region Child Algo Selectors
protected virtual IEnumerable<ChildAlgoData> get_selector_datum() =>
OrderDataHelper.get_child_algo_data(order_type);
protected void sync_selectors() {
using (reset()) {
foreach (var c in get_selector_datum()) {
_children_vm[c.name] = mk_selector(c.name, new Func<AlgoDefi, AlgoDefi?>(defi => {
var mchild = c.get_child(defi.CloneToMutable());
if (mchild != null)
return new AlgoDefi(mchild);
return null;
}),
c.assign_child);
}
_populate_selectors();
}
// local methods
IDisposable reset() {
var to_remove = _child_algo_selector_data.Keys.ToList();
_child_algo_selector_data.CollectionChanged += on_change;
void on_change(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException($"{nameof(_child_algo_selector_data)} was reset; unable to continue processing incremental changes.");
default:
// catches Replace/Remove/Add ops (Add ops account for cases where the item was effectively replaced by a remove/re-add)
if (e.NewItems != null) {
foreach (AlgoSelectorData sd in e.NewItems)
to_remove.Remove(sd.prop_name);
}
break;
}
}
return Disposable.Create(() => {
_child_algo_selector_data.CollectionChanged -= on_change;
foreach (var id in to_remove)
_child_algo_selector_data.Remove(id);
});
}
IAlgoSelector mk_selector(string prop_name, Func<AlgoDefi, AlgoDefi?> get_order, Action<AlgoDef, AlgoDef> set_order) {
var rv = new AlgoSelector();
_child_algo_selector_data[prop_name] = new AlgoSelectorData(prop_name, rv, get_order, set_order);
return rv;
}
return Disposable.Create(() => {
_child_algo_selector_data.CollectionChanged -= on_change;
foreach (var id in to_remove)
_child_algo_selector_data.Remove(id);
});
}
#endregion
@@ -347,7 +307,7 @@ namespace mbts.spark {
protected Property<T> add_property<T>(string prop_name, int target_field_number, T init_value = default) {
var prop = backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
_auto_props.Add((mval => {
_auto_props.Add((prop_name, mval => {
if (mval != null)
field_desc.Accessor.SetValue(mval, prop.Value);
}, mval => {
@@ -356,25 +316,14 @@ namespace mbts.spark {
return prop;
}
protected UserProperty<T> add_user_property<T>(string prop_name, int target_field_number, T init_value = default) {
var prop = user_backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
add_property(
mdef => ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(mdef)),
(mdef, val) => field_desc.Accessor.SetValue(mdef, val),
val => prop.bypass_setter(val),
() => prop.Value);
return prop;
}
protected Property<TImmutableValue> add_property<TMutableValue, TImmutableValue>(string prop_name, int target_field_number, TImmutableValue init_value = default) where TMutableValue : class, IMessage, new() where TImmutableValue : IImmutableMessage {
var prop = backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
var immutable_factory = OrderTypeVMUtils.immutable_factory<TMutableValue, TImmutableValue>();
add_property<TMutableValue>(
prop_name,
mdef => field_desc.Accessor.GetValue(mdef) as TMutableValue,
(mdef, mval) => field_desc.Accessor.SetValue(mdef, mval),
mval => prop.set(immutable_factory(mval ?? new TMutableValue())),
mval => prop.set(ProtoHelper.immutable_factory<TMutableValue, TImmutableValue>(mval ?? new TMutableValue())),
() => {
// TODO: perhaps CloneToMutable() should be a member of IImmutableMessage.
// this method makes things so simple for the caller...but this dynamic cast is a little sketch.
@@ -383,15 +332,37 @@ namespace mbts.spark {
return prop;
}
protected void add_property<TValue>(Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Action<TValue> set_local, Func<TValue> get_local) =>
_auto_props.Add((def => {
protected void add_property<TValue>(string prop_name, Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Action<TValue> set_local, Func<TValue> get_local) =>
_auto_props.Add((
prop_name,
def => {
assign_to_def(def, get_local());
}, def => {
set_local(get_from_def(def));
}));
protected virtual void on_model_internal(OrdersModel model) { }
protected virtual void on_security_md_internal(SecurityMD? security_md) { }
protected void add_property(string prop_name, Action<TMutable> assign, Action<TMutable> load) =>
_auto_props[prop_name] = (prop_name, assign, load);
protected UserProperty<T> add_user_property<T>(string prop_name, int target_field_number, T init_value = default) {
var prop = user_backing(prop_name, init_value);
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
add_property(
prop_name,
mdef => ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(mdef)),
(mdef, val) => field_desc.Accessor.SetValue(mdef, val),
val => prop.bypass_setter(val),
() => prop.Value);
return prop;
}
protected void remove_property(string prop_name) =>
_auto_props.Remove(prop_name);
#endregion
protected virtual void on_model_internal() { }
protected virtual void on_security_md_internal() { }
protected virtual void on_security_internal() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
@@ -402,36 +373,99 @@ namespace mbts.spark {
return;
}
this.model = model;
on_model_internal(model);
_reset();
using (_def_change_notifier.defer_execution()) {
on_model_internal();
sync_selectors();
}
}
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
this.security_md = security_md;
on_security_md_internal(security_md);
_reset();
using (_def_change_notifier.defer_execution())
using (_update_sec_input())
_security_input = _security_input.with_security_md(security_md);
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
// security
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? security) {
using (_def_change_notifier.defer_execution())
using (_update_sec_input())
_security_input = _security_input.with_security(security);
}
IDisposable _hook;
void _reset() {
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
void IObserver<SecurityImmutable?>.OnCompleted() { }
// editing permissions
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) => this.editing_permissions = editing_permissions;
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
IDisposable _update_sec_input() {
var old_sec = security;
var old_sec_md = security_md;
return Disposable.Create(() => {
if (security != old_sec) {
on_security_internal();
if (_editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditStructure))
_populate_selectors();
_security_changed?.Invoke(security);
}
if (security_md != old_sec_md)
on_security_md_internal();
});
}
IDisposable _selector_data_hooks;
void _populate_selectors() {
if (model != null && security.HasValue) {
var algo_data = model.factory.default_algos(security.Value);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
foreach (var sd in _child_algo_selectors)
sd.load_algo_data_recursive(algo_data, default_algo);
Disposable.replace(ref _hook, ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
}));
Disposable.replace(
ref _selector_data_hooks,
ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
s.load_algo_data_recursive(algo_data, default_algo);
return Disposable.empty;
}));
}
}
}
public IDisposable Subscribe(IObserver<OrdersModel> observer) => throw new NotImplementedException();
#endregion
// security input may come in 2 forms:
// 1) SecurityImmutable?
// 2) SecurityMD?
// This structure resolves conflicts that may arise from the different inputs
struct SecurityInput {
readonly SecurityMD? _raw_security_md;
readonly SecurityImmutable? _raw_security;
public readonly SecurityMD? security_md;
public readonly SecurityImmutable? security;
SecurityInput(SecurityImmutable? raw_security, SecurityMD? raw_security_md, SecurityImmutable? security, SecurityMD? security_md) {
_raw_security = raw_security;
_raw_security_md = raw_security_md;
this.security_md = security_md;
this.security = security;
}
public SecurityInput with_security(SecurityImmutable? security) =>
new SecurityInput(
security,
_raw_security_md,
security,
security.HasValue && _raw_security_md.HasValue && _raw_security_md.Value.security.Id != security.Value.Id ?
null : _raw_security_md);
public SecurityInput with_security_md(SecurityMD? security_md) =>
new SecurityInput(
_raw_security,
security_md,
security_md?.security ?? _raw_security,
security_md);
}
struct AlgoSelectorData {
@@ -453,14 +487,15 @@ namespace mbts.spark {
IReadOnlyDictionary<AlgoKey, AlgoData> _pending_data;
AlgoKey? _pending_default;
public AlgoSelector() =>
public AlgoSelector() {
algos_view = ObservableAutoWrapper.create_sorted_wrapper(algos, AlgoCollectionHelper.algo_vm_comparer);
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
}
public void load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
_pending_data = algo_data;
_pending_default = default_algo;
_sync_formats();
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
}
public event PropertyChange<IAlgoVM> selected_algo_changed;
@@ -1,4 +1,5 @@
using bts;
using bts.api;
using bts.api.orderman;
using mbts.ui.controls;
using mbts.ui.utility;
@@ -11,11 +12,9 @@ using SpreaderDefi = bts.api.orderman.SpreaderOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<SecurityMD?>, IObserver<OrdersModel> {
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<OrdersModel> {
readonly Property<SpreaderDefi?> _template;
readonly StrategyEditorWrapper _strategy_editor_wrapper;
OrdersModel _model;
SecurityMD? _security_md;
IDisposable _strategy_sub;
public SpreaderOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
@@ -23,7 +22,38 @@ namespace mbts.spark {
_template = backing<SpreaderDefi?>(nameof(template));
/// <see cref="_strategy_editor_wrapper"/> depends on <see cref="_template"/> (i.e. initialize the latter first).
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
concat_disposable(_strategy_editor_wrapper, _template.Subscribe(vals => validate()));
_resources.AddRange(_strategy_editor_wrapper, _template.Subscribe(vals => validate()));
// strategy
add_property(
nameof(SpreaderDef.ExecutionStrategy),
mdef => mdef.ExecutionStrategy,
(mdef, val) => mdef.ExecutionStrategy = val,
val => {
if (template.HasValue) {
template = template.Value.with(execution_strategy: val);
} else {
var mdef = new SpreaderDef {
Security = security?.CloneToMutable(),
ExecutionStrategy = val
};
template = new SpreaderDefi(mdef);
}
},
() => strategy_editor?.get_strategy().CloneToMutable());
// security
add_property(
nameof(SpreaderDef.Security),
mdef => mdef.Security,
(mdef, val) => mdef.Security = val,
val => {
if (template.HasValue) {
template = template.Value.with(security: val);
} else {
var mdef = new SpreaderDef { Security = val };
template = new SpreaderDefi(mdef);
}
},
() => security?.CloneToMutable());
validate();
}
@@ -43,39 +73,33 @@ namespace mbts.spark {
}
public bool is_loaded { get; private set; }
protected override void assign_to_internal(SpreaderDef value) =>
value.MergeFrom(template.HasValue ? template.Value.CloneToMutable() : new SpreaderDef());
protected override void load_from_internal(SpreaderDefi value) =>
_template.set(value);
protected override void validate() {
base.validate();
if (!template.HasValue)
set_error(nameof(template), "Waiting for security and/or strategy.", true);
else clear_error(nameof(template));
}
<<<<<<< HEAD
protected override void on_model_internal() => _reset_strategy_sub();
protected override void on_security_internal() => _reset_strategy_sub();
=======
void IObserver<SecurityMD?>.OnCompleted() { }
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
_security_md = security_md;
_reset_strategy_sub();
}
void IObserver<OrdersModel>.OnCompleted() { }
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
_model = model;
_reset_strategy_sub();
}
protected override void on_security_md_internal() => _reset_strategy_sub();
protected override void on_model_internal() => _reset_strategy_sub();
>>>>>>> master
void _reset_strategy_sub() {
Disposable.dispose(ref _strategy_sub);
if (_model != null) {
if (_security_md.HasValue) {
_strategy_sub = _model.spreader.security_strategy(_security_md.Value.security.Id)
.Select(strategy => new SpreaderDefi?(_model.factory.spreader_orderi(_security_md.Value.security, strategy)))
if (model != null) {
<<<<<<< HEAD
if (security.HasValue) {
_strategy_sub = model.spreader.security_strategy(security.Value.Id)
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security.Value, strategy)))
=======
if (security_md.HasValue) {
_strategy_sub = model.spreader.security_strategy(security_md.Value.security.Id)
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security_md.Value.security, strategy)))
>>>>>>> master
.OnDispatcher()
.Subscribe(s => {
is_loaded = true;
@@ -115,7 +139,7 @@ namespace mbts.spark {
}
}
OrdersModel _model => _owner._model;
OrdersModel _model => _owner.model;
SpreaderDefi? _template { get => _owner.template; set => _owner.template = value; }
public void Dispose() {
@@ -31,11 +31,6 @@ namespace mbts.spark {
public ContractSpecificationImmutable? spec => _security_md.spec_or_default();
protected override void load_from_internal(StopLimitDefi value) {
// TODO: the StopLimitOrderDefinition, StopLimitOrderDefinitionImmutable needs relative vs absolute prices to function as a useful template (talk to Pedro)
_stop_price.bypass_setter(value.StopPrice);
}
// no need to constantly poll the md, just check the inline props when the popup opens, and right before sending an order
// (if the user never looks at the popup, assume that the bid_price will be used)
// TODO: add settings so the trigger-price can default to something relative to the md, e.g "bid-price" or "ask-price - nticks" or "best-bid-price with a minimum size of 10"
@@ -16,14 +16,26 @@ namespace mbts.spark {
public TimeBoxOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
add_property(def => def.Start, (def, val) => def.Start = val, val => absolute_start = TimeTextTuple.FromDateTime(val.ToDateTime()), () => {
_validate(out DateTime? start_time, out DateTime? _);
return start_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
add_property(def => def.Stop, (def, val) => def.Stop = val, val => absolute_stop = TimeTextTuple.FromDateTime(val.ToDateTime()), () => {
_validate(out DateTime? _, out DateTime? stop_time);
return stop_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
// start
add_property(
nameof(TimeBoxOrderDefinition.Start),
def => def.Start,
(def, val) => def.Start = val,
val => absolute_start = TimeTextTuple.FromDateTime(val.ToDateTime()),
() => {
_validate(out DateTime? start_time, out DateTime? _);
return start_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
// stop
add_property(
nameof(TimeBoxOrderDefinition.Stop),
def => def.Stop,
(def, val) => def.Stop = val,
val => absolute_stop = TimeTextTuple.FromDateTime(val.ToDateTime()),
() => {
_validate(out DateTime? _, out DateTime? stop_time);
return stop_time.GetValueOrDefault().ToUniversalTime().ToGoogleTime();
});
validate();
}
@@ -1,5 +1,4 @@
using bts.api;
using bts.api.orderman;
using bts.api.orderman;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -31,13 +30,59 @@ namespace mbts.spark {
_is_priceout_checked = create_user_prop(nameof(is_priceout_checked), false);
_is_timeout_checked = create_user_prop(nameof(is_timeout_checked), false);
_timeout_secs = create_user_prop(nameof(timeout_secs), 10);
add_property(nameof(TriggerDef), assign, load);
validate();
//
// local methods
UserProperty<T> create_user_prop<T>(string name, T default_value = default) {
var rv = user_backing(name, default_value);
rv.Changed += validation_callback;
return rv;
}
void assign(TriggerDef trigger_def) {
trigger_def.TriggerAction = new TriggerAction();
if (security.HasValue)
trigger_def.TriggerAction.SecurityId = security.Value.Id;
trigger_def.TriggerCondition = new TriggerCondition() {
PriceType = trigger_price_type,
AbsolutePriceTrigger = new AbsolutePriceTrigger() { TriggerQuantity = new TriggerQuantity() },
};
trigger_def.ExitCondition = new TriggerExitCondition {
ConditionalCancel = is_priceout_checked,
TimeoutEnabled = is_timeout_checked,
TimeoutSeconds = (uint)(timeout_secs)
};
switch (order_price_case) {
case OrderpriceOneofCase.Price:
// 'fixed price' is no longer supported (ca. 03/04/2019)
goto case OrderpriceOneofCase.Payup;
case OrderpriceOneofCase.Payup:
trigger_def.TriggerAction.Price = double.NaN;
trigger_def.TriggerAction.Payup = new TriggerPayupPrice {
PayupTicks = payup_ticks,
PayupPriceType = payup_price_type
};
break;
}
}
void load(TriggerDef trigger_defi) {
if (trigger_defi.TriggerCondition is TriggerCondition c) {
trigger_price_type = c.PriceType;
}
if (trigger_defi.ExitCondition is TriggerExitCondition ec) {
is_priceout_checked = ec.ConditionalCancel;
is_timeout_checked = ec.TimeoutEnabled;
if (ec.TimeoutSeconds < int.MaxValue)
timeout_secs = (int)ec.TimeoutSeconds;
}
if (trigger_defi.TriggerAction is TriggerAction a) {
if (a.Payup is TriggerPayupPrice pp) {
order_price_case = OrderpriceOneofCase.Payup;
fixed_price = double.NaN;
payup_ticks = pp.PayupTicks;
payup_price_type = pp.PayupPriceType;
}
}
}
void validation_callback<T>(T oldval, T newval) => validate();
}
@@ -56,68 +101,20 @@ namespace mbts.spark {
public bool is_timeout_checked { get => _is_timeout_checked.Value; set => _is_timeout_checked.Value = value; }
public int timeout_secs { get => _timeout_secs.Value; set => _timeout_secs.Value = value; }
protected override void assign_to_internal(TriggerDef trigger_def) {
trigger_def.TriggerAction = new TriggerAction();
if (security.HasValue)
trigger_def.TriggerAction.SecurityId = security.Value.Id;
trigger_def.TriggerCondition = new TriggerCondition() {
PriceType = trigger_price_type,
AbsolutePriceTrigger = new AbsolutePriceTrigger() { TriggerQuantity = new TriggerQuantity() },
};
trigger_def.ExitCondition = new TriggerExitCondition {
ConditionalCancel = is_priceout_checked,
TimeoutEnabled = is_timeout_checked,
TimeoutSeconds = (uint)(timeout_secs)
};
switch (order_price_case) {
case OrderpriceOneofCase.Price:
// should already be null, setting it here for clarity
trigger_def.TriggerAction.Payup = null;
trigger_def.TriggerAction.Price = fixed_price;
break;
case OrderpriceOneofCase.Payup:
trigger_def.TriggerAction.Price = double.NaN;
trigger_def.TriggerAction.Payup = new TriggerPayupPrice {
PayupTicks = payup_ticks,
PayupPriceType = payup_price_type
};
break;
}
}
protected override void load_from_internal(TriggerDefi trigger_defi) {
if (trigger_defi.HasTriggerCondition) {
trigger_price_type = trigger_defi.TriggerCondition.PriceType;
}
if (trigger_defi.HasExitCondition) {
var exit_condition = trigger_defi.ExitCondition;
is_priceout_checked = exit_condition.ConditionalCancel;
is_timeout_checked = exit_condition.TimeoutEnabled;
if (exit_condition.TimeoutSeconds < int.MaxValue)
timeout_secs = (int)exit_condition.TimeoutSeconds;
}
if (trigger_defi.HasTriggerAction) {
var trigger_action = trigger_defi.TriggerAction;
if (trigger_action.HasPayup) {
order_price_case = OrderpriceOneofCase.Payup;
fixed_price = double.NaN;
var payup = trigger_action.Payup;
payup_ticks = payup.PayupTicks;
payup_price_type = payup.PayupPriceType;
} else {
order_price_case = OrderpriceOneofCase.Price;
fixed_price = trigger_action.Price;
}
}
}
void _refresh_md() {
protected override void on_security_md_internal() {
if (security.HasValue) {
security_md.Value.md.get_bbo(ref _bbo_ref, out co2.NativePriceSize bid, out co2.NativePriceSize ask);
_best_bid = bid.price;
_best_ask = ask.price;
security_md.Value.md.get_last_trade(ref _last_trade_ref, out co2.NativePriceSize ps, out DateTime ts, out ulong v);
}
if (double.IsNaN(fixed_price) || !_fixed_price.setter_called) {
switch (trigger_price_type) {
case TriggerPriceType.Bid: _fixed_price.bypass_setter(_best_bid); break;
case TriggerPriceType.Ask: _fixed_price.bypass_setter(_best_ask); break;
case TriggerPriceType.LastTradePrice: _fixed_price.bypass_setter(_last_trade_price); break;
}
}
}
protected override void validate() {
@@ -131,17 +128,6 @@ namespace mbts.spark {
}
assert(!is_timeout_checked || timeout_secs > 0, nameof(timeout_secs), "Please specify valid Timeout(secs).");
}
protected override void on_security_md_internal(SecurityMD? security_md) {
_refresh_md();
if (double.IsNaN(fixed_price) || !_fixed_price.setter_called) {
switch (trigger_price_type) {
case TriggerPriceType.Bid: _fixed_price.bypass_setter(_best_bid); break;
case TriggerPriceType.Ask: _fixed_price.bypass_setter(_best_ask); break;
case TriggerPriceType.LastTradePrice: _fixed_price.bypass_setter(_last_trade_price); break;
}
}
}
}
}
}
}
+23 -16
View File
@@ -86,7 +86,7 @@ namespace mbts.spark {
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
ot => ot.order.Key);
on_order_tuples = order_secs_algo_meta_join;
on_order_tuples = order_secs_algo_meta_join.Cache();
var on_fills =
_service.subscribei_fills()
.Naggle(interval, scheduler)
@@ -101,17 +101,14 @@ namespace mbts.spark {
(f, fk, o, ok) => new FillTuple(f, o),
(f, fk, ok) => new FillTuple(f, null),
f => f.fill.FillKey);
on_fill_tuples = fill_order_tuples_join;
on_fill_tuples = fill_order_tuples_join.Cache();
_dispose = Disposable.Create(
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
on_positions.Subscribe(_security_positions_demuxer),
order_secs_algo_meta_join.Connect(),
algo_meta_join.Connect(),
fill_order_tuples_join.Connect(),
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
_bus.subscribe(SparkTags.CrackOrders).Subscribe(x => crack_orders(x).ignore()),
_bus.subscribe(SparkTags.DetachOrders).Subscribe(x => detach_orders(x).ignore()),
_bus.subscribe(SparkTags.UpdateOrder).Subscribe(x => this.update_order(x.key, x.price, x.size).ignore())
);
}
@@ -166,27 +163,37 @@ namespace mbts.spark {
return success;
}
public async Task<bool> update_order(OrderRecordImmutable order, double price, uint size) {
if (order.working_quantity() <= 0) return false;
_activity.update_order(order, price, size);
public async Task<bool> update_order(OrderRecordImmutable order, double? new_price = null, uint? new_size = null, CustomOrderDefinitionImmutable? new_algo = null) {
if (order.working_quantity() <= 0) {
// order already filled to completion
return false;
}
var size = new_size ?? (uint)order.working_quantity();
var price = new_price ?? order.price();
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
// no parameters would be changed by this update
return false;
}
_activity.update_order(order, price, size, new_algo.HasValue);
var result = await _service.update_order_async(new Update() {
OrderKey = order.Key,
Price = price,
Quantity = size
Quantity = size,
Custom = new_algo?.CloneToMutable()
});
(var key, string msg) = _handle_error(result, "Failed to update order: {0}");
bool success = key.HasValue;
if (!success) {
_activity.update_order_failed(order, price, size);
_activity.update_order_failed(order, price, size, new_algo.HasValue);
}
return success;
}
public Task<bool[]> crack_orders(IEnumerable<uint> orders) =>
Task.WhenAll(orders.Select(crack_order));
public Task<bool[]> detach_orders(IEnumerable<uint> orders) =>
Task.WhenAll(orders.Select(detach_order));
public async Task<bool> crack_order(uint order) {
var result = await _service.crack_order_async(new Crack() { OrderKey = order });
public async Task<bool> detach_order(uint order) {
var result = await _service.detach_order_async(new Detach() { OrderKey = order });
return _handle_error(result, "Failed to break order: {0}").key.HasValue;
}
@@ -285,7 +292,7 @@ namespace mbts.spark {
// DO NOT REMOVE (END)
public static IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_root_orders(this OrdersModel orders_model, uint security_key) =>
orders_model.on_security_orders(security_key).Scan(new RootOrderData(), (rod, table) => {
orders_model.on_security_orders(security_key).AddRemoveOnly().Scan(new RootOrderData(), (rod, table) => {
// ** using HashSet/List pairs, rather than a Dictionary, because TableUpdateTransform calls for IReadOnlyLists, while Dictionary.Values only implements IReadOnlyCollection.
// (this way, we avoid copying both added/removed Dictionary.Values to new Lists at the end, see comment at bottom of method if this is unclear) **
var added_root_keys = new HashSet<uint>();
Loaded 100 of 341 files, more files were not shown because too many files have changed in this diff. Show more