Compare commits

...
596 Commits
Author SHA1 Message Date
John Colvin 9a85a475db fix: Revert accidental change to externals 2023-01-10 09:51:54 -05:00
Taha Afzal 8a1ab0ba9e Updating generated files (and minor code adjustment) for CME schema 8 changes: 2023-01-09 10:28:10 -05:00
John Colvin 57ac54b6c5 build (sparkd): Pipe 'eval' cmd stdout/err back to shell
So we can actually see the build output
2023-01-05 14:03:40 -05:00
johncolvin-og 4a8433246c build: Use unix eol in build_sparkd, exec not eval 2023-01-05 14:03:40 -05:00
John Colvin d6351cd187 Update docker/linux
To get python3-venv
2023-01-05 14:03:40 -05:00
John Colvin eef263f2e7 build: Ensure python virtual env is installed/up-to-date 2023-01-05 14:03:40 -05:00
johncolvin-og 2009027586 build: Port build/publish_spark scripts over from crypto branches 2023-01-05 14:03:40 -05:00
John Colvin feaad6335d build: Set up cmake 'install' build target
The 'install' build target creates a self-contained (deployable) sparkd
package. It copies the sparkd executable and its dependencies into a
single directory (no symlinks), which can then be copied/deployed to
another machine.

Run `cmake --build . --target install` to install sparkd in the
directory defined by the CMAKE_INSTALL_PREFIX variable.
2023-01-05 14:03:40 -05:00
John Colvin b96612d37b fix build (linux): Port py3 build scripts over from spark_crypto.dev 2023-01-05 14:03:40 -05:00
John Colvin 84f9338005 Add .venv dirs to .gitignore 2023-01-05 14:03:40 -05:00
John Colvin 2837f3edfb chore: Add requirements.txt (py venv) 2023-01-05 14:03:40 -05:00
John Colvin a82932652b fix build: Copy a handful of files over from spark_crypto.dev
This fixes the last of the boost-related errors
2022-09-28 19:49:17 -04:00
John Colvin c9b5ff844f fix build (partial): Update externals to get new websocketpp
This fixes some of the boost errors related to the recent boost upgrade
2022-09-28 19:47:39 -04:00
John Colvin b0cbc2cb71 fix build: Disable web build
That's one way to fix it!
2022-09-28 19:33:19 -04:00
BTS User b2a2cae9c1 test 2022-09-28 13:44:48 -04:00
BTS User 93d9a924e9 test 2022-09-28 13:39:09 -04:00
BTS User 9d699711c1 test 2022-09-28 13:31:29 -04:00
BTS User 5951691cbd test 2022-09-28 13:28:20 -04:00
BTS Devs cbff70d5a1 plugin.py edited online with Bitbucket 2022-09-28 16:10:31 +00:00
BTS Devs b59d45ae28 plugin.py edited online with Bitbucket 2022-09-28 16:07:14 +00:00
BTS Devs ea6e158e63 plugin.py edited online with Bitbucket 2022-09-28 15:54:41 +00:00
BTS Devs 55a32935a9 plugin.py edited online with Bitbucket 2022-09-28 15:41:32 +00:00
BTS Devs 9f895df70c audit_ice.py edited online with Bitbucket 2022-09-23 21:25:37 +00:00
BTS Devs 8551936383 audit_upload.sh edited online with Bitbucket 2022-09-20 15:36:21 +00:00
John Colvin cb0378d785 fix: Load FairValueLine price type in settings dialog 2021-07-02 13:55:34 -04:00
John Colvin 489ac45722 Add fair value lines for DJ 2021-06-29 14:07:08 -04:00
John Colvin d910c65e20 fix: ContractPicker syncs selected sec/md w/ selected key 2021-06-29 15:18:06 -04:00
Pedro Pinto cb0e4de6c5 Updated TradingSystemName in IL3 connection. 2021-04-29 15:13:09 -04:00
Pete Fisher 71539424cc updated ilink3 schema to v7 2021-03-25 12:46:49 -04:00
Pete Fisher 602929b945 updating submodule to latest 2021-03-18 12:13:11 -04:00
Pete Fisher 07591f8e28 ice audit scripts 2021-03-18 11:05:34 -04:00
Pedro Pinto 0cb11b7f8d Made parsing of dump security easier to facilitate locating ice symbols. 2021-03-16 11:32:59 -04:00
Pete Fisher 1921f02d30 Merged in DJ_ICE_ManualOrderIndicator (pull request #417)
added ManualOrderIndicator to ICENativeOrder.cpp after merge with Prod
2021-03-02 15:47:34 +00:00
Pete Fisher 2199efc95d added ManualOrderIndicator to ICENativeOrder.cpp after merge with Prod 2021-02-26 15:43:41 -05:00
Pete Fisher 54ff55f22f merge with prod 2021-02-26 14:59:38 -05:00
John Colvin ca00f256e1 ui (DJ): Avoid darkening price text in session range in ladder
Put the session range visual beneath the column backgrounds
(on the z axis), and make the background slightly translucent
while leaving the font color fully opaque.  This effectively
darkens the former without changing the latter.
2021-02-17 12:57:26 -06:00
John Colvin 733ce7dfc6 Set alpha bits to 'FF' in historic ladder settings colors 2021-02-17 12:38:51 -06:00
John Colvin fda812d792 ui (dj): Reduce opacity of session range layer in ladder 2021-02-11 23:03:53 -06:00
John Colvin 1bf9d0aa4a ui (dj): Replace hand cursor with arrow in ladder bid/ask 2021-02-11 22:57:48 -06:00
John Colvin 54eec61f46 ui (dj): Make ladder look more like tt
These changes can be configured via LadderKnobs (defined in UI.proto)
2021-02-11 22:57:40 -06:00
Pete Fisher b84ba1d3f5 changed ICEMain.cpp from NonImplied to FullImplied 2021-02-03 10:26:47 -05:00
John Colvin 70bf5e0c95 fix: Correct visibility binding in ladder session high/low
The other price points visibility bindings were refactored
in a similar manner, for consistency's sake.
2021-01-14 11:06:17 -06:00
John Colvin 2bae938549 feat (DJ): Right align bid, price, and ask column text in ladder
This is hardcoded (not configurable via settings).
2021-01-14 10:12:04 -06:00
John Colvin a67b32fd33 ui-tweak (DJ): Display 'volume' just below 'day change' in ladder 2021-01-07 11:36:35 -06:00
John Colvin 3da8fbd25d feat (DJ): Give bid/ask cells with markets a lighter background in ladder
There is a small chance this will impact performance when
a large number of ladders (50+) are open.  Therefore, this
behavior can be toggled on/off in config, via
LadderKnobs.lighten_market_background.
2021-01-07 11:18:13 -06:00
John Colvin a8cd07cbd2 refactor: Consolidate tick tests after tricky merge
The branches that were merged, DJ_ICE and
DJ_ICE_ladder_price_aggregation, both added new
tick tests.  The former put them in a new file,
"TickTests.cs," while the latter added them
to "SecurityTests.cs."  This commit places all
tick tests in "TickTests.cs."
2021-01-07 10:37:02 -06:00
John Colvin badb463e03 Merge remote-tracking branch 'origin/DJ_ICE_ladder_price_aggregation' into DJ_ICE 2021-01-06 12:08:39 -06:00
John Colvin b6a942d6b2 Add tooltips to ladder left-panel price points 2021-01-06 11:00:51 -06:00
John Colvin 7d8bc13282 Tailor default ladder price point settings for DJ 2021-01-06 11:00:51 -06:00
Pete Fisher cbad79b909 fixed use_onixs 2021-01-06 11:00:51 -06:00
Pedro Pinto 58768567d1 Added operator id to tag 116. 2021-01-06 11:00:51 -06:00
Pedro Pinto 2475ca3662 Used clearing account to populate tag 116 (ATID) 2021-01-06 11:00:51 -06:00
John Colvin f12b8fedc0 feat (DJ): Display settings for ladder left-panel price points
* Add background and font settings for all price points

* Indicate 'day change' sign with modal settings (positive,
  zero, and negative) for background and font color

* Hide 'day change' leading zeros in tick part

* Don't show 'day change' zero integer part (e.g.,
  '25' instead of '0.25')
2021-01-06 11:00:51 -06:00
John Colvin ed920c7c5a Move security/tick data classes to spark_core
* spark_core is the appropriate project for such
  'vocabulary' types.

* unit tests in the spark_tests project can now
  reference these types (this was the immediate
  impetus for the move).
2021-01-04 18:23:27 -06:00
John Colvin db20473949 add some docker scripts (start_docker.sh, docker_bash.sh) 2021-01-04 10:12:20 -06:00
John Colvin 5bf8162fcf vscode (build): change linux build task to run in docker
* prompts user for configuration (debug or release)

* looks for docker instance named "${USER}s_docker"
2021-01-04 10:10:23 -06:00
Taha Afzal 447d3303d1 Time based throttles code complete 2020-09-28 09:57:29 -04:00
Taha Afzal 5693fc4444 Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-09-27 22:48:30 -04:00
Taha Afzal 8896066e2c Time based price throttles 2020-09-25 09:49:19 -04:00
John Colvin 4992f4e0cf config toggle to show/hide ladder scrollbar for dj (defaults to hide) 2020-09-24 16:25:43 -05:00
John Colvin 3503946011 add quote-time-throttle params to SyntheticEditor gui 2020-09-21 19:28:41 -05:00
Pedro Pinto e7806d2fec Merge branch 'master' into prod 2020-09-16 16:28:36 -04:00
Pedro Pinto 090a9dfb17 Added iLink3 documentation. 2020-09-14 12:02:37 -04:00
John Colvin d31d8648f1 frozen price lines in ladder (for dj) 2020-09-09 16:39:53 -04:00
Pete Fisher 27aa43f44d fixed time on ilink2 audit 2020-09-09 12:16:57 -04:00
Pedro Pinto 19e087aa69 Fixed time reporting in iLink3. 2020-09-09 10:58:34 -04:00
Pedro Pinto 167c8c23a0 Fixed batching of orders under iLink3. 2020-08-27 08:19:47 -04:00
Taha Afzal 2c15f83c85 merge fix 2020-08-26 09:49:16 -04:00
Taha Afzal f9dfb04fca tooltip change 2020-08-25 23:02:02 -04:00
Taha Afzal 680b19b6fb Enable Hedging and Lean Ticks Offset 2020-08-25 22:58:15 -04:00
Pete Fisher d235f4be41 fixed jetfix.conf ports on relay 2020-08-25 10:03:09 -04:00
Pedro Pinto ec43dbd82d Maybe fixed the self match prevention problem. 2020-08-24 17:02:58 -04:00
Pedro Pinto f07998d6a6 Fixed order rounding error in ilink3 2020-08-24 16:17:31 -04:00
Pedro Pinto b3dc2cdcd4 Changed logging subsystem from ilink3 to iLink3 2020-08-24 11:06:44 -04:00
John Colvin f790b8f59b RiskManagement view updates when imported row overwrites existing one 2020-08-20 11:14:56 -05:00
Pete Fisher 0963aee137 added audit log scripts to be run by the docker container 2020-08-19 17:49:47 -04:00
John Colvin fbed8e5428 fix AdminModel.delete_risk_limits: don't increment i at end of iteration when an item is removed 2020-08-14 13:15:35 -05:00
John Colvin 524f3be25a RiskManagementWindow - imported rows weren't filtered by account 2020-08-14 11:21:49 -05:00
John Colvin 9823b44083 RiskManagementWindow - hook up menu display vm 2020-08-14 11:04:14 -05:00
John Colvin 30feda72bb Merged DJ_ICE_proprietary_risk into DJ_ICE 2020-08-14 10:54:47 -05:00
John Colvin 2d0e63546d Merge branch 'DJ_ICE' into DJ_ICE_proprietary_risk 2020-08-14 10:53:26 -05:00
John Colvin cc02fa1c84 risk limits import dialog (for dj) 2020-08-13 14:13:44 -05:00
Pedro Pinto c2f8e92b10 Hopefully fixed the ilink3 filled qty reporting problem.
This was crashing the spreader.
2020-08-13 08:42:43 -04:00
Taha Afzal 6139d53e49 Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-08-12 15:04:34 -04:00
Taha Afzal 7f3c2e75a3 Fixed the qty in the leg fills for spreads with different exchange_qty_mults 2020-08-12 15:04:15 -04:00
John Colvin 0f61e24ae5 fix ColumnBase.SortDirection binding to DIYGridColumnModel 2020-08-12 13:00:03 -05:00
John Colvin 8da2ebee5c SelectionModel should only select the pending selection once (was reselecting it every time the items changed) 2020-08-12 12:59:19 -05:00
Pedro Pinto 7ae0142dff Fixed reset of sequence numbers problem 2020-08-12 11:56:08 -04:00
Pedro Pinto a1faf685eb CMake, you suck. 2020-08-11 15:59:40 -04:00
Pedro Pinto 9e2b01a723 Cleaned up branch for delivery to DJ 2020-08-11 13:54:27 -04:00
John Colvin d03c74f270 remove unused 'actual_width' prop from DIYGridColumnModel 2020-08-05 15:11:06 -05:00
John Colvin 834a28916b synchronize column model's GridColumnSettings with the column view 2020-08-05 15:10:23 -05:00
John Colvin 43ccb4e3e7 fix risk management thread name typo 2020-08-05 12:20:24 -05:00
Taha Afzal f199cb4157 Fixed the broken test (needed to update the size attribute on the managed ColdEntry 2020-08-05 10:54:43 -04:00
Taha Afzal 5a3bfd7f81 Merge branch 'DJ_ICE_proprietary_risk' of bitbucket.org:bluetradesys/spark into DJ_ICE_proprietary_risk 2020-08-04 15:40:29 -04:00
Taha Afzal bf6461c126 More logs and TAS HNG can not be the master 2020-08-04 15:40:21 -04:00
John Colvin 8892505d11 RiskManagement price cells show 4 decimal places, use commas 2020-08-04 12:11:55 -05:00
John Colvin 5d0a29432a RiskManagementVM - row sorting must refer to immutable data (else binary search can break) 2020-08-04 12:11:55 -05:00
John Colvin cd01c7ae5a RiskManagementVM - remove unused props/methods 2020-08-04 12:11:55 -05:00
Taha Afzal bf19ec0fda Merge branch 'DJ_ICE_proprietary_risk' of bitbucket.org:bluetradesys/spark into DJ_ICE_proprietary_risk 2020-08-04 13:08:14 -04:00
John Colvin 1a0158817e remove size constraints on RiskManagementControl 2020-08-04 11:28:23 -05:00
John Colvin 53853591fe move RiskManagementControl.xaml/cs to 'View' dir 2020-08-04 11:28:23 -05:00
Taha Afzal 80d4899f2c removing fake data functions (when the UI was not around) 2020-08-04 09:51:32 -04:00
Taha Afzal 75f15c50d8 working long/short pos was not getting copied in copy_positions_from 2020-08-03 18:25:40 -04:00
John Colvin f197f213f3 Risk Management ui - open edit-rows dialog near mouse 2020-08-03 15:52:26 -05:00
John Colvin a7e1b71883 TextCell available_size is updated in ArrangeOverride 2020-08-03 15:28:41 -05:00
John Colvin f9f22e1e36 add risk management window 2020-08-03 15:28:41 -05:00
John Colvin cda50f562d AdminModel - add methods to subscribe, and modify proprietary risk limits (dj) 2020-08-03 15:28:41 -05:00
John Colvin 4010298a46 parameterize SecurityStore.join (don't mess with core logic/impl) 2020-08-03 15:28:41 -05:00
John Colvin 48e701ba00 client side data types that mirror api protos (minimize client/server coupling) 2020-08-03 15:28:41 -05:00
John Colvin cb07abbf42 made new SelectorVM (todo: replace refs to old one) 2020-08-03 15:28:41 -05:00
John Colvin 3c8e2c636f add SimpleGridSettingsManagerVM (uses DIYGridColumnModels instead of the view type ColumnBase) 2020-08-03 15:28:41 -05:00
John Colvin 6a3b4ebe75 add DIYGridColumnModel, so the DIYGrid column paradigm is cleanear mvvm 2020-08-03 15:28:41 -05:00
John Colvin 80256fbe12 add get/load_state methods to non-generic IDataGridColumnFilter 2020-08-03 15:28:41 -05:00
John Colvin a0a5d82139 add PropertyEqualityComparerAttribute (supports simple automatic equality impl) 2020-08-03 15:28:41 -05:00
John Colvin ee6b00b9f2 unify SecurityCriteria initialization with securities (use pretty_name if single sec) 2020-08-03 15:28:41 -05:00
Taha Afzal 9a494ce24f Parent Child Secrity Pos Limit 2020-08-03 16:25:24 -04:00
Taha Afzal ae78ebac9a All sec based risk done (master, slave remain) 2020-07-31 17:27:06 -04:00
Taha Afzal 5c11eecbd1 SecurityLimitNode keeps working qty and positions differently 2020-07-31 00:21:17 -04:00
Taha Afzal 78a47c33c7 Fixing order rejection message 2020-07-30 14:55:11 -04:00
Taha Afzal 9dcd80e3db Security level risk controls are passed to update order 2020-07-30 13:02:22 -04:00
Taha Afzal f7563413bd New security level controls hooked to order entry 2020-07-30 11:14:45 -04:00
Taha Afzal 29c418db42 Routed the SecLimit to SecurityLimitNode 2020-07-29 18:15:18 -04:00
Taha Afzal f177efde70 Added price arg to the Limiter's can_send and can_update functions 2020-07-29 14:28:16 -04:00
Taha Afzal 3b83d105a0 Filled in the prop risk limits persistence code 2020-07-29 12:29:56 -04:00
Taha Afzal 34aafd79cd Added new risk proto and Admin API function to update it 2020-07-29 11:30:36 -04:00
Taha Afzal c30bab899f Changing TASCounter to use the new formula 2020-07-29 09:43:00 -04:00
Taha Afzal d3de164c53 Moved the 2500 translation to the end points of the system 2020-07-27 17:05:19 -04:00
Taha Afzal 46544f36f1 Pyramid Algo now sends hedges for fills while a cancel is in flight 2020-07-23 13:13:52 -04:00
John Colvin c7df3ac43c Merge remote-tracking branch 'origin/csv_export' into prod 2020-07-22 10:44:58 -05:00
Taha Afzal 1d1c0d6b85 Jan exps' are showing 12 for the month, adjustment for risk 2020-07-21 12:09:41 -04:00
Taha Afzal 2f127ecc18 Using day positions for risk enforcement 2020-07-20 23:48:35 -04:00
Taha Afzal 3fc080d02c Better position violation comments 2020-07-20 23:21:20 -04:00
Taha Afzal d86543261c Risk enforcement changes for ICE NG OTC markets 2020-07-20 13:01:56 -04:00
John Colvin 8761f313f5 remove with_reguard_to_price param from tick 'format' extension 2020-07-20 10:50:15 -05:00
John Colvin 3906ef6507 fix typo in ConvertUtils.update_value_type (was missing negation '!') 2020-07-19 13:28:09 -05:00
John Colvin 78055d79c6 fix tick unit tests, and reorganize security tests a bit 2020-07-19 13:27:12 -05:00
John Colvin 885a9dd939 add precision param (epsilon) to tickup/down methods 2020-07-19 13:20:00 -05:00
John Colvin 20c449de60 TickFactors are cached in a ThreadStatic dictionary rather than concurrent (no locks required) 2020-07-15 11:22:11 -05:00
John Colvin 52b487946a ticksize extension handles invalid tick props gracefully 2020-07-15 10:50:25 -05:00
John Colvin 98144ea939 remove unused parameter from TickTestHelper.full_ticks_from_zero 2020-07-14 15:46:06 -05:00
John Colvin 5e27a5fa7c fix ladder session low/high grid animation (was not using the ticks per row setting) 2020-07-14 15:39:55 -05:00
John Colvin 6cfc15ccd8 LadderGridHost invalidates '_spec' on connect_vm (not just on settings updates) 2020-07-14 15:38:21 -05:00
John Colvin 2de08c2c48 fix typo in LadderGridHost._spec getter (was accidentally returning the fallback value) 2020-07-14 15:27:03 -05:00
John Colvin fb6e05560b make tick unit tests more robust (test tick_up/down, use these methods to covalidate results) 2020-07-14 12:48:21 -05:00
John Colvin 1abf84337f include Sim ContractSpecs in tick unit tests 2020-07-14 11:53:57 -05:00
John Colvin d691e0aeee ladder settings dialog - fix 'when implied is > 0' label alignment 2020-07-14 11:01:32 -05:00
John Colvin 5e0f1b59fb added tick unit test (namely for 'full_ticks_from_zero' method) 2020-07-14 10:53:30 -05:00
John Colvin a234ecbf26 added 'full_ticks_from' method which accepts an origin price (instead of just an arbitrary 0) 2020-07-14 10:53:27 -05:00
John Colvin 062cabf075 TickUtils.full_ticks_from_zero checks pricebreak 2020-07-13 13:52:29 -05:00
Taha Afzal 1a7fcb2027 Added is_ice_lots to the ICE sec proto 2020-07-12 17:03:37 -04:00
Pedro Pinto db0ffba8ed Fixed windows build 2020-07-10 13:00:20 -04:00
Pedro Pinto a274a214b4 Merged with master 2020-07-10 11:31:47 -04:00
Pedro Pinto bbcfa3c75a merged with prod 2020-07-10 10:45:10 -04:00
Pedro Pinto 89229d65f1 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2020-07-10 10:43:34 -04:00
Pedro Pinto 0e75cde409 Fixed a bad asser in recovery, a few typos, haderned against bad securities. 2020-07-10 10:36:03 -04:00
Pedro Pinto 42fa6dfaff Fixed life cycle of exe reports and config during replay. 2020-07-09 16:06:39 -04:00
Pedro Pinto 400143c84d Protected against ticker subs for expired securities. 2020-07-09 14:45:51 -04:00
Pedro Pinto e915945880 Trying to replay fix logs. 2020-07-09 14:27:22 -04:00
Taha Afzal ca6673e98f Merge branch 'prod' 2020-07-09 13:09:57 -04:00
Taha Afzal 479f3dccff Adding OnSignal (this is a new algo which is hidden by default) 2020-07-09 13:07:28 -04:00
John Colvin 39e1fd4a1a move LadderUtils methods to TickUtils (all were related to ticks) 2020-07-09 12:05:17 -05:00
John Colvin 4d902747df move tick data\utils to spark_core 2020-07-09 11:58:19 -05:00
John Colvin 50d0078556 add 'ticks per row' ladder setting 2020-07-09 11:45:31 -05:00
Pedro Pinto 9601ebab20 Adding replay of stored messages. 2020-07-09 10:16:31 -04:00
John Colvin d86b7c6c50 add TickUp/DownCommand dependency props to TickUpDownUInt 2020-07-08 12:48:45 -05:00
Taha Afzal c272d8a8c2 Pyramid Algo: Takes care of the rounding error when Top == sum (child orders) 2020-07-08 13:35:21 -04:00
Pedro Pinto b344ab3377 Fixed an issue with order recovery from Exec reports. 2020-07-08 12:07:48 -04:00
Pedro Pinto ae3080b031 Refactoring. 2020-07-08 10:10:14 -04:00
Pedro Pinto f0464331cb Added better handling of cancel rejects. 2020-07-08 09:52:18 -04:00
Pedro Pinto 0648b618bf Refactored. 2020-07-08 08:42:53 -04:00
Pedro Pinto 59282b8c70 Added one more test to manual order entry list. 2020-07-08 08:42:18 -04:00
Pedro Pinto 34a86009ce Doc and tool minor update. 2020-07-08 07:44:01 -04:00
Pedro Pinto 0ad9ae9001 Small tweak to logging tool. 2020-07-07 20:19:59 -04:00
Pedro Pinto a296e1e772 Fixed parsing of stop px in link3. 2020-07-07 20:19:41 -04:00
Pedro Pinto 7f53d8b161 Improved logging, fix iceberg order, removed duplicate logging. 2020-07-07 17:01:24 -04:00
John Colvin fbd8bbd4a8 algo abbreviation code uses string.Join instead of list_string (the latter method is redundant, should be removed) 2020-07-07 13:16:18 -05:00
John Colvin 3da52b350f fix pure crash (algo abbreviation code choked on names that end with a space) 2020-07-07 13:15:55 -05:00
Pedro Pinto 243d6827d4 Merge remote-tracking branch 'refs/remotes/origin/ilink3_final' into ilink3_final 2020-07-07 12:12:09 -04:00
Pedro Pinto 00221f9bd9 Fixed spread order entry. 2020-07-07 12:11:11 -04:00
Pedro Pinto a6171ae405 Fixed spread order entry. 2020-07-07 11:55:05 -04:00
Pedro Pinto be5d01a797 Fixed fills. 2020-07-07 06:47:58 -04:00
Pedro Pinto e5d9d324bf Order order entry manual test. 2020-07-06 18:22:39 -04:00
Pedro Pinto c34dee9d96 Taked order status from execution report, parse error message in il3 2020-07-06 18:21:44 -04:00
Pedro Pinto e5a98aab41 Merged with master 2020-07-06 12:49:24 -04:00
Pedro Pinto 10cd35372b Can enter, modify, cancel orders. 2020-07-06 11:38:17 -04:00
Pedro Pinto ad4515be39 Renamed NativeOrder to GLBXNativeOrder 2020-07-06 09:47:34 -04:00
Pedro Pinto ff4d87dd6b Fixed IL3 execution report parser to convert exchange prices. 2020-07-06 09:34:26 -04:00
Pedro Pinto 8ddde3f9da Part 2 of MBO service fix. 2020-07-06 09:34:09 -04:00
Pedro Pinto 51093dc3a5 Added simple tool to watch spark logs. 2020-07-06 09:33:35 -04:00
Pedro Pinto 5d5db9ad9f Fixed MBO parser to extract the right exchange key. 2020-07-06 09:33:06 -04:00
Pedro Pinto fca808fe0d Made ilink3 gw wait for start() before its starts. 2020-07-03 14:11:22 -04:00
Pedro Pinto 07d14f877b More nonsense 2020-07-02 20:28:49 -04:00
Pedro Pinto 5ea6dccdd4 Something here breaks 2020-07-02 19:04:49 -04:00
Pedro Pinto ac956a5c02 Renamed ExchangeOrderIDu64 to ExchangeOrderID64 2020-07-02 19:00:32 -04:00
Pedro Pinto 1fba09cf94 Refactor of ids. 2020-07-02 11:05:04 -04:00
Pedro Pinto 0baa6b875e Fixed merge error. 2020-07-02 09:09:20 -04:00
Pedro Pinto cdacc2d4d4 Merged 2020-07-02 07:26:43 -04:00
Pedro Pinto 1433541947 Automatically advanced our own seq number on connection failure.
It is fairly easy for our sequence number to be behind CME if we
crash. Since we can detect this issue (CME tells us about it) we can
try to fix it automatically by incrementing our own sequence
number. This should allow us to recover automatically in some
scenarios.
2020-07-02 06:57:01 -04:00
Pedro Pinto 1070ca7ace Ran clang-format. 2020-07-02 06:28:32 -04:00
Pedro Pinto 56fa27dd7e Handled non text timestamp in native order. 2020-07-02 06:23:07 -04:00
Pedro Pinto d87b522bc6 Added better logging when terminated by exchange. 2020-07-02 06:22:38 -04:00
Eric Harding 166cb03782 Provide account when parsing execution reports. 2020-07-01 14:40:10 -04:00
John Colvin 40bad84c48 algo studio includes algos with undefined children in the dropdown 2020-07-01 12:09:12 -05:00
Eric Harding c3786d9d64 ilink3: Provide SecurityOracle to execution report parser 2020-07-01 11:50:03 -04:00
Pedro Pinto 9080e3124b Filled in cust_order_handing_inst 2020-07-01 11:29:47 -04:00
Pedro Pinto d9c536124a Fixed logging of session seq numbers. 2020-07-01 11:29:30 -04:00
Pedro Pinto 7de6b2907e Filled in sendercompid in routing record.
iLink3 requires this.
2020-07-01 10:48:01 -04:00
Eric Harding 75f017e9f8 ilink3: Get PartyDetails from sender_cmp_id 2020-07-01 10:29:20 -04:00
Pedro Pinto 7757ab3ea0 Fixed a few bugs. 2020-06-30 19:35:53 -04:00
Pedro Pinto 3186dd383c We can connect now. 2020-06-30 19:03:35 -04:00
Pedro Pinto 45f751c9c6 Bad merge. 2020-06-30 19:03:35 -04:00
Pedro Pinto e4d3774980 Better logging. 2020-06-30 19:03:35 -04:00
Eric Harding eedb3ce05d Clear hmac for json logging 2020-06-30 17:11:01 -04:00
Eric Harding 6a7d38c0fd flip session bool 2020-06-30 16:10:26 -04:00
Eric Harding 7f9967834d add readme note 2020-06-30 15:34:03 -04:00
Eric Harding 31ccbfcd19 Switch to ilink3 logging group 2020-06-30 14:43:38 -04:00
Eric Harding c2abf90cc4 IL3MessageFramer owns outgoing messages so it needs to outlive ILinkGW 2020-06-30 14:36:02 -04:00
Eric Harding 8dc4735248 ilink3: set_state(Error) only after disconnect 2020-06-30 14:25:59 -04:00
Pedro Pinto d940d9f11d For now, caught serialization error in some ilink3 msgs. 2020-06-30 14:10:07 -04:00
Pedro Pinto 1e0c75c6c7 Added iLink3 subsystem for logging. 2020-06-30 14:09:55 -04:00
Eric Harding fbaa17bc40 Add some comments to marketdata.proto 2020-06-30 13:16:30 -04:00
Eric Harding 38e3cd6197 Update market data readme 2020-06-30 13:13:48 -04:00
Pedro Pinto 3bb9961af4 Refactor. 2020-06-30 11:35:40 -04:00
Pedro Pinto da8088fd8d Rebased 2020-06-30 11:27:56 -04:00
Eric Harding 748a65f671 Merge branch 'ilink3_gateway' of bitbucket.org:bluetradesys/spark into ilink3_gateway 2020-06-30 10:05:33 -04:00
Eric Harding 66d80136bd ilink3: Keep outgoing messages alive forever. 2020-06-30 10:04:42 -04:00
Eric Harding 10386311d4 Update market data readme 2020-06-29 12:25:30 -04:00
Eric Harding 629a7f8130 Remove unused class in MD 2020-06-29 12:25:14 -04:00
Pedro Pinto 81189bb2cc Forgot to add message logger. 2020-06-29 12:08:22 -04:00
Pedro Pinto e5681a50f2 Made session name come from date. 2020-06-29 10:59:15 -04:00
Pedro Pinto 69fe4569e3 Made FIX session name available as uint64_t as well as strings. 2020-06-29 10:57:40 -04:00
Pedro Pinto c7caf66f9c Changed the ILink3 log format to json. 2020-06-26 19:11:05 -04:00
Pedro Pinto 14504f4a94 Cleaned up comment. 2020-06-26 19:10:49 -04:00
Pedro Pinto 5b601a4016 Fixed encofing of clorid. 2020-06-26 19:10:23 -04:00
John Colvin e3207b921a truncate list of securities in transitive compare test (was timing out due to too many permutations) 2020-06-26 13:32:27 -05:00
Taha Afzal 98db8e61a5 Removing hardcoded ref's to Katallage 2020-06-25 17:05:10 -04:00
Pedro Pinto 45aa75e8bb Brought back the ilink3/ilink2 encoding abstraction. 2020-06-25 16:49:46 -04:00
Pedro Pinto b2471d45b2 Added tool 2020-06-25 16:49:10 -04:00
Pedro Pinto 11e2df8c76 Tweaked build tools. 2020-06-25 16:49:10 -04:00
Pedro Pinto cba6852655 Updated docker 2020-06-25 16:49:10 -04:00
Pedro Pinto 5eeaedde26 Added a few build tools. 2020-06-25 16:49:10 -04:00
Pedro Pinto b63052dbaa Updated ICE Onixs 2020-06-25 16:49:10 -04:00
Pedro Pinto 29f5bc798e Added +x 2020-06-25 16:49:10 -04:00
Pedro Pinto 04603acbb9 Added publishing of new routing info when orders are recorevered from disk 2020-06-25 16:49:10 -04:00
Pedro Pinto bc6890055e temp commit, fixing glbxnative order 2020-06-25 16:49:10 -04:00
Pedro Pinto ea94c09c78 Eric intermediate ilink3 fixes 2020-06-25 16:49:10 -04:00
Eric Harding e3c3bc78fc Note what this address list is for 2020-06-24 20:55:20 -04:00
Eric Harding a11d390dcd ilink3 message logging 2020-06-24 20:52:21 -04:00
Eric Harding 7c74a961d4 Add canned cert sessions for ilink3 nrcert 2020-06-24 20:43:27 -04:00
Eric Harding 5db21ad2ca fix build, session and clordid changes 2020-06-24 19:32:12 -04:00
Eric Harding f703dabdfe Raw msg should be 2 strings 2020-06-24 17:53:30 -04:00
Eric Harding 237d3eea4b encode_clordid needs security key 2020-06-24 17:29:06 -04:00
Taha Afzal e009316bfb Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-06-24 17:13:38 -04:00
Taha Afzal 5528efd2d2 Cleaning up 2020-06-24 17:13:31 -04:00
Eric Harding 818bdec130 Add NrCert keys 2020-06-24 15:07:02 -04:00
John Colvin 09f91735fe Security sort tests run with ice securities 2020-06-24 12:58:29 -05:00
John Colvin d9fc18547b SecurityHelper.Compare fallsback on 'ToString' then 'Id' comparisons to guarentee deterministic sort 2020-06-24 12:56:29 -05:00
Eric Harding d257cf2c77 Disable broken ilink3 tests for now 2020-06-24 13:28:33 -04:00
Eric Harding 079f46404f Add some testing notes 2020-06-24 10:57:15 -04:00
Eric Harding edcc73e934 Remove old ILink3ExecutionGateway. Instead use GlbxIlink3ExecutionGateway 2020-06-23 22:41:33 -04:00
Taha Afzal 5905ca3e92 Merging frmo master 2020-06-23 13:51:14 -04:00
Pedro Pinto bc7ac8523b Added tool 2020-06-23 13:06:15 -04:00
Eric Harding 0c4c2b7231 cme autocert msgw_failover test script 2020-06-23 13:05:32 -04:00
Eric Harding 420351c093 Replay refactor #4 2020-06-23 12:01:52 -04:00
Eric Harding f69e6b89c5 Sequence number fixes 2020-06-22 22:23:13 -04:00
Eric Harding c3e22e4b23 Recovery revamp #3 2020-06-22 20:16:21 -04:00
John Colvin 32b7b7f40d quantity conversion for ladder right click order 2020-06-22 17:02:23 -05:00
Pedro Pinto 54e5a68730 Tweaked build tools. 2020-06-22 08:41:19 -04:00
Pedro Pinto 880c95d92b Tweaked build tools. 2020-06-20 17:17:22 -04:00
John Colvin e7da6dd79e remove unused 'save/remove_template_cmd' props from OrderPropertiesEditor 2020-06-19 16:42:38 -05:00
John Colvin 8bf78aa880 cleanup/normalize whitespace in OrderDataHelper.cs 2020-06-19 16:37:21 -05:00
John Colvin 1c44165181 remove unused methods/props in OrderDataHelper.cs 2020-06-19 16:35:15 -05:00
John Colvin 8165ec0e3d fix AlgoProvider.get_permitted_algos (seed intersection aggregate a full set instead of empty) 2020-06-19 16:14:58 -05:00
John Colvin 7d1ec5b82a Merge remote-tracking branch 'refs/remotes/origin/DJ_ICE' into DJ_ICE 2020-06-19 15:48:55 -05:00
John Colvin 02e7fb1ee0 iceberg 'show quantity' param uses display divisor 2020-06-19 15:45:17 -05:00
John Colvin c0ea6943e1 AlgoProvider ensures synthetic security algo's last child is a Spreader algo 2020-06-19 15:12:42 -05:00
John Colvin 4c914a2d48 Merge branch 'DJ_ICE_algo_config' into DJ_ICE 2020-06-19 10:59:10 -05:00
Taha Afzal 0ee86f2dd4 CPP override fix 2020-06-19 11:58:58 -04:00
Taha Afzal 00bf3c7f47 Fixing the PNL timestamp 2020-06-19 11:10:23 -04:00
Taha Afzal b61e18d10e CPP logic 2020-06-19 00:44:13 -04:00
John Colvin 90b2607f86 pnl window symbol filter items are sorted 2020-06-18 13:06:41 -05:00
John Colvin e452697088 remove struct contraint on TKey in KeyedMultiSelector, and TableUpdateItemsFilter 2020-06-18 12:50:23 -05:00
John Colvin 1988443497 fix pnl symbol filter (was checking the security.Symbol instead of the 'pnl_symbol') 2020-06-18 12:40:07 -05:00
John Colvin 3f33627f00 add pnl_cpp_tuple extension method bones (taha will implement) 2020-06-18 12:14:44 -05:00
John Colvin 0610612ae6 make PNLData.with a member method instead of extension so it isn't
passed by value (it's a large struct)
2020-06-18 11:35:00 -05:00
John Colvin efcf78bb95 pnl window restores fill/market price decimal format when workspace loaded 2020-06-18 11:28:17 -05:00
John Colvin 03678e79a5 PNLSettings.cs line endings (need to figure out why they keep changing) 2020-06-18 11:00:32 -05:00
Taha Afzal c15a364496 Merge branch 'DJ_ICE_pnl_decimals' into DJ_ICE 2020-06-17 23:50:17 -04:00
Taha Afzal d8ec713dc5 Spreads in PNL show under a different symbol 2020-06-17 23:22:33 -04:00
Taha Afzal daaa7311a0 Merge branch 'DJ_ICE_pnl_symbol' into DJ_ICE_cpp_mult 2020-06-17 22:58:10 -04:00
Taha Afzal f893af1930 Strip Legs are not counted toward futures 2020-06-17 22:57:08 -04:00
Taha Afzal 6ec413c516 Dictinction bw spread legs and strip legs 2020-06-17 15:50:06 -04:00
John Colvin a2a0d88a81 pnl window disposes filters of type IDisposable 2020-06-17 12:39:59 -05:00
John Colvin 7a4dca9589 PNLWindow uses special pnl_symbol (taha will implement pnl_symbol method later, returns regular symbol in meantime) 2020-06-17 12:30:01 -05:00
John Colvin b21cd12cea copy AppendTable extension from another branch 2020-06-17 12:24:58 -05:00
John Colvin ec0b726751 ladder mid tick rounds down for negative numbers (still rounds up for positive) 2020-06-16 16:40:28 -05:00
John Colvin a9f2b4ad9a PNL window - setting for market/fill price column decimal places 2020-06-16 15:32:51 -05:00
Taha Afzal 9cb3a21e5a position service exposes both cpp and overridden cpp 2020-06-15 23:05:53 -04:00
Eric Harding 3231ffb343 Meaningless autocert code 2020-06-15 22:48:28 -04:00
Eric Harding 8d7f1738cf update cert secure logon 2020-06-15 22:47:30 -04:00
John Colvin 60d4dd87fd rename ISecurityAlgoProvider.cs -> IAlgoProvider.cs 2020-06-15 17:01:02 -05:00
John Colvin 3a7eeda70e AlgoProvider.subscribe_algos always fires at least once (doesn't depend on studio algos being present) 2020-06-15 17:00:13 -05:00
John Colvin 712146e028 Merge branch 'algo-type-config' into DJ_ICE_algo_config 2020-06-15 16:57:07 -05:00
Eric Harding 9101214e66 Add message logger and revamp replay sequence handling 2020-06-15 14:51:41 -04:00
Taha Afzal f381d536d6 Merge branch 'DJ_ICE' into DJ_ICE_cpp_mult 2020-06-15 09:53:32 -04:00
Pedro Pinto d8d5f3b93c Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-06-15 09:29:08 -04:00
Pedro Pinto 1d4dc47a62 Added missing update to session/high low. 2020-06-15 09:28:23 -04:00
Taha Afzal 482c300b72 Merge branch 'DJ_ICE_space_bar_recenter' into DJ_ICE 2020-06-14 21:08:51 -04:00
Pedro Pinto 59774ccbb5 Updated docker 2020-06-14 15:27:18 -04:00
Pedro Pinto fcb6c9b8d9 Added a few build tools. 2020-06-14 15:27:01 -04:00
Pedro Pinto 69ad995ba9 Updated docker 2020-06-14 15:23:18 -04:00
Pedro Pinto 0b26ec1132 Added a few build tools. 2020-06-14 15:21:55 -04:00
John Colvin 3de494c80f recenter all ladders on spacebar (don't require shift) 2020-06-12 17:05:53 -05:00
Eric Harding c6b2e2f21e OMF setting to true, always 2020-06-12 17:49:37 -04:00
Eric Harding 2eac97c1a9 meaningless cert change 2020-06-12 15:18:20 -04:00
Eric Harding b4b0e10f29 Fix IOC=FAK order type (not FOK) 2020-06-12 15:17:38 -04:00
John Colvin 7deb25dd1c fix ladder recenter on spacebar 2020-06-12 13:16:57 -05:00
Pedro Pinto 7a29ae1a83 Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-06-12 08:44:36 -04:00
Pedro Pinto 6da893128c Cleaned up logging. 2020-06-12 08:44:20 -04:00
Pedro Pinto 2cb458af2d Added OnBehalfofcompid tag to ICE as per cert. 2020-06-12 08:42:40 -04:00
Taha Afzal 04db38d627 Merge branch 'DJ_ICE_release_candidates' into DJ_ICE 2020-06-11 21:22:18 -04:00
John Colvin a144b03361 ladder recenter on spacebar (across thread barrier) 2020-06-11 18:28:37 -05:00
John Colvin 9c89a9a47e TabDocument forwards key press events to IShellWindows 2020-06-11 18:24:26 -05:00
John Colvin 007af71bb2 fix ladder center-on-settle behavior once and for all 2020-06-11 17:09:05 -05:00
John Colvin 7cfe8f4214 add override cpp context menu item in pnl window 2020-06-11 16:33:28 -05:00
John Colvin 24f8e66953 add IPositionModel (has function to override cpp) 2020-06-11 16:30:35 -05:00
John Colvin 90fc6d901f add PropertyEditor control/dialog 2020-06-11 16:29:45 -05:00
Eric Harding 38d9db4a90 Session and sequence number fixes 2020-06-11 11:36:37 -04:00
Taha Afzal a216d1cc81 Fixing sell order check for distance from settlement 2020-06-11 09:19:03 -04:00
Pedro Pinto f8395c11b5 Fixed application, vendor tags for DJ. 2020-06-11 08:26:26 -04:00
John Colvin 2b0e46ed38 fix ladder center around settle behavior 2020-06-11 00:36:11 -05:00
John Colvin 5f3b243265 'max settlement cross ticks limit' ladder setting 2020-06-10 18:16:52 -05:00
John Colvin c9ce2d18bf simplify 'Safety Checks' content alignment in ladder settings dialog 2020-06-10 17:33:13 -05:00
John Colvin 7b563ba66c ladder centers around settlement price if no bid/ask market 2020-06-10 13:17:01 -05:00
Eric Harding 6a679ff78c Fix for cancel/replace 2020-06-10 14:13:28 -04:00
Taha Afzal 3e1be70923 Fix for HHD Balmo spreads 2020-06-10 13:57:19 -04:00
John Colvin aadd7df6c6 quantity divisor is 1 for 'ICE Lots' secs 2020-06-09 22:49:57 -05:00
Eric Harding aba3f21020 stop order does not carry price 2020-06-09 19:18:36 -04:00
John Colvin 8206132a37 quantity conversion in pnl for dj 2020-06-09 17:27:29 -05:00
John Colvin f8b3fd2e14 QuantityConversionData.to/from_display extensions use 'safe_divide' 2020-06-09 17:10:21 -05:00
Eric Harding fede19fd11 stop-limit success 2020-06-09 16:30:00 -04:00
John Colvin 8b17ee72ff quantity conversion in ladder when last size is transposed over volume column 2020-06-09 10:50:06 -05:00
Eric Harding 3fa8dbf112 Cannot send price with market order 2020-06-08 23:09:10 -04:00
Eric Harding 77ae4703b3 Fix clOrdID and custOrdCapacity 2020-06-08 22:58:53 -04:00
John Colvin 31ed473999 quantity conversion in time and sales for dj 2020-06-08 17:25:13 -05:00
John Colvin 673805a7f4 quantity conversion in fills for dj 2020-06-08 17:10:39 -05:00
John Colvin 91422449e9 quantity conversion in manual fills for dj 2020-06-08 17:02:55 -05:00
John Colvin 438089a1cd quantity conversion in mview for dj 2020-06-08 16:36:57 -05:00
John Colvin 6a186d1d7e MarketViewRow - remove unreferenced fields 2020-06-08 16:33:55 -05:00
John Colvin 9d831a2569 fix line endings in MarketViewVM.cs 2020-06-08 16:33:55 -05:00
John Colvin d47cf1a54e (Dynamic)DoubleDivisorConverter uses safe_divide extension 2020-06-08 16:33:55 -05:00
John Colvin 8c9f669e72 refactor IsRealNonZero to is_real_positive (orig name was a typo), add a few more similar extensions to MathUtils 2020-06-08 16:33:55 -05:00
John Colvin f5bda11797 quantity conversion in orderbook for dj 2020-06-08 12:19:43 -05:00
John Colvin 1841a75fec quantity conversion in ladder/ticket for dj 2020-06-08 12:19:43 -05:00
John Colvin 34cdaeec93 don't use Disposable.replace bc it creates the next disposable before disposing the previous one 2020-06-07 16:01:27 -05:00
Taha Afzal 3230fb9e48 Regular TAS contract's formatting did not have the word TAS in it 2020-06-05 16:42:05 -04:00
Taha Afzal 888b4e22c0 Ignoring strips with > 512 components 2020-06-05 14:11:34 -04:00
Taha Afzal 534ce5a8ec strips are XL too 2020-06-05 13:12:57 -04:00
Taha Afzal c2b9ac57dc Fixing build 2020-06-05 11:55:54 -04:00
Taha Afzal 2783361325 Merge branch 'master' into DJ_ICE 2020-06-05 10:50:42 -04:00
Taha Afzal 46d35131d7 Overridable cpps 2020-06-05 10:01:18 -04:00
Pedro Pinto 3b95154c04 Updated ICE Onixs 2020-06-05 09:11:03 -04:00
Pedro Pinto 1a6b918853 Updated docker 2020-06-04 13:43:25 -04:00
Pedro Pinto bc22605a25 Added +x 2020-06-04 13:43:00 -04:00
Pedro Pinto f3d28add1c Added publishing of new routing info when orders are recorevered from disk 2020-06-04 13:41:34 -04:00
Eric Harding a28bfa1175 execution report handling tweaks 2020-06-04 10:05:44 -04:00
Eric Harding 4e4a48283d bool parse_report(string&, SecurityOracle&, ExecutionReport*); 2020-06-03 13:09:56 -04:00
Taha Afzal 63d260a6f8 Security formatting for ICE 2020-06-03 09:42:44 -04:00
Pedro Pinto 7a761d00ce Added security oracle to SessionStore so we can resolve exe reports. 2020-06-03 08:51:31 -04:00
Pedro Pinto 968be6a99a Added storage for sessions. Still missing parsing of exec reports. 2020-06-02 17:13:53 -04:00
Taha Afzal 9b4a63b4a8 Changes to make HNG/HIS/HEN/HHD to work 2020-06-02 11:15:47 -04:00
Pedro Pinto 03404eb3f1 Made some progress in GLBXiLink3ExecutionGateway 2020-06-02 09:16:02 -04:00
Pedro Pinto ff1a970982 Fixed build some more 2020-05-28 14:29:45 -04:00
Pedro Pinto ed890b4d84 Fixed broken tests for subscribe_exe_report change 2020-05-28 13:50:04 -04:00
Pedro Pinto e87003cc41 Merged with master 2020-05-28 12:21:51 -04:00
Eric Harding bc06037843 Fix the build 2020-05-28 12:20:30 -04:00
Pedro Pinto a502c365e5 Build tweaks 2020-05-28 12:15:16 -04:00
Pedro Pinto c4c7cebfc5 Updated externals 2020-05-28 12:12:46 -04:00
Pedro Pinto f18bcd2995 Build tweaks 2020-05-28 12:12:46 -04:00
Pedro Pinto a6a1b6ac53 Fixed clang build. 2020-05-28 12:12:46 -04:00
Pedro Pinto fdacd8b543 Added -f when removing simlinks in build setup. 2020-05-28 12:02:46 -04:00
John Colvin 3d1de9a0c7 nuget_restore.cmd uses externals package source (builds offline) 2020-05-28 12:02:31 -04:00
John Colvin b2b3fa5949 update jenkinsbuild.cmd to use externals nuget source (for windows) 2020-05-28 12:02:17 -04:00
Pedro Pinto 86ecf69820 Updated external subrepos 2020-05-28 11:53:51 -04:00
Eric Harding 8c3da4e43c Re-arrange some methods in ILinkGW.h 2020-05-27 16:51:36 -04:00
Eric Harding 06b955dceb Exchange seq number fix 2020-05-27 11:49:08 -04:00
Eric Harding e75d15146c cme cert test update 2020-05-26 23:55:27 -04:00
Eric Harding cb07384c9f Fix problem with party details 2020-05-26 23:55:13 -04:00
Eric Harding fc9e502539 cert refactor 2020-05-26 12:40:16 -04:00
Eric Harding fcbf3388c0 update ilinkbinaryxml templates 2020-05-26 12:40:04 -04:00
Eric Harding 899bea940f improve ilinkgw logging 2020-05-26 12:39:55 -04:00
Eric Harding 14b7509607 ensure local launch timezone 2020-05-26 12:39:41 -04:00
Pedro Pinto 8f537a8fda Merged in build_improvements (pull request #414)
Build improvements
2020-05-26 16:32:43 +00:00
Pedro Pinto 01b9ff6b47 Dropped celoxica 2020-05-26 12:28:35 -04:00
Pedro Pinto 2609663d3e Updated setup_builds to work even if its run outside the tree. 2020-05-26 12:06:08 -04:00
Eric Harding 25668e8df0 dont' build web client on wsl2 2020-05-26 11:54:19 -04:00
Pedro Pinto fd89241611 Updated externals pointer 2020-05-26 11:49:29 -04:00
John Colvin a69118c5d3 fix line endings in release_notes 2020-05-22 23:42:27 -05:00
John Colvin 5accd0e2b4 Merge branch 'master' into prod 2020-05-22 19:14:16 -05:00
John Colvin df5ad61cd2 release notes for synthetic gui integer tick sizes 2020-05-22 19:12:29 -05:00
John Colvin 4a7674eb29 synthetic gui supports integer tick sizes 2020-05-22 18:15:53 -05:00
Eric Harding 5a458287f4 update license search info in disable_onixs.py 2020-05-22 15:18:46 -04:00
John Colvin 5921f9f6b5 Merge branch 'master' into prod 2020-05-22 12:34:18 -05:00
John Colvin 4c73a63ae8 release notes for bug fix (unsorted custom state grid params in orderbook) 2020-05-22 12:30:53 -05:00
John Colvin 42b1c921a8 OrderBook custom state grid params are sorted alphabetically (again) 2020-05-22 12:24:42 -05:00
Pedro Pinto fab3334b1e Updated release notes 2020-05-22 11:23:03 -04:00
Pedro Pinto 7239dd3ade Merge branch 'master' into prod 2020-05-22 11:20:17 -04:00
John Colvin dd4c8eb649 Merged csharp-api-examples-sln into master 2020-05-22 10:19:13 -05:00
John Colvin 76a29bdc8a client ui supports all integer tick sizes (not just 1) 2020-05-21 15:26:50 -05:00
John Colvin 06d708f3ea new csharp api examples 2020-05-21 15:17:32 -05:00
John Colvin e2f70cbc19 minor config tweaks needed after upgrading VisualStudio 2020-05-21 13:04:00 -05:00
Pedro Pinto 9d54e47a03 Update CMakeDeps 2020-05-19 17:00:24 -04:00
Eric Harding 5345ba8ff8 securities for sending orders ilink3 test 2020-05-19 15:58:56 -05:00
John Colvin faccdf581e Merged in limit-algo-clean-history (pull request #413)
Limit algo clean history
2020-05-19 13:53:07 +00:00
John Colvin 1a64714a54 always log errors observed in LadderGridHost._volume_from_row (not just when it fails after max_num_tries) 2020-05-18 17:03:39 -05:00
John Colvin 3ba6792a85 wrap LadderGridHost._volume_from_row in try/catch (addresses Pure client crash) 2020-05-18 16:46:23 -05:00
Pedro Pinto c32a7f1d62 Made native orders calculate route when recovered from disk.
This is in response to the Pure debacle of May 17th. CME changed the
MSGW used for certain products. As we only route orders once, when
they are created, any orders that already existed never transitioned
to the new MSGW. This made it so that users could no longer control
these orders. With this change we recalculate routes when the orders
are first loaded from disk. Assuming we have up to date securities,
something that sometimes does not happen unfortunately, this should
result in the order being automatically relocated to its new MSGW. As
a bonus we also get the hability to change MSGW configuration even if
there are working for that MSGW.
2020-05-18 08:59:42 -04:00
John Colvin 10e75f30e6 fix algo params grid enum template for GenericOrderVMs 2020-05-15 13:16:25 -05:00
John Colvin 20eac19c4f GenericOrderVM - normalize white space 2020-05-15 13:16:25 -05:00
John Colvin 8c0765a2c7 fix build 2020-05-15 10:34:59 -05:00
Taha Afzal 7a1f88e813 Merge branch 'limit-algo-clean-history' of bitbucket.org:bluetradesys/spark into limit-algo-clean-history 2020-05-14 22:01:32 -04:00
Taha Afzal 3420f14c8a DeleteAndCover detached hedges by default: 2020-05-14 22:01:22 -04:00
Eric Harding 9bf05973bd Add in/out to logs 2020-05-14 20:51:15 -05:00
John Colvin 86277dfbfe config toggle for new 'specific' CustomOrderDefinition params dialog (disabled by default) 2020-05-14 20:14:51 -05:00
John Colvin 4839e464c6 added dialog to edit CustomOrderDefinition params of a specific algo node 2020-05-14 20:02:14 -05:00
John Colvin dc2ad071a9 GenericOrderHelper should use 'FirstOrDefault' (not 'First') 2020-05-14 19:59:06 -05:00
John Colvin 65e49992f9 LimitCoverAlgo.do_update - remove redundant conversion from DynamicOrderDefinition to LimitCoverDefinition 2020-05-14 17:08:01 -05:00
John Colvin afbb9ae92d LimitCover algo - 'Hedge Ticks' and 'Top Ticks Offset' params actually assigned to DeleteAndCover child 2020-05-14 12:29:29 -05:00
John Colvin ff088142d9 fix custom state output grid 2020-05-14 12:29:29 -05:00
Pedro Pinto 93b7cf346b Added an option for the Cover algo to detach hedges. 2020-05-14 10:20:46 -04:00
Eric Harding f0edeadd50 Make shared mem size configurable 2020-05-13 20:29:38 -05:00
Eric Harding cea4d05bc4 log every msg 2020-05-13 20:19:48 -05:00
Taha Afzal 8ee9b84488 Merge branch 'limit-algo-clean-history' of bitbucket.org:bluetradesys/spark into limit-algo-clean-history 2020-05-13 20:39:13 -04:00
Taha Afzal e3648f5373 Better Logging 2020-05-13 20:39:07 -04:00
John Colvin facd3a9d81 LimitCoverAlgo - remove unnecessary else block in 'do_before_launch' 2020-05-13 17:26:13 -05:00
John Colvin 84969cb481 LimitCoverAlgo - dont update me->child_qty in the child_changed callback, since its async 2020-05-13 17:21:32 -05:00
John Colvin 6d521e77db LimitCoverAlgo - don't override do_launch_attached (it's not supported) 2020-05-13 17:15:30 -05:00
John Colvin 82c49f8f49 remove redundant methods: tick_up/less, payup/less 2020-05-13 17:15:30 -05:00
John Colvin e074309da1 typo broke build (missed constructor in 'TickOffset' -> 'PriceOffset' refactor) 2020-05-13 17:15:30 -05:00
John Colvin 2d622a5f39 LimitCover algo - convert 'ticks_offset' param be in price terms 2020-05-13 16:55:01 -05:00
John Colvin d51d20e84c LimitCover algo - remove cancel_on_blah params, add hedge_ticks 2020-05-13 16:18:46 -05:00
John Colvin 2ccdef9dc5 fix clang build errors 2020-05-13 15:59:45 -05:00
John Colvin 42e62a30bf to_string(OrderError) uses generated protobuf enum Name method 2020-05-13 15:32:27 -05:00
Eric Harding 0b7208784b Can't take the double negatives anymore 2020-05-13 14:01:16 -05:00
John Colvin c233c607fa Formatter.h - pass strings/templated args by const ref instead of value 2020-05-13 13:40:07 -05:00
John Colvin 462cb930f7 dont redefine ChildTemplate 2020-05-13 13:20:55 -05:00
Eric Harding 059ccbdab6 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ilink3_gateway 2020-05-13 11:05:31 -05:00
Pedro Pinto 2c0f920422 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2020-05-13 08:54:14 -04:00
Pedro Pinto 5ee26065ca Updated .gitignore 2020-05-13 08:53:52 -04:00
John Colvin 550a4e8d50 add config toggle to enable/disable LimitCover algo 2020-05-12 19:11:16 -05:00
John Colvin 99b8221efc LimitCoverAlgo unit tests 2020-05-12 19:11:16 -05:00
John Colvin 19e74c30f8 AlgoTestDriverBase - AlgoState << operator 2020-05-12 19:03:53 -05:00
John Colvin 26693a4c98 AlgoTestDriverBase - after_child_changed callback, and find_child method 2020-05-12 19:03:53 -05:00
John Colvin d8bdb69a7c AlgoTestDriverBase - ChildTemplate impl, added OrderDefinition param to child_launching callback 2020-05-12 19:03:53 -05:00
John Colvin 6d357a74e1 AlgoTestDriverBase - expose cancel/cancel_child methods 2020-05-12 19:03:53 -05:00
John Colvin 73f4e4814c LimitCover algo for pure 2020-05-12 19:03:53 -05:00
John Colvin 312bc4be15 add Formatter.h (defines commonly needed << operators for algos, basic string formatting utils) 2020-05-12 18:55:37 -05:00
John Colvin a0c3ba449a add DynamicOrderAdapter (util to keep param proto conversion code out of the algo) 2020-05-12 18:55:37 -05:00
John Colvin d5a2a7f3f0 orderman_utils.h pragma once 2020-05-12 18:55:37 -05:00
John Colvin eda8c94def replicate_to_linux_vm.py - ignore some dirs generated by visual studio 2020-05-12 18:55:37 -05:00
John Colvin 9a8e80e149 add payup/less price calc utils (Security.h)' 2020-05-12 18:55:37 -05:00
John Colvin 6a7e3d739d fix typo 'epislon' -> 'epsilon' 2020-05-12 15:58:00 -05:00
Taha Afzal e6b8274813 DeletAndCoverOrderDefinition does not contain the nested enum anymore 2020-05-12 15:58:00 -05:00
Taha Afzal 9783520c62 DeleteAndCover completed 2020-05-12 15:58:00 -05:00
Taha Afzal 7e7f0bc37f Adding DeleteAndCover proto 2020-05-12 15:58:00 -05:00
Eric Harding 75eaf8c3d4 IL3ExecutionReportParser 2020-05-12 13:54:28 -05:00
John Colvin b9e2ecdffa Merged in transitive-security-compare (pull request #411)
Transitive security compare

Approved-by: Eric Harding
2020-05-11 19:42:50 +00:00
Eric Harding f1c021a474 Merged in security_preload (pull request #412)
Security preload
2020-05-11 19:41:00 +00:00
Eric Harding 6fda54ffa1 Merged master into security_preload 2020-05-11 15:44:50 +00:00
Taha Afzal bdb0adc16c TASCounter test fixed 2020-05-07 13:07:44 -04:00
Eric Harding 4b19b0ea14 update launch.json 2020-05-06 18:26:03 -05:00
Eric Harding fab1b4dd0f Add SecurityPreload key to pre-load securities from the security DB for sunday startup. 2020-05-06 18:25:29 -05:00
John Colvin f3ed3075cf fix Security comparison impl (its transitive now) 2020-05-06 17:35:46 -05:00
John Colvin 742af36b93 Security comparison unit test (addresses bug that caused Krandall OrderBook crash) 2020-05-06 17:35:46 -05:00
John Colvin c573b5fbb2 add ProtoBufCapture extension (writes TableUpdate state to file when subscription is disposed) 2020-05-06 17:30:00 -05:00
Eric Harding 2d6d4af594 add vscode spark local linux debugger settings 2020-05-06 12:27:11 -05:00
Eric Harding 1e8fcd794f Update ExecutionReport::exchange_order_id 2020-05-06 12:22:33 -05:00
John Colvin 2dcfad979c move FileStreamHelper.cs to spark_core 2020-05-06 12:11:03 -05:00
Eric Harding 726329c4bf Add ilink3 execution report parser and order test 2020-05-06 11:56:52 -05:00
Eric Harding 2d01f3f0f4 CME Certification requires very specific UUID generation 2020-05-06 11:56:01 -05:00
Eric Harding 2a595c0fd7 cme cert hard coding 2020-05-06 11:54:32 -05:00
Taha Afzal ba7e35649e Ignore Spread Pos Limits are defaulted to true 2020-05-06 10:36:40 -04:00
Eric Harding 5f9866c9e8 SecurityCache now hands out raw pointers. SecurityOracle now indexes exchange security key 2020-05-05 11:17:16 -05:00
Pedro Pinto cda4a870b7 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2020-05-05 06:14:30 -04:00
Pedro Pinto b3a8564365 Fixed int32 params in DynamicOrders to be int32 rathern than uint32. 2020-05-05 06:13:40 -04:00
Pedro Pinto 1e0114a4cf Treated disconnected orders as working. 2020-05-05 06:13:06 -04:00
Eric Harding 3a6e95ef05 Downgrade security scanner message to info 2020-05-04 16:16:55 -04:00
Eric Harding 78f1cf88e0 cmake needs love too 2020-05-04 11:16:21 -05:00
Eric Harding 4877436877 Ilink3 order unit test 2020-05-04 11:32:30 -04:00
Eric Harding 1b43124756 Add ReentrancyGuard.h 2020-05-02 13:10:49 -04:00
Eric Harding 6875263743 Update 'update' instructions for spark_sdk 2020-05-01 14:17:07 -04:00
Taha Afzal eac828f323 Merge branch 'master' into TASCounter 2020-05-01 13:49:27 -04:00
Taha Afzal 1635d6f649 Added test for TASCounter 2020-05-01 13:48:50 -04:00
Eric Harding a1feb3fddf PrimaryGatewayConnectionEnforcement 2020-05-01 13:22:35 -04:00
Eric Harding 2da535e9ff Update onixs license 2020-05-01 12:51:31 -04:00
Eric Harding 024328bf8b Refactor empty session 2020-05-01 11:44:03 -04:00
Taha Afzal a98be44a40 Merge branch 'master' into TASCounter 2020-04-30 13:59:49 -04:00
John Colvin 6210ac2ee6 OrderBook custom state grid only shows data if the selected rows are of the same root/branch 2020-04-29 16:21:17 -05:00
Eric Harding 80d7e34bf1 Fixes for updated sbe codegen 2020-04-28 18:16:45 -04:00
Pedro Pinto cfebab0488 Merged in api_improvements (pull request #405)
Api improvements

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2020-04-28 20:06:53 +00:00
Pedro Pinto eacd766359 Updated release notes. 2020-04-28 16:05:21 -04:00
Pedro Pinto 8fb8f8ff58 Merge branch 'master' into api_improvements 2020-04-28 15:58:43 -04:00
Taha Afzal f4b22816a2 Putting config to hide TASCounter 2020-04-27 23:39:41 -04:00
Eric Harding 8ab69c919b Improve cme provided error message 2020-04-27 16:56:32 -04:00
Eric Harding 99531bcfa0 Update ilink3 generated templates 2020-04-27 16:50:54 -04:00
Eric Harding 6bd516d4f2 cleanup 2020-04-27 11:26:42 -04:00
Taha Afzal 1029a464d7 Fixing the bts::timestamp tp protobuf timestamp function (unused, but useful) 2020-04-27 09:57:21 -04:00
Taha Afzal fd8b8e03ea cleanup, comments 2020-04-26 23:10:16 -04:00
Eric Harding 1a1d771e1b addition ilink3 tests 2020-04-25 13:18:58 -04:00
Eric Harding 050fcf7c6e Passing simple log in test 2020-04-24 12:31:30 -04:00
Pedro Pinto 41cf66a943 Added the hability to retrieve all securities in the API. 2020-04-24 08:00:13 -04:00
Pedro Pinto 1c57c9a012 Added position updates in the API. 2020-04-24 07:03:40 -04:00
Taha Afzal 8ccab66120 Merge branch 'add_custom_algo_state_reporting_to_api' into TASCounter 2020-04-23 18:14:28 -04:00
John Colvin e2b2f7ec9c better default widths for custom state grid columns 2020-04-23 16:11:35 -05:00
John Colvin b3c897bf0f custom state grid params are sorted alphabetically 2020-04-23 16:02:38 -05:00
John Colvin 87c9f54c8b custom state grid populates with first child row that has params (if selected row doesn't have any itself) 2020-04-23 15:59:49 -05:00
John Colvin df56b76ab6 OrderBookRowVM - remove dead property 'is_visible' 2020-04-23 15:54:30 -05:00
Taha Afzal 28ff13db11 Merge branch 'add_custom_algo_state_reporting_to_api' into TASCounter 2020-04-23 13:23:30 -04:00
Taha Afzal 2957075769 comments 2020-04-23 13:22:33 -04:00
John Colvin a6477824a8 OrderBookVM - clear the previous 'custom state' params before adding the new ones 2020-04-23 11:16:47 -05:00
Taha Afzal 17431f5ac0 Code complete, output is not showing in the UI which is making testing harder 2020-04-23 00:09:23 -04:00
Taha Afzal 8708f02fe0 Merge branch 'add_custom_algo_state_reporting_to_api' into TASCounter 2020-04-21 17:44:58 -04:00
Taha Afzal 752fb2bc4a Merge branch 'master' into TASCounter 2020-04-21 17:10:32 -04:00
Taha Afzal 6efb78ecf9 Code complete 2020-04-21 14:26:32 -04:00
John Colvin 75c1970aef remove useless IObservable 'Select' op in 'SubscribeEach' extension 2020-04-20 10:39:31 -05:00
John Colvin c2583518d7 OrderBook was only updating custom state grid when selection changed 2020-04-20 10:37:12 -05:00
Eric Harding aadeae3201 update cmake, avoid web build on wsl 2020-04-17 22:38:53 -04:00
Eric Harding b72e8aed02 ilink3 test code 2020-04-17 20:30:11 -05:00
John Colvin 8792871387 add algo custom state grid to orderbook (right panel) 2020-04-17 16:36:05 -05:00
Eric Harding 68a593af7b Add more info to system test 2020-04-17 16:48:10 -04:00
Eric Harding 0820cc3e9a Fix a very subtle bug in serializer 2020-04-17 13:15:12 -05:00
Eric Harding b1ba9a1315 update ilink code gen readme 2020-04-16 14:03:53 -05:00
John Colvin 17c2321a82 Merged in SP-289-naudio-crash (pull request #407)
SP-289 naudio crash

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2020-04-16 18:11:18 +00:00
John Colvin 0affc8a942 SP-289 naudio crash 2020-04-16 12:07:44 -05:00
John Colvin ce36ea4109 move Delegates.cs to spark_core 2020-04-16 10:47:41 -05:00
Eric Harding 39ce4323a1 Add CME cert command line tool 2020-04-13 16:18:59 -05:00
Pedro Pinto 7c29a3d1d8 Added custom state reporting to API. 2020-04-13 11:30:32 -04:00
Eric Harding d9fcd290d9 source files don't need to be executable 2020-04-13 09:53:28 -05:00
Taha Afzal 90bc20575a TimeStamps fixed 2020-04-13 08:56:49 -04:00
Pedro Pinto cb268f505a Added settlements to the new algo API. 2020-04-13 08:19:55 -04:00
Pedro Pinto dfd93b836b Updated externals 2020-04-13 07:44:52 -04:00
Eric Harding 05939b02c5 save 2020-04-12 15:11:46 -05:00
Taha Afzal 28e9c66675 Added all the state, order execution remains 2020-04-10 12:54:57 -04:00
Eric Harding aa91638fe4 ilink3 receive buffer handler + tests 2020-04-10 10:55:53 -05:00
Taha Afzal c0520d6a31 Initial Check-in, skeleton code 2020-04-09 09:30:00 -04:00
Eric Harding 065fc54f73 IL3Reactor 2020-04-07 14:05:31 -05:00
Pedro Pinto 1ba6e3280c Updated CMakeDeps 2020-04-06 09:38:19 -04:00
Eric Harding 3a6f50dfea Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2020-04-03 15:40:30 -04:00
Eric Harding d711f99548 fix externals pointer 2020-04-03 15:40:15 -04:00
pfisher 07ace80871 fixed keycode.conf 2020-04-03 13:40:56 -05:00
John Colvin 93c6277bfc Merged prod into master 2020-04-03 13:20:33 -05:00
Jake Norbut 7ac9d05ae2 Merged in manual_fills_permissions (pull request #403)
add permission filter to manual fills

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2020-04-03 18:18:16 +00:00
Eric Harding 95fb13f58b Fix some build issues 2020-04-01 15:21:04 -05:00
Eric Harding 4eb2931cb8 Additional ilink3 refactoring 2020-03-28 21:08:47 -05:00
Eric Harding 5326015a48 Testcase for framer 2020-03-27 13:46:29 -05:00
Eric Harding fcbe319470 Split out functional pieces for easier testing 2020-03-27 09:14:30 -05:00
Eric Harding 5ad21c88b2 Add tests and refactor includes for faster build 2020-03-25 21:15:19 -05:00
Eric Harding 5a4b31ccc0 add cpp sample to service.md api page 2020-03-20 12:10:40 -04:00
John Colvin fb568aab39 SP-288 ladder crash (IndexOutOfRange ex accessing price array) 2020-03-19 15:28:27 -05:00
Eric Harding 64416de10d Fix the braces in my sample. 2020-03-16 20:41:23 -04:00
Eric Harding 46953cb639 add c# market data sample 2020-03-16 20:39:48 -04:00
Eric Harding 7e68a235ee Add securities sub to client_sdk_example 2020-03-16 15:18:39 -04:00
Eric Harding 3a9c1d557b Split Retransmit of old vs new session logic 2020-03-09 16:29:15 -05:00
Jake Norbut 85f2285ac0 add permission filter to manual fills 2020-03-09 09:28:23 -05:00
Eric Harding 70e0979836 override sim port for sdk image 2020-03-06 12:45:09 -05:00
Kevin Darby 1f80c3366e Merged in master (pull request #401)
Master
2020-03-03 19:16:43 +00:00
Eric Harding 6fb5b83249 Only send Negotiate if the session is new 2020-02-28 15:33:27 -06:00
Eric Harding 95830fafdf send_cancel 2020-02-27 14:48:56 -06:00
Eric Harding d68b03bb91 send_cancel_replace 2020-02-26 17:56:50 -06:00
Eric Harding e7062ce01e send_order call for link3 2020-02-26 17:36:18 -06:00
Kevin Darby 19e66a1c32 add a new stunnel path for Pete 2020-02-25 11:16:40 -05:00
Pedro Pinto 932738f98e Merge branch 'master' into ilink3_gateway 2020-02-20 08:39:47 -05:00
Pedro Pinto 1426b00e83 Added fake iLink3 gateway 2020-02-19 16:21:06 -05:00
Pedro Pinto e724409b58 Added jetfix support for string_view 2020-02-19 10:55:56 -05:00
Pedro Pinto 9cf828e9a4 Implemented iLink3 order 2020-02-18 15:37:06 -05:00
Eric Harding 41408fb609 ILink3ExecutionGateway placeholder 2020-02-14 16:07:44 -06:00
Eric Harding 4c21e32838 ILink3GW core gateway 2020-02-14 16:06:58 -06:00
Eric Harding 385f3ea402 add unix_nano_now() to datetime.h 2020-02-14 16:05:00 -06:00
Eric Harding 8d6d8cab18 Update SecureLogon for ilink3 2020-02-14 16:05:00 -06:00
Eric Harding 01615f824a Generated ilink3 messages 2020-02-14 16:05:00 -06:00
Pedro Pinto c6779ddc90 Reactore GLBX native order for iLink3. 2020-02-13 15:26:33 -05:00
Pedro Pinto 46eff65435 Changed IExecutionGateway to support non FIX gateways. 2020-02-12 10:11:53 -05:00
John Colvin 17b314ea3a Merge branch 'master' into algo-type-config 2020-02-07 13:35:50 -06:00
John Colvin e713210030 remove obsolete algo types from hardcoded defaults 2019-10-29 18:07:32 -05:00
John Colvin c057bf18f6 Merge remote-tracking branch 'origin/master' into algo-type-config 2019-10-29 18:07:01 -05:00
John Colvin 17a0bef824 Merge remote-tracking branch 'origin/master' into algo-type-config 2019-10-21 12:15:00 -05:00
John Colvin 9180c48b67 CompositeAlgoVM 'group' is AlgoDisplayGroup.Studio 2019-10-14 15:29:02 -05:00
John Colvin 01acbe08f5 remove dead code 2019-10-14 15:26:01 -05:00
John Colvin 8a47cd4821 exchange/security based algo type config 2019-10-14 15:19:36 -05:00
John Colvin 03050d9b07 refactor some Disposable.replace calls so that the prev hook is disposed before the new one is created 2019-10-14 13:25:21 -05:00
John Colvin 9178878880 AlgoStudio model does not really need to import AdminModel (just UserTuple) 2019-10-14 13:25:21 -05:00
John Colvin f38b1c7af9 ContractPickerModel.result getter performs dictionary lookup (was brute force enumeration) 2019-10-14 13:25:21 -05:00
John Colvin 45cd958b1e make security criteria clause immutable 2019-10-14 13:25:21 -05:00
John Colvin 4d49c8f030 add .rej to gitignore 2019-10-14 13:25:21 -05:00
John Colvin b2227ce37b DynamicOrderHelper stores dyn types in map 2019-10-14 13:25:21 -05:00
679 changed files with 186661 additions and 11047 deletions

No files matched your search

+3
View File
@@ -34,4 +34,7 @@ trace_data/
/doc/user/public/*
.ionide/*
/.ignore
*.rej
/build
.clangd/
.venv/**
+29 -1
View File
@@ -1,13 +1,41 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Spark Local",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
"args": [
"run",
"test",
"-a",
"OutrightVanillaOrder"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
"environment": [
{ "name": "TZ", "value": "America/Chicago" }
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "SparkVM Launch",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"run"
"test",
"-a",
"[ilink3]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
+14 -2
View File
@@ -48,9 +48,9 @@
}
},
{
"label": "linux build (Debug)",
"label": "linux_build",
"type": "shell",
"command": "cd build/ninja_debug; time ninja",
"command": "docker exec -i ${USER}s_docker bash -c \"cd build/ninja_${input:build_configuration} && ninja\"",
"group": {
"kind": "build",
"isDefault": true
@@ -59,5 +59,17 @@
"$gcc"
]
}
],
"inputs": [
{
"id": "build_configuration",
"type": "pickString",
"default": "debug",
"description": "Configuration:",
"options": [
"debug",
"release"
]
}
]
}
+13 -2
View File
@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.5)
project(spark)
set (USE_ONIX "TRUE")
if (WIN32)
set (platform "windows")
else ()
@@ -40,11 +42,17 @@ else ()
-fPIC -Wall -pedantic -Wextra -Werror \
-Wno-missing-braces -Wno-ignored-qualifiers \
--std=c++17 -fdiagnostics-color")
if (${USE_ONIX})
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DUSE_ONIX")
endif()
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wno-undef")
add_subdirectory(src/server/sparkd)
add_subdirectory(src/lib)
add_subdirectory(src/web)
# add_subdirectory(src/web)
add_subdirectory(doc/user)
add_subdirectory(src/sdk)
add_subdirectory(src/data_files)
@@ -55,11 +63,14 @@ else ()
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
COMMENT "Creating server runtime layout"
DEPENDS sparkd data_files_dir)
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libACE-5.7.5.so" DESTINATION bin)
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0" DESTINATION bin)
endif()
@@ -124,4 +124,17 @@ service OrderService {
```
### C++ Example
```cpp
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/OrderService.client.pb.h"
using namespace bts::api;
ServiceContext ctx;
auto orders = ctx.locate<orderman::OrderService>();
auto unsub = orders.subscribe_fills().subscribe([](const auto& update) {
std::cout
<< "Added " << update.added().size() << " items, "
<< "Removed " << update.removed().size() << " items, "
<< "Updated " << update.updated().size() << " items, "
<< "Total items " << update.all().size() << std::endl;
});
std::this_thread::sleep_for(std::chrono::minutes(5));
```
+49 -1
View File
@@ -1,3 +1,51 @@
+++
title = "Market Data"
+++
+++
Use the MarketDataService to subscribe enumerate all available securities.
```cs
using System;
using System.Threading;
using bts.api;
using bts.api.core;
namespace sample {
class Program {
static void Main(string[] args) {
using var context = new ServiceContext();
var md = context.locate<MarketDataService>();
using var sub = md.subscribe_securities().Subscribe(update => {
foreach (var security in update.added) {
Console.WriteLine($"Added security {security.Id}: {security.Symbol}");
}
});
Thread.Sleep(TimeSpan.FromMinutes(5));
}
}
}
```
```cpp
#include "lib/api/public/ServiceContext.h"
#include "spark/lib/domain_model/Security.h"
#include "lib/api/services/MarketDataService.client.pb.h"
#include <thread>
using namespace bts::api;
int main(int argc, char** argv) {
ServiceContext ctx;
auto md_service = ctx.locate<bts::api::MarketDataService>();
auto sub = md_service.subscribe_securities().subscribe([](const auto& update) {
for (const auto& s : update.added()) {
std::cout << "Added security: " << s->id() << ": " << s->symbol() << std::endl;
}
});
std::this_thread::sleep_for(std::chrono::minutes(5));
return 0;
}
```
@@ -68,12 +68,21 @@ To update your Spark SDK simply download an unzip a new [spark_sdk.zip](https://
To preserve the old image so you can go back to it later run `docker tag spark_sdk spark_sdk:your_custom_tag` before loading the new SDK image.
<!--
Then stop, remove and restart spark_sdk.
### Re-build the Dev Container
If you are using Visual Studio Code you also need to rebuild the dev-container so it picks up the new image.
#### Inside VSCode
To re-build from inside Visual Studio Code. Click the 'Remote Explorer' icon on the left hand side.
You should see your spark_sdk project listed under the Containers section. Right click on it and select **rebuild-container**.
#### Command Line
To re-build from the command line run `docker ps -a` and find the row with the spark_sdk image.
```
c:\bts> docker stop spark_sdk
c:\bts> docker rm spark_sdk
c:\bts> docker run -d -v %cd%:/sdk --name spark_sdk bluetradingsytems/spark_sdk
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
54d96cfa3888 spark_sdk "/bin/sh -c 'echo Co…" About a minute ago Up About a minute 127.0.0.1:9000-9001->9000-9001/tcp quizzical_lichterman
```
Then run `docker rm 54d96cfa3888` (use your container id instead of 54d96cfa3888) or `docker rm quizzical_lichterman` (use your container name).
``` -->
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/pfisher/bts/DJ_ICE/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+22 -1
View File
@@ -1,9 +1,29 @@
+++
+++
title = "Release notes"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.16.2
### User Visible Change
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
## 2.16.1
### Bug Fixes
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
## 2.16.0
### Bug Fixes
- Solved an issue that could result in incorrect ladder levels
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
### API Changes
- Add C# client side API samples for order entry, position monitoring and market data observation
## 2.15.10
### Bug Fixes
- Solved an issue that could cause the user interface to fail when playing some alert sounds
### API Changes
- Added settlement and position information to the API
- Added the ability for user algos to report custom state to the user interface
- Added the ability for algos to discover all active securities
## 2.15.3
### Bug Fixes
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
@@ -708,3 +728,4 @@ insert_anchor_links = "right"
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
- Corrected cash per point values, used in pnl calculations, for a number of products
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
+6
View File
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "2.1.508",
"rollForward": "latestMajor"
}
}
+21
View File
@@ -0,0 +1,21 @@
astroid==2.7.3
autopep8==1.5.7
cffi==1.14.6
colorama==0.4.4
isort==5.9.3
lazy-object-proxy==1.6.0
mccabe==0.6.1
pkg_resources==0.0.0
platformdirs==2.3.0
protobuf==3.19.1
pybars3==0.9.7
pycodestyle==2.7.0
pycparser==2.20
pylint==2.10.2
PyMeta3==0.5.1
termcolor==1.1.0
toml==0.10.2
typed-ast==1.4.3
typing_extensions==4.1.1
wrapt==1.12.1
yapf==0.31.0
+4
View File
@@ -22,6 +22,10 @@
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
</PropertyGroup>
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
</PropertyGroup>
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
<ItemGroup>
+10
View File
@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
+51
View File
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace RiskUI {
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:RiskUI"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:MyNamespace="clr-namespace:RiskUI;assembly=RiskUI"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:CustomControl1/>
///
/// </summary>
public class CustomControl1 : Control {
static CustomControl1() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
}
+24
View File
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<RootNamespace>bts</RootNamespace>
<LangVersion>7.2</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<Version>$(major).$(minor).$(patch)</Version>
<Version Condition="'$(version)'=='..'">1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../spark_api/spark_api.csproj" />
<ProjectReference Include="../Spark/Spark.csproj" />
</ItemGroup>
</Project>
+17
View File
@@ -0,0 +1,17 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RiskUI">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
+122 -2
View File
@@ -1,24 +1,33 @@
using bts;
using bts.admin.risk;
using bts.api;
using bts.api.admin;
using bts.api.core;
using bts.spark;
using bts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
[Export]
class AdminModel : IDisposable {
public readonly UserTuple user;
readonly AdminService _service;
readonly ErrorReporter _error_reporter;
IDisposable _dispose;
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
[ImportingConstructor]
AdminModel(AdminService service, UserTuple user) {
AdminModel(AdminService service, UserTuple user, SecurityStore security_store, ErrorReporter error_reporter) {
this.user = user;
_service = service;
_error_reporter = error_reporter;
// accounts
on_accounts = service.subscribei_admin_data()
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
@@ -31,6 +40,34 @@ namespace mbts.spark {
new Dictionary<uint, string>(0))
.Replay(1)
.RefCount();
// account risk limits
account_security_risk_limits = service.subscribei_admin_data().Where(ad => ad.all.Any())
.Select(upd => {
AdminDataImmutable admin_data = upd.all.First();
return admin_data.Accounts.SelectMany(acc =>
(acc.HasProprietaryRiskLimits ? acc.ProprietaryRiskLimits.SecurityLimits : Enumerable.Empty<SecurityRiskLimitUnitImmutable>())
.Select(api_limit => AccountSecurityRiskLimitsDataHelper.to_client_data(acc.Id, api_limit)))
.ToDictionary(AccountSecurityRiskLimitsDataHelper.key_fn);
})
.Table(AccountSecurityRiskLimitsDataHelper.key_fn);
// this isn't the most efficient join, but the SecurityStore join is more complex than a simple
// left join operation, because missing securities are retrieved asynchronously, then merged back into the output sequence.
// Until it is known that this inefficiency is problematic, best to leave the battle-tested SecurityStore code alone.
account_security_risk_limits = security_store.join<
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>,
AccountSecurityRiskLimits,
AccountSecurityKey,
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>>(account_security_risk_limits,
cli_lim => cli_lim.security_key,
table => (table.state.Values, table.added, table.changes, table.removed, AccountSecurityRiskLimitsDataHelper.key_fn),
(all, added, changes, removed, key_fn) =>
new TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>(
ImmutableDictionary.CreateRange<AccountSecurityKey, AccountSecurityRiskLimits>(
all.Select(tup => new KeyValuePair<AccountSecurityKey, AccountSecurityRiskLimits>(tup.val.key, risk_lim_with_sec(tup)))),
added.Select(risk_lim_with_sec),
changes.Select(ch => new Change<AccountSecurityRiskLimits>(risk_lim_with_sec(ch.Old), risk_lim_with_sec(ch.New))),
removed.Select(risk_lim_with_sec),
AccountSecurityRiskLimitsDataHelper.key_fn));
// users
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
@@ -45,12 +82,16 @@ namespace mbts.spark {
.Cache() :
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
_dispose = on_accounts.Subscribe(map => _account_map = map);
// local methods
AccountSecurityRiskLimits risk_lim_with_sec((SecurityImmutable? sec, AccountSecurityRiskLimits val) tup) =>
tup.val.with(security: tup.sec);
}
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
public IObservable<TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>> account_security_risk_limits { get; }
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
user.permissions == UserTuple.Permissions.Admin ?
@@ -68,7 +109,86 @@ namespace mbts.spark {
public AccountTuple? get_default_account() =>
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
public async Task<Result<AdminServiceOperationResult>[]> set_risk_limits(IReadOnlyDictionary<uint, IEnumerable<(uint, RiskLimits)>> state) {
var results = await Task.WhenAll(state.Select(impl));
return results;
async Task<Result<AdminServiceOperationResult>> impl(KeyValuePair<uint, IEnumerable<(uint, RiskLimits)>> kv) {
var prlimits = new ProprietaryRiskLimits();
foreach (var (sec, lim) in kv.Value) {
var api_lim = AccountSecurityRiskLimitsDataHelper.from_client_data(sec, lim);
prlimits.SecurityLimits.Add(api_lim);
}
return await _service.update_proprietary_risk_limits_async(new ProprietaryRiskLimitsAPIArgument {
AccountId = kv.Key,
ProprietaryRiskLimits = prlimits
});
}
}
public Task<(bool success, string error_msg)> add_or_update_risk_limits(uint account_key, IEnumerable<(uint security_key, RiskLimits limits)> new_limits) {
return _modify_risk_limits("Updating Risk Limits", account_key, add_limits);
//
ProprietaryRiskLimits add_limits(ProprietaryRiskLimits prlimits) {
foreach (var (security_key, limits) in new_limits) {
var api_limits = AccountSecurityRiskLimitsDataHelper.from_client_data(security_key, limits);
if (prlimits.SecurityLimits.TryGetFirstIndex(l => l.SecurityKey == security_key, out int index)) {
prlimits.SecurityLimits[index] = api_limits;
} else {
prlimits.SecurityLimits.Add(api_limits);
}
}
return prlimits;
}
}
public Task<(bool success, string error_msg)> delete_risk_limits(uint account_key, IEnumerable<uint> security_keys) {
return _modify_risk_limits("Deleting Risk Limits", account_key, remove_secs);
//
ProprietaryRiskLimits remove_secs(ProprietaryRiskLimits prlimits) {
var sec_key_set = new HashSet<uint>(security_keys);
for (int i = 0; i < prlimits.SecurityLimits.Count;) {
if (sec_key_set.Contains(prlimits.SecurityLimits[i].SecurityKey)) {
prlimits.SecurityLimits.RemoveAt(i);
} else i++;
}
return prlimits;
}
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
async Task<(bool success, string error_msg)> _modify_risk_limits(string op_desc, uint account_key, Func<ProprietaryRiskLimits, ProprietaryRiskLimits> modify) {
var api_limits = new ProprietaryRiskLimits();
var snapshot = await _get_current_admin_data_async();
if (!snapshot.HasValue)
return (false, "Timed out waiting for data from server.");
var mutable_snap = snapshot.Value.CloneToMutable();
if (!snapshot.Value.Accounts.TryGetFirst(acc => acc.Id == account_key, out var target_acci)) {
return (false, $"Account id '{account_key}' not found.");
}
var prlimits = target_acci.HasProprietaryRiskLimits ? target_acci.ProprietaryRiskLimits.CloneToMutable() : new ProprietaryRiskLimits();
prlimits = modify(prlimits);
if (prlimits is null) {
return (false, "Client-side modification function returned null.");
}
var req = new ProprietaryRiskLimitsAPIArgument {
AccountId = account_key,
ProprietaryRiskLimits = prlimits
};
var reply = await _error_reporter.safe_execute<AdminServiceOperationResult>(
() => _service.update_proprietary_risk_limits_async(req),
r => r.IsSuccess,
r => r.ErrorText,
op_desc);
return (reply.success, reply.value.ErrorText);
}
async Task<AdminDataImmutable?> _get_current_admin_data_async() =>
await _service.subscribei_admin_data()
.Where(upd => upd.all.Any())
.Select(upd => (AdminDataImmutable?)upd.all.First())
.FirstOrDefaultAsync(TimeSpan.FromSeconds(5));
//async Task<AdminDataImmutable?>
}
}
}
+183
View File
@@ -0,0 +1,183 @@
using bts.api;
using bts.api.admin;
using bts.utils;
using System;
namespace bts.admin.risk {
readonly struct AccountSecurityKey : IEquatable<AccountSecurityKey> {
public AccountSecurityKey(uint account, uint security) {
this.account = account;
this.security = security;
}
public uint account { get; }
public uint security { get; }
public bool Equals(AccountSecurityKey other) =>
security == other.security &&
account == other.account;
public override bool Equals(object obj) =>
obj is AccountSecurityKey other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(security.GetHashCode(), account.GetHashCode());
public static bool operator ==(AccountSecurityKey a, AccountSecurityKey b) => a.Equals(b);
public static bool operator !=(AccountSecurityKey a, AccountSecurityKey b) => !a.Equals(b);
}
readonly struct AccountSecurityRiskLimits : IEquatable<AccountSecurityRiskLimits> {
readonly AccountTuple? _account;
public AccountSecurityRiskLimits(AccountSecurityKey key, RiskLimits limits, SecurityImmutable? security = null, AccountTuple? account = null) {
this.key = key;
this.limits = limits;
this.security = security;
_account = account;
}
public AccountSecurityKey key { get; }
public uint account_key => key.account;
public uint security_key => key.security;
public string account_name => _account?.name;
public SecurityImmutable? security { get; }
public RiskLimits limits { get; }
public bool Equals(AccountSecurityRiskLimits other) =>
key.Equals(other.key) &&
limits.Equals(other.limits);
public override bool Equals(object obj) =>
obj is AccountSecurityRiskLimits other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.auto_phase(key.GetHashCode(), limits.GetHashCode());
}
readonly struct RiskLimits : IEquatable<RiskLimits> {
public RiskLimits(
bool is_position_limit_enabled,
uint max_long_position,
uint max_short_position,
bool is_clip_limit_enabled,
uint clip_limit,
bool is_price_limit_enabled,
double min_price,
double max_price) {
//
this.is_position_limit_enabled = is_position_limit_enabled;
this.max_long_position = max_long_position;
this.max_short_position = max_short_position;
this.is_clip_limit_enabled = is_clip_limit_enabled;
this.clip_limit = clip_limit;
this.is_price_limit_enabled = is_price_limit_enabled;
this.min_price = min_price;
this.max_price = max_price;
}
// position
public bool is_position_limit_enabled { get; }
public uint max_long_position { get; }
public uint max_short_position { get; }
// clip
public bool is_clip_limit_enabled { get; }
public uint clip_limit { get; }
// price
public bool is_price_limit_enabled { get; }
public double min_price { get; }
public double max_price { get; }
public bool Equals(RiskLimits other) =>
is_position_limit_enabled == other.is_position_limit_enabled &&
max_long_position == other.max_long_position &&
max_short_position == other.max_short_position &&
is_clip_limit_enabled == other.is_clip_limit_enabled &&
clip_limit == other.clip_limit &&
is_price_limit_enabled == other.is_price_limit_enabled &&
min_price == other.min_price &&
max_price == other.max_price;
public override bool Equals(object obj) =>
obj is RiskLimits other && Equals(other);
public override int GetHashCode() {
const int non_bin_bits = 28;
// Evenly split the non-binary props across the first 28 bits.
// The last 3 bits are used to represent each of the 'is_enabled' (binary) props.
var hash = EqualityComparer.auto_phase(new[] {
max_long_position.GetHashCode(),
max_short_position.GetHashCode(),
clip_limit.GetHashCode(),
min_price.GetHashCode(),
max_price.GetHashCode()
}, non_bin_bits);
if (is_position_limit_enabled)
hash |= 1 << (non_bin_bits + 1);
if (is_clip_limit_enabled)
hash |= 1 << (non_bin_bits + 2);
if (is_price_limit_enabled)
hash |= 1 << (non_bin_bits + 3);
return hash;
}
}
static class AccountSecurityRiskLimitsDataHelper {
public static readonly Func<AccountSecurityRiskLimits, AccountSecurityKey> key_fn =
a => a.key;
// Don't make this a constructor. Subscribers to this data are discouraged
// from using the server side data (client/server coupling should be minimal).
public static AccountSecurityRiskLimits to_client_data(uint account_key, SecurityRiskLimitUnitImmutable api_data) =>
new AccountSecurityRiskLimits(
new AccountSecurityKey(account_key, api_data.SecurityKey),
new RiskLimits(
api_data.EnforceNetPosLimits,
api_data.NetLongPositionLimit,
api_data.NetShortPositionLimit,
api_data.EnforceClipLimits,
api_data.ClipSize,
api_data.EnforcePriceBands,
api_data.MinSellPrice,
api_data.MaxBuyPrice));
public static AccountSecurityRiskLimits with(this AccountSecurityRiskLimits cli_data, RiskLimits? limits = null, AccountTuple? account = null, SecurityImmutable? security = null, AccountSecurityKey? key = null) =>
new AccountSecurityRiskLimits(
key ?? cli_data.key,
limits ?? cli_data.limits,
account: account ?? new AccountTuple(cli_data.account_key, cli_data.account_name),
security: security ?? cli_data.security);
public static RiskLimits with(this RiskLimits limits,
bool? is_position_limit_enabled = null,
uint? max_long_position = null,
uint? max_short_position = null,
bool? is_clip_limit_enabled = null,
uint? clip_limit = null,
bool? is_price_limit_enabled = null,
double? min_price = null,
double? max_price = null) =>
new RiskLimits(
is_position_limit_enabled ?? limits.is_position_limit_enabled,
max_long_position ?? limits.max_long_position,
max_short_position ?? limits.max_short_position,
is_clip_limit_enabled ?? limits.is_clip_limit_enabled,
clip_limit ?? limits.clip_limit,
is_price_limit_enabled ?? limits.is_price_limit_enabled,
min_price ?? limits.min_price,
max_price ?? limits.max_price);
public static SecurityRiskLimitUnit from_client_data(uint security_key, RiskLimits limits) =>
new SecurityRiskLimitUnit {
SecurityKey = security_key,
NetLongPositionLimit = limits.max_long_position,
NetShortPositionLimit = limits.max_short_position,
MaxBuyPrice = limits.max_price,
MinSellPrice = limits.min_price,
ClipSize = limits.clip_limit,
EnforceNetPosLimits = limits.is_position_limit_enabled,
EnforceClipLimits = limits.is_clip_limit_enabled,
EnforcePriceBands = limits.is_price_limit_enabled
};
}
}
@@ -0,0 +1,193 @@
using bts;
using bts.api;
using bts.api.core;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class AlgoProviderFactory {
[ImportingConstructor]
AlgoProviderFactory(IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
var from_conf = config.Configuration.get<bts.config.AlgoTypeRuleSet>();
if (from_conf.rules.Count == 0) {
// There seems to be a config race bug, where the returned config values are empty, despite having entries in spark.conf.
// [John]I've only noticed it happen when attached to the debugger,
// but best to have a contingency plan in place anyways, to prevent any chance of regression.
// The fallback sets below mimic the old hardcoded algo type arrays.
var full_algo_set = new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(EnumHelper.get_values_except(
// non-factory algos
AlgoType.None,
AlgoType.Dynamic,
AlgoType.Composite,
// obsolete algos
AlgoType.Oco,
AlgoType.TimeBox,
AlgoType.Pricer,
AlgoType.Tick,
AlgoType.Trigger,
AlgoType.QueueHolder)),
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
var rules = new[] {
// Glbx: Futures/Spreads
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
exchanges: new[] { Venue.Glbx },
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
new AlgoTypeSetImmutable(
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader }),
full_algo_set.dynamic_types)),
// Gen: Futures/Spreads
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
exchanges: EnumHelper.get_values_except(Venue.Glbx, Venue.Unset).ToArray(),
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
new AlgoTypeSetImmutable(
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader, AlgoType.GlbxIceberg }),
full_algo_set.dynamic_types)),
// Gen: Synthetics
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
security_types: new[] { SecurityType.Synthetic }),
new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(new[] {
AlgoType.Spreader,
AlgoType.Iceberg,
AlgoType.Lean,
AlgoType.Join,
AlgoType.Cover,
AlgoType.Offset,
AlgoType.Top,
AlgoType.Splitter,
AlgoType.Scratch,
AlgoType.Prorata
}),
full_algo_set.dynamic_types))
};
// attachable
var attachable_algos =
ImmutableHashSet.CreateRange(new[] {
AlgoType.Iceberg,
AlgoType.Lean,
AlgoType.Join,
AlgoType.Cover,
AlgoType.Top,
AlgoType.Offset,
AlgoType.Scratch,
AlgoType.Prorata
});
var attachable_rules = Array.ConvertAll(rules,
satsi => new SecurityAlgoTypeSetImmutable(
satsi.security_criteria,
new AlgoTypeSetImmutable(
satsi.algo_type_set.factory_types.Intersect(attachable_algos),
satsi.algo_type_set.dynamic_types)));
Log.Config.warn($"Failed to load AlgoTypes config, falling back to the following algo set rules:" +
"\n\tStandard\n" +
rules.fmt_diagnostic("\n\t\t") +
"\n\n\tAttachable\n" +
attachable_rules.fmt_diagnostic("\n\t\t"));
security_algo_provider = new AlgoProvider(rules, studio_model, dynamic_helper, security_auxiliary_model);
attachable_algo_provider = new AttachableAlgoProvider(attachable_rules, studio_model, dynamic_helper, security_auxiliary_model);
} else {
var algo_type_data =
EnumHelper.get_values_except(AlgoType.Dynamic, AlgoType.Composite, AlgoType.None)
.Select(t => _get_type_data(t))
.Concat(dynamic_helper.dynamic_types.Select(dt => _get_type_data(AlgoType.Dynamic, dt)))
.ToListLazy();
var conf_sets = from_conf.rules.Select(r => _to_security_algo_set(r, dynamic_helper, algo_type_data)).ToList();
security_algo_provider = new AlgoProvider(conf_sets.Select(tup => tup.standard).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
attachable_algo_provider = new AttachableAlgoProvider(conf_sets.Select(tup => tup.attachable).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
}
}
[Export]
public IAlgoProvider security_algo_provider { get; }
[Export]
public IAttachableAlgoProvider attachable_algo_provider { get; }
static (string name, AlgoType factory, string dynamic) _get_type_data(AlgoType type, string dynamic_type = null) {
string type_name = type.ToString();
return (type_name, type, dynamic_type ?? type_name);
}
static SecurityCriteriaClause _to_clause(bts.config.AlgoTypeContext context) =>
context == null ?
new SecurityCriteriaClause() :
new SecurityCriteriaClause(
exchanges: context.exchanges?.Select(EnumHelper.parse<Venue>)?.ToArray(),
security_types: context.security_types?.Select(EnumHelper.parse<Security.ContractOneofCase>)?.Select(t => t.security_type())?.ToArray(),
symbols: context.symbols?.ToArray());
static (SecurityAlgoTypeSetImmutable standard, SecurityAlgoTypeSetImmutable attachable) _to_security_algo_set(
bts.config.AlgoTypeRule rule,
DynamicOrderHelper dynamic_helper,
IEnumerable<(string name, AlgoType factory, string dynamic)> algo_type_data) {
//
var factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
var dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
var attachable_factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
var attachable_dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
foreach (var a in rule.algos) {
if (Enum.TryParse(a.name, out AlgoType sys_algo) && sys_algo != AlgoType.Composite && sys_algo != AlgoType.Dynamic) {
on_sys_match(sys_algo, a.is_attachable);
}
else if (dynamic_helper.try_parse(a.name, out var dyn_type)) {
on_dyn_match(dyn_type, a.is_attachable);
} else {
foreach (var (name, factory, dynamic) in algo_type_data) {
if (Regex.Match(name, a.name).Success) {
switch (factory) {
case AlgoType.None:
case AlgoType.Composite:
// unexpected, but no need to crash
continue;
case AlgoType.Dynamic:
on_dyn_match(dynamic, a.is_attachable);
break;
default:
on_sys_match(factory, a.is_attachable);
break;
}
}
}
}
}
return (
// standard
new SecurityAlgoTypeSetImmutable(
_to_clause(rule.context),
new AlgoTypeSetImmutable(
factory_types.ToImmutable(),
dynamic_types.ToImmutable())),
// attachable
new SecurityAlgoTypeSetImmutable(
_to_clause(rule.context),
new AlgoTypeSetImmutable(
attachable_factory_types.ToImmutable(),
attachable_dynamic_types.ToImmutable())));
// local methods
void on_sys_match(AlgoType sys_algo, bool is_attachable) {
factory_types.Add(sys_algo);
if (is_attachable)
attachable_factory_types.Add(sys_algo);
}
void on_dyn_match(string dyn_algo, bool is_attachable) {
dynamic_types.Add(dyn_algo);
if (is_attachable)
attachable_dynamic_types.Add(dyn_algo);
}
}
}
}
@@ -0,0 +1,132 @@
using bts.api.core;
using bts.api.orderman;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
readonly struct AlgoTypeSetImmutable {
public static readonly AlgoTypeSetImmutable empty = new AlgoTypeSetImmutable(ImmutableHashSet<AlgoType>.Empty, ImmutableHashSet<string>.Empty);
public readonly ImmutableHashSet<AlgoType> factory_types;
public readonly ImmutableHashSet<string> dynamic_types;
public AlgoTypeSetImmutable(
ImmutableHashSet<AlgoType> factory_types,
ImmutableHashSet<string> dynamic_types) {
this.factory_types = factory_types;
this.dynamic_types = dynamic_types;
}
}
readonly struct SecurityAlgoTypeSetImmutable {
public readonly SecurityCriteriaClause security_criteria;
public readonly AlgoTypeSetImmutable algo_type_set;
public SecurityAlgoTypeSetImmutable(SecurityCriteriaClause security_criteria, AlgoTypeSetImmutable algo_type_set, CustomOrderDefinitionImmutable? default_algo = null) {
this.security_criteria = security_criteria;
this.algo_type_set = algo_type_set;
}
}
static class AlgoTypeSetExtensions {
public static string fmt_diagnostic(this IEnumerable<SecurityAlgoTypeSetImmutable> rules, string line_delim = "\n") {
var sb = new StringBuilder();
int rule_count = 1;
using (var en = rules.GetEnumerator()) {
if (en.MoveNext()) {
string line_delim_tab1 = line_delim + "\t";
string line_delim_tab2 = line_delim + "\t\t";
append_rule(en.Current);
while (en.MoveNext()) {
sb.AppendLine();
append_rule(en.Current);
}
//
void append_rule(SecurityAlgoTypeSetImmutable r) {
sb.Append(line_delim + $"**RULE {rule_count++}**");
sb.Append(line_delim_tab1);
sb.Append("Security Criteria: ");
sb.Append(r.security_criteria.is_defined ? string.Join("; ", r.security_criteria.fmt_diagnostic_msgs()) : "(Undefined)");
sb.Append(line_delim_tab1);
// Factory
sb.Append("Factory Types:");
sb.Append(line_delim_tab2);
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.factory_types));
sb.Append(line_delim_tab1);
// Dynamic
sb.Append("Dynamic Types:");
sb.Append(line_delim_tab2);
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.dynamic_types));
}
}
}
return sb.ToString();
}
public static AlgoTypeSetImmutable intersect_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
new AlgoTypeSetImmutable(a.factory_types.Intersect(b.factory_types), a.dynamic_types.Intersect(b.dynamic_types));
public static AlgoTypeSetImmutable union_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
new AlgoTypeSetImmutable(a.factory_types.Union(b.factory_types), a.dynamic_types.Union(b.dynamic_types));
public static IEnumerable<AlgoData> define_algos(
this AlgoTypeSetImmutable algo_set,
DynamicOrderHelper dynamic_helper,
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
uint display_qty_divisor = 1) =>
//
algo_set.factory_types
.Select(algo_type => {
var def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(algo_type);
if (fit_to_type != null)
def = fit_to_type(def);
// multiply quantity props by display qty divisor to get appropriate actual value
if (display_qty_divisor > 1) {
switch (def.CustomCase) {
case AlgoType.Iceberg:
var mdef = def.CloneToMutable();
mdef.Iceberg.ShowQuantity *= display_qty_divisor;
def = new CustomOrderDefinitionImmutable(mdef);
break;
}
}
return def;
})
.Concat(algo_set.dynamic_types.Select(dynamic_helper.create_custom_defi))
.Select(AlgoData.from_custom_def);
public static ImmutableDictionary<AlgoKey, AlgoData> match_algos(
this IEnumerable<SecurityAlgoTypeSetImmutable> security_algo_sets,
SecurityCriteriaClause clause,
DynamicOrderHelper dynamic_helper,
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
uint display_qty_divisor = 1) {
//
ImmutableDictionary<AlgoKey, AlgoData> rv = null;
foreach (var set in security_algo_sets) {
if (!clause.is_subset_of(set.security_criteria)) {
continue;
} else if (rv == null) {
rv = ImmutableDictionary.CreateRange(
set.algo_type_set.define_algos(dynamic_helper, fit_to_type, display_qty_divisor)
.Select(a => new KeyValuePair<AlgoKey, AlgoData>(a.key, a)));
} else {
foreach (var kv in rv) {
if (kv.Value.value.CustomCase == AlgoType.Dynamic) {
if (!set.algo_type_set.dynamic_types.Contains(kv.Value.value.Dynamic.DynamicOrderType))
rv = rv.Remove(kv.Key);
} else if (!set.algo_type_set.factory_types.Contains(kv.Value.value.CustomCase))
rv = rv.Remove(kv.Key);
}
}
}
return rv ?? ImmutableDictionary<AlgoKey, AlgoData>.Empty;
}
}
}
@@ -0,0 +1,234 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
interface IAlgoProvider {
AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security);
AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause);
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos();
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security);
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause clause);
}
// This interface is used to distinguish the attachable algo provider
// from the standard one wrt MEF export/import.
interface IAttachableAlgoProvider : IAlgoProvider { }
class AlgoProvider : IAlgoProvider {
readonly IEnumerable<SecurityAlgoTypeSetImmutable> _security_algo_types;
readonly IAlgoStudioModel _studio_model;
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
readonly IObservable<TableUpdate<AlgoData, AlgoKey>> _studio_algos_source;
readonly DynamicOrderHelper _dynamic_helper;
readonly AlgoTypeSetImmutable _full_set;
public AlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
_security_algo_types = algo_sets;
_studio_model = studio_model;
_dynamic_helper = dynamic_helper;
_full_set = new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(
EnumHelper.get_values_except(CustomOrderDefinition.CustomOneofCase.None, CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite)),
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
_security_auxiliary_model = security_auxiliary_model;
/// initialize <see cref="_studio_algos_source"/> with an empty table, so the
/// <see cref="UpdateExtensions.AppendTable{T, TKey}(IObservable{TableUpdate{T, TKey}}, IEnumerable{T})"/>
/// calls made by the 'subscribe_algos' methods will fire at least once, even if the underlying
/// studio algos stream is empty have been created (this would be the case if the user had not yet created any studio algos).
/// <see cref="subscribe_algos"/>,
/// <see cref="subscribe_algos(SecurityCriteriaClause)"/>,
/// <see cref="subscribe_algos(SecurityImmutable)"/>
_studio_algos_source = studio_model.definitions
.StartWith(TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn))
.Cache();
}
public AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security) =>
_security_algo_types.Where(p => p.security_criteria.includes(security))
.Select(sec_algos => sec_algos.algo_type_set)
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
public AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause) =>
_security_algo_types.Where(p => p.security_criteria.is_subset_of(clause))
.Select(sec_algos => sec_algos.algo_type_set)
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos() =>
_studio_algos_source
.AppendTable(_security_algo_types
.Select(s => s.algo_type_set)
.Aggregate(AlgoTypeSetImmutable.empty, (a, b) => a.union_with(b))
.define_algos(_dynamic_helper));
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause security_clause) {
var system_algos = _security_algo_types.match_algos(
security_clause,
_dynamic_helper,
def => _fit_to_type(def, security_clause, _security_algo_types));
return _studio_algos_source.Select(
a => (algo: a, fitted_def: _fit_to_type(a.value, security_clause, _security_algo_types)),
tup => tup.algo.key)
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), a => a.key)
.AppendTable(system_algos.Values);
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security) {
var clause = new SecurityCriteriaClause(
exchanges: new[] { security.Exchange },
security_types: new[] { security.security_type() });
// i.e., non-studio algos
var system_algos = _security_algo_types.match_algos(
clause,
_dynamic_helper,
def => _fit_to_type(def, security, _security_algo_types),
_security_auxiliary_model.get_display_quantity_divisor(security));
return _studio_algos_source.Select(
a => (algo: a, fitted_def: _fit_to_type(a.value, security, _security_algo_types)),
tup => tup.algo.key)
// filter out algos that use an unsupported root, because this would have the effect of duplicating the default algo
// (e.g., GlbxIceberg orders would be converted to Limit/Spreader orders for non-Glbx securities).
.Filter(tup => tup.fitted_def.actual().CustomCase == tup.algo.value.actual().CustomCase, tup => tup.algo.key)
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), AlgoData.key_fn)
.AppendTable(system_algos.Values);
}
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) {
if (security_clause.security_types.empty_if_null().Contains(Security.ContractOneofCase.Synthetic.security_type())) {
mdef.Spreader = new SpreaderOrderDefinition();
} else {
mdef.Limit = new LimitOrderDefinition();
}
}
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) {
if (security.ContractCase == Security.ContractOneofCase.Synthetic) {
mdef.Spreader = new SpreaderOrderDefinition {
Security = security.CloneToMutable()
};
} else {
mdef.Limit = new LimitOrderDefinition();
}
}
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
var mroot = defi.CloneToMutable();
if (_fit_to_type(mroot, security_clause, algo_sets)) {
defi = new CustomOrderDefinitionImmutable(mroot);
}
return defi;
}
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
var mroot = defi.CloneToMutable();
if (_fit_to_type(mroot, security, algo_sets)) {
defi = new CustomOrderDefinitionImmutable(mroot);
}
return defi;
}
bool _fit_to_type(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
mdef = mdef.Composite.Definition;
var sec_sets = algo_sets.Where(s => security_clause.is_subset_of(s.security_criteria));
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
if (!algo_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
assign_fallback_algo(mdef, security_clause);
return true;
}
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
assign_fallback_algo(mdef, security_clause);
return true;
}
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security_clause, algo_sets)) > 0;
}
bool _fit_to_type(CustomOrderDefinition mdef, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
mdef = mdef.Composite.Definition;
var sec_sets = algo_sets.Where(s => s.security_criteria.includes(security));
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
if (!sec_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
assign_fallback_algo(mdef, security);
return true;
}
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
assign_fallback_algo(mdef, security);
return true;
}
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security, algo_sets)) > 0;
}
}
class AttachableAlgoProvider : AlgoProvider, IAttachableAlgoProvider {
public AttachableAlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model)
: base(algo_sets, studio_model, dynamic_helper, security_auxiliary_model) { }
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) =>
mdef.Limit = new LimitOrderDefinition();
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) =>
mdef.Limit = new LimitOrderDefinition();
}
static class AlgoProviderExtensions {
// This stream filters out composite algos that are not subsets of the non-composite algo types in the table
// (normally, such composites are 'auto-fit,' meaning the algo nodes that are not part of the set are replaced with ones that are).
// For example, consider a composite algo the following structure: Offset -> GlbxIceberg.
// If an update arrives with these noncomposite types: "Iceberg, Limit, Market," that composite algo is not a subset of the non-composite algo types,
// because GlbxIceberg is not in the table, so it would only be added to the table if/when GlbxIceberg arrives.
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(this IAlgoProvider algo_provider, bool allow_undefined_child = false) =>
subscribe_complete_algos(algo_provider.subscribe_algos(), allow_undefined_child);
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(IObservable<TableUpdate<AlgoData, AlgoKey>> source, bool allow_undefined_child = false) =>
Observable.Create<TableUpdate<AlgoData, AlgoKey>>(o => {
var last_out = TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn);
var incomplete_composites = ImmutableHashSet<AlgoData>.Empty;
return source.Subscribe(table => {
var bd = last_out.to_builder();
foreach (var algo in incomplete_composites.Where(a => _validate_algo_structure(a.value, table.state, allow_undefined_child))) {
incomplete_composites = incomplete_composites.Remove(algo);
bd.add_or_update(algo);
}
foreach (var a in table.removed.Concat(table.changes.Old()))
bd.remove(a.key);
foreach (var a in table.added.Concat(table.changed)) {
if (_validate_algo_structure(a.value, table.state, allow_undefined_child)) {
bd.add_or_update(a);
} else {
incomplete_composites = incomplete_composites.Add(a);
}
}
o.OnNext(last_out = bd.to_table());
});
});
static bool _validate_algo_structure(CustomOrderDefinitionImmutable custom_def, IReadOnlyDictionary<AlgoKey, AlgoData> allowed_algo_parts, bool allow_undefined_child) =>
OrderDataHelper.enumerate_algo_structure(custom_def)
.Select((algo_node, index) =>
// None is an acceptable algo type as long as it's not the root algo (i.e., index > 0)
(index > 0 && allow_undefined_child && algo_node.type == CustomOrderDefinition.CustomOneofCase.None) || _is_part_contained(algo_node, allowed_algo_parts))
.All();
static bool _is_part_contained((CustomOrderDefinition.CustomOneofCase type, string dyn, uint studio_id, uint spreader_sec) structure, IReadOnlyDictionary<AlgoKey, AlgoData> state) =>
structure.type == CustomOrderDefinition.CustomOneofCase.Composite ||
structure.type == CustomOrderDefinition.CustomOneofCase.Dynamic ?
state.ContainsKey(AlgoKey.from_dynamic_type(structure.dyn)) :
state.ContainsKey(AlgoKey.from_known_type(structure.type));
}
}
+10 -1
View File
@@ -115,7 +115,7 @@ namespace mbts.ui.shell
{
try
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
string path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
if (File.Exists(path + "config\\stunnel.conf"))
{
uint job_error = BTSUINativeMethods.setup_process_job_object();
@@ -124,6 +124,15 @@ namespace mbts.ui.shell
kill_stunnel();
start_stunnel(path, job_error);
}
path = AppDomain.CurrentDomain.BaseDirectory + "\\..\\stunnel\\";
if (File.Exists(path + "config\\stunnel.conf"))
{
uint job_error = BTSUINativeMethods.setup_process_job_object();
//if we can't put this process and stunnel into a job, try to stop stunnel then restart
if (job_error != 0)
kill_stunnel();
start_stunnel(path, job_error);
}
}
catch(Exception ex)
{
+2
View File
@@ -27,6 +27,7 @@ namespace mbts.ui {
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
CurrencyExchangeGeometryKey = nameof(CurrencyExchangeGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
@@ -161,6 +162,7 @@ namespace mbts.ui {
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
public static ImageSource CopyBoxes => create_icon(CopyBoxesGeometryKey, null, _copy_boxes_pen);
public static ImageSource CurrencyExchange => create_icon(CurrencyExchangeGeometryKey);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
@@ -62,6 +62,10 @@
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.CurrencyExchangeGeometryKey}">
M0,24L15.324005,24C16.042007,24.785004,16.875,25.460999,17.800003,26L2,26 2,30 23.087997,30 23.087006,27.424988C23.771011,27.423004,24.439011,27.352997,25.085999,27.225983L25.085999,30C25.085999,31.10199,24.189011,32,23.085999,32L2,32C0.89700317,32,0,31.10199,0,30z M22.484015,13.026998L23.475018,13.026998 23.475018,13.856997C24.151018,13.885996,24.605021,14.016996,24.93902,14.167996L24.646021,15.233995C24.38302,15.129995 23.919018,14.912995 23.191017,14.912995 22.535015,14.912995 22.323015,15.176995 22.323015,15.440995 22.323015,15.751994 22.676016,15.950994 23.535017,16.251994 24.73702,16.647994 25.222021,17.166993 25.222021,18.015992 25.222021,18.854991 24.58502,19.571991 23.424017,19.759991L23.424017,20.72199 22.434015,20.72199 22.434015,19.825991C21.757013,19.797991,21.101011,19.627991,20.71701,19.420991L21.019012,18.317992C21.444014,18.533992 22.040014,18.731992 22.697016,18.731992 23.272017,18.731992 23.666018,18.524992 23.666018,18.147992 23.666018,17.788992 23.343018,17.562993 22.596016,17.326993 21.515013,16.987993 20.777012,16.516994 20.777012,15.601995 20.777012,14.771996 21.404013,14.119996 22.484015,13.922997z M0.065009117,12.384011L13.628021,12.384011C13.320021,13.018011,13.075021,13.687011,12.89902,14.384011L2.065011,14.384011 2.065011,18.384011 12.68002,18.384011C12.77602,19.075011,12.93702,19.743011,13.16102,20.384011L0.065009117,20.384011z M23.060013,10.460008C19.480015,10.460008 16.568016,13.368006 16.568016,16.943003 16.568016,20.519001 19.480015,23.426999 23.060013,23.427 26.640011,23.426999 29.55201,20.519001 29.55201,16.943003 29.55201,13.368006 26.640011,10.460008 23.060013,10.460008z M23.060013,8.4600086C27.74201,8.4600086 31.552008,12.266006 31.552008,16.943003 31.552008,21.621 27.74201,25.426998 23.060013,25.426998 18.378014,25.426998 14.568016,21.621 14.568016,16.943003 14.568016,12.266006 18.378014,8.4600086 23.060013,8.4600086z M2.0000181,2L2.0000181,6 23.08602,6 23.08602,2z M2.0000181,0L23.08602,0C24.18902,0,25.08602,0.89699936,25.08602,2L25.08602,6.6599998C24.430019,6.5310001 23.753019,6.4599991 23.060019,6.4599991 21.06402,6.4599991 19.20402,7.0300007 17.614019,8L1.8119812E-05,8 1.8119812E-05,2C1.8066406E-05,0.89699936,0.89701813,0,2.0000181,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
</sys:String>
@@ -276,6 +280,7 @@
m464.867188 0c-25.988282 0-47.132813 21.144531-47.132813 47.132812v313.75c0 49.324219 11.660156 98.710938 33.71875 142.824219 2.539063 5.082031 7.734375 8.292969 13.414063 8.292969 5.683593 0 10.875-3.210938 13.417968-8.292969 22.054688-44.113281 33.714844-93.503906 33.714844-142.824219v-313.75c0-25.988281-21.144531-47.132812-47.132812-47.132812zm0 30c9.445312 0 17.132812 7.6875 17.132812 17.132812v33.199219h-34.265625v-33.199219c0-9.445312 7.683594-17.132812 17.132813-17.132812zm17.132812 323.46875h-34.265625v-243.136719h34.265625zm-17.132812 105.4375c-8.71875-24.277344-14.1875-49.734375-16.222657-75.4375h32.445313c-2.035156 25.707031-7.507813 51.160156-16.222656 75.4375zm0 0
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CheckMarkGeometryKey}">
F1M9.97498,1.22334L4.6983,9.09834 4.52164,9.09834 0,5.19331 1.27664,3.52165 4.255,6.08833 8.33331,1.52588E-05 9.97498,1.22334z
</sys:String>
@@ -17,7 +17,7 @@ namespace bts.shell {
class MarketShellModule : IShellModule {
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub, orders_model.security_auxiliary_model);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -1,10 +1,12 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.co2;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -24,7 +26,6 @@ namespace mbts.spark {
readonly AnimationManager _animation = new AnimationManager();
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
readonly string _security_text, _security_key_text;
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
readonly Property<bool> _last_price_from_spread;
readonly CellProperty<int> _position;
@@ -33,48 +34,51 @@ namespace mbts.spark {
readonly CellProperty<bool> _inside_bids, _inside_asks;
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
readonly CellProperty<double> _working_best_bid, _working_best_ask;
readonly CellProperty<string> _working_bids_text, _working_asks_text;
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
PropertyAutoWrapper<uint, uint>
_bid_direct_size,
_bid_implied_size,
_bid_merged_size,
_ask_direct_size,
_ask_implied_size,
_ask_merged_size;
double _raw_day_position;
string _bid_merged_size_text, _ask_merged_size_text;
ulong _bbo_ref, _last_ref;
uint? _display_qty_divisor_backing;
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
_bus = bus;
_orders_model = orders_model;
_security_text = smd.security.ToString();
_security_key_text = security_key.ToString();
string format_price(double price) => security.ContractSpec.format(price);
_position = new CellProperty<int>(this, nameof(position), 0);
_position = new CellProperty<int>(this, nameof(position), fmt_display_qty_int);
_bid_price = backing(nameof(bid_price), double.NaN);
_ask_price = backing(nameof(ask_price), double.NaN);
_last_price = backing(nameof(last_price), double.NaN);
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
_last_size = new CellProperty<uint>(this, nameof(last_size), fmt_display_qty_uint);
_day_change = backing(nameof(day_change), double.NaN);
_session_low = backing(nameof(session_low), double.NaN);
_session_high = backing(nameof(session_high), double.NaN);
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
_settlement = backing(nameof(settlement), double.NaN);
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
_volume = new CellProperty<ulong>(this, nameof(volume), fmt_display_qty_ulong, 0);
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
//
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
_bid_direct_size = backing(nameof(bid_direct_size), 0u, convert_display_qty_uint);
_bid_implied_size = backing(nameof(bid_implied_size), 0u, convert_display_qty_uint);
_bid_merged_size = backing(nameof(bid_merged_size), 0u, convert_display_qty_uint);
_ask_direct_size = backing(nameof(ask_direct_size), 0u, convert_display_qty_uint);
_ask_implied_size = backing(nameof(ask_implied_size), 0u, convert_display_qty_uint);
_ask_merged_size = backing(nameof(ask_merged_size), 0u, convert_display_qty_uint);
//
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
//
default_command = new DelegateCommand(_show_ladder);
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
@@ -90,17 +94,45 @@ namespace mbts.spark {
_animation.AnimationsChanged += on_animations_changed;
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
_dispose = Disposable.Create(
attach_prop_changed_cb(_bid_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_direct_size_text))),
attach_prop_changed_cb(_bid_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_implied_size_text))),
attach_prop_changed_cb(_bid_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_merged_size_text))),
attach_prop_changed_cb(_ask_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_direct_size_text))),
attach_prop_changed_cb(_ask_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_implied_size_text))),
attach_prop_changed_cb(_ask_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_merged_size_text))),
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position))) {
RaisePropertyChanged(nameof(raw_day_position_text));
raise_invalidated();
}
}),
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
);
uint convert_display_qty_uint(uint value) =>
value.safe_divide(_display_qty_divisor);
string fmt_display_qty_int(int value) =>
value.safe_divide(_display_qty_divisor).ToString();
string fmt_display_qty_uint(uint value) =>
value.safe_divide(_display_qty_divisor).ToString();
string fmt_display_qty_ulong(ulong value) =>
value.safe_divide(_display_qty_divisor).ToString();
PropertyChange<T> mk_echo_prop_changed_cb<T>(params string[] props) =>
(oldval, newval) => RaisePropertyChanged(props);
IDisposable attach_prop_changed_cb<TView, TModel>(PropertyAutoWrapper<TModel, TView> prop, PropertyChange<(TModel, TView)> callback) {
prop.changed += callback;
return Disposable.Create(() => prop.changed -= callback);
}
}
public event Action invalidated;
@@ -109,32 +141,63 @@ namespace mbts.spark {
public bool inside_bids => _inside_bids.value;
public bool inside_asks => _inside_asks.value;
public int position => _position.value;
public string position_text => _position.text;
public double raw_day_position => _raw_day_position;
public string raw_day_position_text => SparkUtils.fmt_position(_raw_day_position.safe_divide(_display_qty_divisor));
public double last_price => _last_price.Value;
public bool last_price_from_spread => _last_price_from_spread.Value;
public uint last_size => _last_size.value;
public string last_size_text => _last_size.text;
// bid price/size
public double bid_price => _bid_price.Value;
public uint bid_direct_size => _bid_direct_size.Value;
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
public uint bid_merged_size => _bid_merged_size.Value;
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
public uint bid_direct_size => _bid_direct_size.model;
public uint bid_implied_size => _bid_merged_size.model - _bid_direct_size.model;
public uint bid_merged_size => _bid_merged_size.model;
// bid size text
public string bid_direct_size_text => _bid_direct_size.view.ToString();
public string bid_implied_size_text => _bid_implied_size.view.ToString();
public string bid_merged_size_text => _bid_merged_size.view.ToString();
// ask price/size
public double ask_price => _ask_price.Value;
public uint ask_direct_size => _ask_direct_size.Value;
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
public uint ask_merged_size => _ask_merged_size.Value;
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
public uint ask_direct_size => _ask_direct_size.model;
public uint ask_implied_size => _ask_implied_size.model;
public uint ask_merged_size => _ask_merged_size.model;
// ask size text
public string ask_direct_size_text => _ask_direct_size.view.ToString();
public string ask_implied_size_text => _ask_implied_size.view.ToString();
public string ask_merged_size_text => _ask_merged_size.view.ToString();
//
public ulong volume => _volume.value;
public string volume_text => _volume.text;
public double settlement => _settlement.Value;
public double day_change => _day_change.Value;
public double session_low => _session_low.Value;
public double session_high => _session_high.Value;
public int working_bid_volume => _working_bid_volume.value;
public int working_ask_volume => _working_ask_volume.value;
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
public string working_bids_text => OrderUtils.order_collection_summary(
_order_summarizer.best_working_bid,
_order_summarizer.best_working_bid_qty.safe_divide(_display_qty_divisor),
_order_summarizer.working_bid_qty.safe_divide(_display_qty_divisor),
security.ContractSpec);
public string working_asks_text => OrderUtils.order_collection_summary(
_order_summarizer.best_working_ask,
_order_summarizer.best_working_ask_qty.safe_divide(_display_qty_divisor),
_order_summarizer.working_ask_qty.safe_divide(_display_qty_divisor),
security.ContractSpec);
public double working_best_bid => _working_best_bid.value;
public double working_best_ask => _working_best_ask.value;
// ice props
public string market_description => security.market_description();
public string cleared_alias => security.cleared_alias();
public string product_description => security.product_name();
public string hub_alias => security.hub_alias();
public string strip_name => security.strip_name();
public string mic_code => security.mic_code();
// animations
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
// commands
public ICommand bid_price_click { get; }
public ICommand bid_merged_size_click { get; }
public ICommand bid_direct_size_click { get; }
@@ -146,27 +209,30 @@ namespace mbts.spark {
public ICommand ask_implied_size_click { get; }
public ICommand working_asks_click { get; }
public ICommand default_command { get; }
public string market_description {get;}
public string cleared_alias { get; }
public string product_description { get; }
public string hub_alias { get; }
public string strip_name{ get; }
public string mic_code { get; }
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
#endregion
uint _display_qty_divisor => _display_qty_divisor_backing ??
(_display_qty_divisor_backing = _orders_model.security_auxiliary_model.get_display_quantity_divisor(security)).Value;
public void raise_invalidated() => invalidated?.Invoke();
public void refresh() {
bool dirty = false;
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
_bid_price.set(b.price);
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
// don't use the '||' operator - every 'set_new' call must be made
// (the operation mustn't terminate the first time a set_new call returns true).
dirty |= (
_bid_direct_size.set_new(b.direct_size) |
_bid_implied_size.set_new(b.implied_size) |
_bid_merged_size.set_new(b.size) |
_ask_direct_size.set_new(a.direct_size) |
_ask_implied_size.set_new(a.implied_size) |
_ask_merged_size.set_new(a.size));
_ask_price.set(a.price);
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
}
@@ -183,16 +249,6 @@ namespace mbts.spark {
_volume.on_next_distinct(md.md.total_volume, ref dirty);
if (dirty)
invalidated?.Invoke();
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
merged.set(nps.size);
direct.set(nps.direct_size);
text = null;
dirty = true;
RaisePropertyChanged(text_prop);
}
}
}
public bool Equals(Row other) =>
@@ -220,7 +276,7 @@ namespace mbts.spark {
ob_settings.filters = new bts.shell.orderbook.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
security_criteria = SecurityCriteria.from_securities(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
@@ -9,6 +9,7 @@ using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Data;
using System.Windows.Media;
@@ -18,6 +19,9 @@ using static mbts.ui.spark.MarketViewView;
namespace mbts.spark {
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
static IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>();
readonly IBus _bus;
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
@@ -37,21 +41,21 @@ namespace mbts.spark {
const double working_orders_col_width = 105;
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.Volume, "Volume", r => r.volume_text, nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size_text, nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position_text, nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => r.raw_day_position_text, nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size_text, nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size_text, nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size_text, nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size_text, nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
@@ -151,14 +155,16 @@ namespace mbts.spark {
working_ask_volume_filter.load_state(filters.working_ask_volume);
volume_filter.load_state(filters.volume);
security_key_filter.load_state(filters.security_key);
market_description_filter.load_state(filters.market_description);
cleared_alias_filter.load_state(filters.cleared_alias);
product_description_filter.load_state(filters.product_description);
hub_alias_filter.load_state(filters.hub_alias);
strip_name_filter.load_state(filters.strip_name);
mic_code_filter.load_state(filters.mic_code);
}
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
}
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
}), DataGridFilterHelper.hook_filters(rows_view, _filters),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
);
@@ -233,13 +239,15 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.market_description, nameof(Row.market_description));
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.cleared_alias, nameof(Row.cleared_alias));
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.product_description, nameof(Row.product_description));
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.hub_alias, nameof(Row.hub_alias));
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.strip_name, nameof(Row.strip_name));
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.mic_code, nameof(Row.mic_code));
IEnumerable<IDataGridColumnFilter<Row>> _filters =>
_filter_getters.Select(fn => fn(this));
public FilterStates get_filter_states() {
return new FilterStates {
@@ -66,7 +66,7 @@ namespace mbts.spark {
if (_sorted_selected_security_criteria_backing != null) {
_selected_security_criteria_backing = _sorted_selected_security_criteria_backing;
} else if (_vm != null)
_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Select(r => r.security));
_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Select(r => r.security));
}
return _selected_security_criteria_backing;
}
@@ -92,7 +92,7 @@ namespace mbts.spark {
}
}
var comp = comp_builder.to_comparer();
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
_sorted_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Ordered(comp).Select(r => r.security));
}
}
return _sorted_selected_security_criteria_backing;
@@ -23,12 +23,15 @@ namespace bts.shell.time_and_sales {
.to_safe();
readonly TradeMessageImmutable _trade;
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
uint? _display_size;
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade) {
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
this.row_key = row_key;
this.security = security;
_trade = trade;
trade_key = trade.Key;
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
}
public event Action invalidated;
@@ -40,6 +43,8 @@ namespace bts.shell.time_and_sales {
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
public double price => _trade.price();
public uint size => _trade.Size;
public uint display_size => _display_size ??
(_display_size = size.safe_divide(_get_display_qty_divisor(security))).Value;
public AggressorSide aggressor_side => _trade.AggressorSide;
public void raise_invalidated() =>
@@ -40,6 +40,7 @@ namespace bts.shell.time_and_sales {
_refresh_filter,
_refresh_sort,
_refresh_selected_rows;
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
IComparer<TimeAndSalesRowVM> _comparer_backing;
Func<TimeAndSalesRowVM, bool> _filter_backing;
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
@@ -58,8 +59,9 @@ namespace bts.shell.time_and_sales {
_filter_change_count,
_sort_change_count;
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager) {
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
_settings_manager = settings_manager;
@@ -95,7 +97,7 @@ namespace bts.shell.time_and_sales {
System.Windows.HorizontalAlignment.Right);
add_column(
TimeAndSalesView.Columns.Size,
r => r.size.ToString(),
r => r.display_size.ToString(),
DefaultColumnHeaders.Size,
DefaultColumnWidths.Size,
size_filter,
@@ -304,7 +306,7 @@ namespace bts.shell.time_and_sales {
}
var all_rows_bd = _all_rows.ToBuilder();
foreach (var t in trades) {
var r = _row_factory.create(t.security, t.trade);
var r = _row_factory.create(t.security, t.trade, _get_display_qty_divisor);
all_rows_bd.Add(r);
if (_filter_backing == null || _filter_backing(r))
rows.InsertSorted(r, _comparer);
@@ -400,11 +402,11 @@ namespace bts.shell.time_and_sales {
// the row key must be unique to each row in the collection
int _row_id_counter;
public TimeAndSalesRowVM create(TradeInfo trade_info) =>
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade);
public TimeAndSalesRowVM create(TradeInfo trade_info, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade, get_display_qty_divisor);
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade) =>
new TimeAndSalesRowVM(_row_id_counter++, security, trade);
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
new TimeAndSalesRowVM(_row_id_counter++, security, trade, get_display_qty_divisor);
}
}
}
@@ -27,6 +27,7 @@ namespace bts.shell.time_and_sales {
readonly TimeAndSalesWindowFactory _factory;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
readonly bool _can_pause_updates;
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
TimeAndSalesVM _vm;
TimeAndSalesModel _model;
SecurityCriteria _security_criteria_backing;
@@ -36,12 +37,21 @@ namespace bts.shell.time_and_sales {
SparkMenuDisplayVM _menu_display_vm;
IDisposable _subscriptions;
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, TimeAndSalesSettings state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager, bool can_pause_updates)
public TimeAndSalesWindow(
TimeAndSalesWindowFactory factory,
IBus bus,
MDConsumer consumer,
TimeAndSalesSettings state,
ImageSource icon,
IWindowSettingsManager<GridSettings> settings_manager,
bool can_pause_updates,
Func<SecurityImmutable?, uint> get_display_qty_divisor)
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
Log.assert(state != null);
_consumer = consumer;
_factory = factory;
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
if (_state.security_criteria == null)
_state = _state.with(new SecurityCriteria());
_security_criteria_backing = _state.security_criteria;
@@ -87,7 +97,7 @@ namespace bts.shell.time_and_sales {
_settings_manager.instance_settings = _state.grid;
_menu_display_vm = new SparkMenuDisplayVM();
_menu_display_vm.load_from(_state.menu_display);
_vm = new TimeAndSalesVM(_settings_manager);
_vm = new TimeAndSalesVM(_settings_manager, _get_display_qty_divisor);
var selected_rows_src = Observable.Create<ImmutableHashSet<TimeAndSalesRowVM>>(o => {
_vm.selected_rows_changed += o.OnNext;
return Disposable.Create(() => _vm.selected_rows_changed -= o.OnNext);
@@ -128,7 +138,7 @@ namespace bts.shell.time_and_sales {
.complete(selected_rows_src, always_true, get_fills_startup_data),
// mview
SparkCommands.ShowMarketView
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria),
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria),
// pause
toggle_is_paused_cmd,
// change securities
@@ -173,7 +183,7 @@ namespace bts.shell.time_and_sales {
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
new FillsSettings(new FillsFilterStates {
security = rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria
security = rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria
});
string get_is_paused_cmd_title() =>
@@ -20,12 +20,15 @@ namespace bts.shell {
readonly MDConsumer _consumer;
readonly UserSettingsHub _settings_hub;
readonly TimeAndSalesConfig _time_and_sales_conf;
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, ISecurityAuxiliaryDataModel security_auxiliary_model)
: base(module, "TimeAndSales") {
_bus = bus;
_consumer = consumer;
_settings_hub = settings_hub;
_security_auxiliary_model = security_auxiliary_model;
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
@@ -38,7 +41,8 @@ namespace bts.shell {
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g)),
_time_and_sales_conf.can_pause_updates));
_time_and_sales_conf.can_pause_updates,
_security_auxiliary_model.get_display_quantity_divisor));
public async void show(SecurityCriteria criteria) {
var w = await load(new TimeAndSalesSettings(criteria));
@@ -549,24 +549,48 @@
</u:BindingUpdateGroupCollection>
</u:Behavior.BindingUpdateGroups>
</TextBox>
<TextBlock
<StackPanel
x:Name="PART_TickFactors_Label"
Grid.Column="2"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks"
IsEnabled="True"
Orientation="Horizontal"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:TickFactorsPicker
>
<TextBlock
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks ("
/>
<CheckBox
Margin="1,0,0,0"
IsChecked="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Padding="0"
>
fractional
</CheckBox>
<TextBlock VerticalAlignment="Center" Margin="1,0,0,0">)</TextBlock>
</StackPanel>
<Grid
x:Name="PART_TickFactors_Picker"
Grid.Column="3"
Margin="{StaticResource Margin.LHeaderPanelItem}"
HorizontalAlignment="Stretch"
IsEnabled="True"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
>
<c:TickFactorsPicker
HorizontalAlignment="Stretch"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:BooleanToVisibilityConverter.HiddenInstance}}"
/>
<c:TickUpDownInt
HorizontalAlignment="Stretch"
Minimum="1"
Value="{Binding IntegerTickSize, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource FalseVisibilityConverter}}"
/>
</Grid>
<TextBlock
x:Name="PART_Pricing_Label"
Grid.Column="4"
@@ -52,6 +52,26 @@ namespace mbts.ui.spark {
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
#endregion
#region UseFractionalTicks
public static readonly DependencyProperty UseFractionalTicksProperty = WPFHelper.create_dp<bool, ExecutionStrategyEditor>(
nameof(UseFractionalTicks), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, true);
public bool UseFractionalTicks {
get => (bool)GetValue(UseFractionalTicksProperty);
set => SetValue(UseFractionalTicksProperty, value);
}
#endregion
#region IntegerTickSize
public static readonly DependencyProperty IntegerTickSizeProperty = WPFHelper.create_dp<int, ExecutionStrategyEditor>(
nameof(IntegerTickSize), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 1);
public int IntegerTickSize {
get => (int)GetValue(IntegerTickSizeProperty);
set => SetValue(IntegerTickSizeProperty, value);
}
#endregion
#region IsNew
public static readonly DependencyProperty IsNewProperty =
DependencyProperty.Register(
@@ -46,7 +46,7 @@ namespace bts.shell {
async void _show_synthetic_create(SecurityCriteria security_criteria) {
var found_securities = await security_criteria.securities(_bus);
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Count > 0);
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Length > 0);
if (!respect_clause_security_ordering_is_possible) {
_show_synthetic_create(found_securities.Values.ToList());
} else {
@@ -114,6 +114,8 @@ namespace bts.shell {
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
public static QuoteTimeBasedThrottle QuoteTimeBasedThrottle => new QuoteTimeBasedThrottle { DelayToImprove = 0, DelayToRun = 0 };
#endregion
#region PreHedge
@@ -36,6 +36,8 @@
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
UseFractionalTicks="{Binding use_fractional_ticks}"
IntegerTickSize="{Binding integer_tick_size}"
/>
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
</Grid>
@@ -79,7 +79,7 @@ namespace mbts.spark {
), SparkCommands.ShowOrders.complete(() => {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
security_criteria = SecurityCriteria.from_securities(_vm.value.row_vms.Select(r => r.security))
};
return ob_settings;
}), SparkCommands.window_settings_command(new DialogCommand(() => {
@@ -10,13 +10,13 @@ namespace mbts.spark {
public override string man_page { get; } = string.Empty;
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.EnableHedging;
leg_tup.leg_params.SendHedge is null || leg_tup.leg_params.SendHedge.Val;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
leg_tup.leg_params.EnableHedging = value;
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
!leg_tups.Any(lt => lt.leg_params.EnableHedging);
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (leg_tup.leg_params.SendHedge is null)
leg_tup.leg_params.SendHedge = new bts.spark.NullableBoolean();
leg_tup.leg_params.SendHedge.Val = value;
}
}
}
}
}
@@ -0,0 +1,21 @@
using mbts.ui.spark;
using System;
namespace mbts.spark {
partial class SyntheticEditorVM {
class LeanOffsetTicksModel : RowModelBase<int> {
public override string display_name { get; } = "Lean Offset Ticks";
public override string help_text { get; } = "Improvement (in ticks) to the lean market";
public override string man_page { get; } = string.Empty;
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams?.LeanOffsetTicks ?? 0;
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
if (leg_tup.leg_params.QuoteParams is null)
leg_tup.leg_params.QuoteParams = new bts.spark.QuoteParams();
leg_tup.leg_params.QuoteParams.LeanOffsetTicks = value;
}
}
}
}
@@ -0,0 +1,40 @@
using bts.shell;
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteTimeThrottleDelayToImproveModel : RowModelBase<uint> {
readonly IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
public override string display_name { get; } = "Delay to Improve (ms)";
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.TimeThrottle };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_throttle(leg_tup).DelayToImprove = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle ?
throttle.DelayToImprove :
_value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.id] = value;
if (_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle)
throttle.DelayToImprove = value;
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_throttle(leg_tup) != null;
QuoteTimeBasedThrottle _get_throttle(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.TimeBasedThrottle;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteTimeBasedThrottle.DelayToImprove);
}
}
}
@@ -0,0 +1,40 @@
using bts.shell;
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteTimeThrottleDelayToRunModel : RowModelBase<uint> {
readonly IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
public override string display_name { get; } = "Delay To Run (ms)";
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.TimeThrottle };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_throttle(leg_tup).DelayToRun = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle ?
throttle.DelayToRun :
_value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.id] = value;
if (_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle)
throttle.DelayToRun = value;
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_throttle(leg_tup) != null;
QuoteTimeBasedThrottle _get_throttle(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.TimeBasedThrottle;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteTimeBasedThrottle.DelayToRun);
}
}
}
@@ -0,0 +1,38 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark {
partial class SyntheticEditorVM {
class QuoteTimeThrottleEnabledModel : RowModelBase<bool> {
readonly QuoteTimeThrottleDelayToRunModel _delay_to_run;
readonly QuoteTimeThrottleDelayToImproveModel _delay_to_improve;
public QuoteTimeThrottleEnabledModel(QuoteTimeThrottleDelayToRunModel delay_to_run, QuoteTimeThrottleDelayToImproveModel delay_to_improve) {
_delay_to_run = delay_to_run;
_delay_to_improve = delay_to_improve;
}
public override string display_name { get; } = string.Empty;
public override string help_text { get; } = string.Empty;
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.TimeThrottle };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.TimeBasedThrottle != null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.QuoteParams.TimeBasedThrottle == null) {
leg_tup.leg_params.QuoteParams.TimeBasedThrottle = SyntheticDataHelper.Defaults.QuoteTimeBasedThrottle;
_delay_to_run.apply_value(leg_tup);
_delay_to_improve.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.QuoteParams.TimeBasedThrottle = null;
}
_delay_to_run.set_enabled(leg_tup, value);
_delay_to_improve.set_enabled(leg_tup, value);
}
}
}
}
@@ -108,6 +108,10 @@ namespace mbts.spark {
"Reduces re-quoting updates by defining a region on either side " +
"of the working quoting order, inside which quote updates will not be sent.");
public static ParameterSection TimeThrottle =>
new ParameterSection("Time Throttle", true,
"Reduces re-quoting updates by requiring a minimum amount of time to elapse between quotes.");
public static ParameterSection Conditional =>
new ParameterSection("Conditional", true,
"Reduces re-quoting updates by only quoting if N ticks away from the opposite " +
@@ -2,7 +2,6 @@
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
@@ -30,8 +29,16 @@ namespace mbts.spark {
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;
bool
_set_name_automatically,
_set_ticks_automatically,
_set_quantities_automatically,
_set_price_mults_automatically,
_is_setting_quantities,
_is_setting_price_mults,
_use_fractional_ticks = true;
TickFactors _tick_factors;
int _integer_tick_size = 1;
ExecutionStrategyImmutable _strategy;
HedgePricing _hedge_pricing;
@@ -44,6 +51,10 @@ namespace mbts.spark {
: this(synthetic, strategy, false) {
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
_use_fractional_ticks = false;
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
}
title = $"Edit '{synthetic.Synthetic.Name}'";
icon = Icons.ModifySynthetic;
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
@@ -74,9 +85,7 @@ namespace mbts.spark {
icon = Icons.Synthetics;
_has_working_orders = backing(nameof(has_working_orders), false);
contract_picker_model = new ContractPickerModel(bus, false, null, new SecurityCriteria(new[] {
new SecurityCriteriaClause() {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
}
new SecurityCriteriaClause(exchanges: EnumHelper.get_values_except(Venue.Bts).ToArray())
}));
contract_picker_model.cancel_action += _close_new_leg_popup;
contract_picker_model.ok_action += () => {
@@ -131,14 +140,14 @@ namespace mbts.spark {
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
valid = false;
}
if (_tick_factors == null || !_tick_factors.is_valid()) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
if (use_fractional_ticks ? (_tick_factors == null || !_tick_factors.is_valid()) : integer_tick_size == 0) {
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, use_fractional_ticks ? nameof(tick_factors) : nameof(integer_tick_size)).ignore();
valid = false;
}
if (valid) {
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
_synthetic.Symbol = _deduce_symbol(_synthetic);
_synthetic.ContractSpec = _tick_factors.to_spec();
_synthetic.ContractSpec = use_fractional_ticks ? _tick_factors.to_spec() : TickUtils.create_integer_tick_spec(integer_tick_size);
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
@@ -187,6 +196,9 @@ namespace mbts.spark {
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
var quote_time_throttle_delay_to_run = new QuoteTimeThrottleDelayToRunModel();
var quote_time_throttle_delay_to_improve = new QuoteTimeThrottleDelayToImproveModel();
var conditional_quoting_ticks = new ConditionalQuotingTicksModel();
var conditional_quoting_price_type = new ConditionalQuotingPriceTypeModel();
@@ -214,17 +226,24 @@ namespace mbts.spark {
_quantity_model,
_price_mult_model,
is_quote_model,
//new EnableHedgeModel(), //TAHA_SPREADER
new EnableHedgeModel(), //TAHA_SPREADER
new HedgeRatioModel(),
//new MinLeanQtyModel(), //TAHA_SPREADER
new LeanOffsetTicksModel(),
//new QuoteHiddenModel(),
new QuoteQueueHolderModel(),
new MarketDataTypeModel(),
new ShowQuantityEnabledModel(show_quantity_quantity),
show_quantity_quantity,
// Throttle
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
quote_throttle_outside_ticks,
// Time Throttle
new QuoteTimeThrottleEnabledModel(quote_time_throttle_delay_to_run, quote_time_throttle_delay_to_improve),
quote_time_throttle_delay_to_run,
quote_time_throttle_delay_to_improve,
new ConditionalQuoteEnabledModel(conditional_quoting_ticks, conditional_quoting_price_type),
conditional_quoting_ticks,
//conditional_quoting_price_type,
@@ -295,6 +314,20 @@ namespace mbts.spark {
public bool has_working_orders => _has_working_orders.Value;
public bool is_new { get; }
public bool is_new_leg_popup_open { get; set; }
public bool use_fractional_ticks {
get => _use_fractional_ticks;
set {
if (Set(ref _use_fractional_ticks, value))
_set_ticks_automatically = false;
}
}
public int integer_tick_size {
get => _integer_tick_size;
set {
if (Set(ref _integer_tick_size, value))
_set_ticks_automatically = false;
}
}
public bool can_add_leg => _can_add_leg.Value;
public SyntheticContract.Types.PriceCalc price_calc { get => _synthetic.Synthetic.PriceCalc; set => _synthetic.Synthetic.PriceCalc = value; }
public ObservableCollection<LegSpreaderParamsTuple> leg_tuples { get; } = new ObservableCollection<LegSpreaderParamsTuple>();
@@ -323,6 +356,16 @@ namespace mbts.spark {
_strategy = strategy;
hedge_pricing = strategy.HedgePricing;
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
if (synthetic.HasContractSpec) {
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
/// rather than the properties <see cref="use_fractional_ticks"/> and <see cref="integer_tick_size"/>
/// because the setters of the former flip <see cref="_set_ticks_automatically"/> to false if the value changes.
/// This behavior causes the ticks to be automatically deduced as legs are added/removed,
/// until the user explicitly enters a different value (as the properties are bound to tick input controls in the view).
Set(ref _use_fractional_ticks, !TickUtils.is_integer_tick(synthetic.ContractSpec), nameof(use_fractional_ticks));
if (!use_fractional_ticks)
Set(ref _integer_tick_size, DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0, nameof(integer_tick_size));
}
RaisePropertyChanged(nameof(name), nameof(tick_factors));
}
@@ -169,7 +169,7 @@ namespace mbts.spark {
void _show_order_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
security_criteria = SecurityCriteria.from_securities(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
@@ -9,6 +9,8 @@ namespace mbts.spark {
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
struct AlgoData : IEquatable<AlgoData> {
public static readonly Func<AlgoData, AlgoKey> key_fn = a => a.key;
public readonly CustomOrderDefinitionImmutable value;
public readonly string name;
public readonly AlgoKey key;
@@ -128,6 +128,7 @@ namespace mbts.spark {
}
public bool is_persisted => mode == Mode.Persisted;
public bool is_composite => _composite_key.HasValue || _unpersisted.HasValue;
public uint persisted => _composite_key.Value;
public Guid unpersisted => _unpersisted.Value;
@@ -199,6 +200,15 @@ namespace mbts.spark {
}
return EqualityComparer.phase_hash(hash, phase_mult);
}
public override string ToString() {
switch (mode) {
case Mode.Dynamic: return $"Dynamic: {dynamic_type}";
case Mode.Factory: return $"Factory: {factory}";
case Mode.Persisted: return $"Composite: {persisted}";
case Mode.Unpersisted: return $"(Composite): {unpersisted}";
default: return mode.ToString();
}
}
public static bool operator ==(AlgoKey a, AlgoKey b) => a.Equals(b);
public static bool operator !=(AlgoKey a, AlgoKey b) => !a.Equals(b);
@@ -12,20 +12,20 @@ namespace mbts.spark {
[Export(typeof(IAlgoStudioModel))]
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
readonly AlgoDefinitionService _service;
readonly AdminModel _admin_model;
readonly UserTuple _user;
readonly ErrorReporter _error_reporter;
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
IDisposable _dispose;
[ImportingConstructor]
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, UserTuple user, ErrorReporter error_reporter) {
_service = service;
this.metadata_service = metadata_service;
_admin_model = admin_model;
_user = user;
_error_reporter = error_reporter;
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
.Filter(r => r.Metadata.IsActive && _user.can_access_user_data(r.Metadata.CreatorUserId))
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
@@ -37,7 +37,7 @@ namespace mbts.spark {
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
_with_key(new AlgoDefinitionUpdateRequest { UserId = _user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving definition");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
@@ -93,4 +93,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -11,6 +11,8 @@ namespace mbts.spark {
/// decouple the client-side key type from the server-side one, so both key types may evolve independently moving forward.
/// </summary>
struct AlgoDefinitionMetadata : IEquatable<AlgoDefinitionMetadata> {
public static readonly Func<AlgoDefinitionMetadata, AlgoKey> key_fn = m => m.id;
public readonly AlgoKey id, default_template;
readonly AlgoKey[] _favorite_templates;
public readonly bool is_favorite, is_hidden;
@@ -35,34 +35,21 @@ namespace mbts.spark {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed)
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) =>
update_persisted_definitions(target, update.added, update.changed, update.removed.Select(AlgoData.key_fn), factory);
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, IEnumerable<AlgoData> added, IEnumerable<AlgoData> changed, IEnumerable<AlgoKey> removed, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var k in removed)
target.Remove(k);
foreach (var d in changed)
target[d.key].load(d);
foreach (var d in update.added) {
foreach (var d in added) {
if (target.TryGetValue(d.key, out AlgoDefinitionVM def)) {
def.load(d);
} else target.Add(factory(d));
}
}
public static void update_persisted_definition_wrappers(IDictionary<AlgoKey, IAlgoVM> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed) {
var vm = target[d.key];
if (vm is AlgoVMWrapper wrapper) {
wrapper.value.load(d);
} else vm.load_from(d.value);
}
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out IAlgoVM def)) {
def.load_from(d.value);
} else target.Add(d.key, new AlgoVMWrapper(factory(d)));
}
}
public static void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).commit_all();
@@ -42,21 +42,21 @@ namespace mbts.spark {
readonly Property<string> _name, _abbreviation;
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
readonly DeferrableActionWrapper _refresh_on_local_changes;
readonly Property<AlgoFormats> _selected_format;
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
int? _favorite_index;
Brush _badge_fill;
Snapshot? _committed;
AlgoDefinitionMetadata _metadata;
IDisposable _loaded_data_hook;
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
new AlgoDefinitionVM(model, data, editing_permissions);
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name) =>
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), (AlgoEditingPermissions)int.MaxValue);
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoEditingPermissions init_permissions) {
// init core data
_model = model;
_permissions_mask.mask = init_permissions;
@@ -80,7 +80,6 @@ namespace mbts.spark {
// init backing props
_name = backing(nameof(name), def_data.name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
_selected_format = backing(nameof(selected_format), init_format);
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
_is_expanded = backing(nameof(is_expanded), true);
_is_selected = backing(nameof(is_selected), true);
@@ -133,17 +132,6 @@ namespace mbts.spark {
break;
}
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeEach(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 = AlgoKey.from_known_type(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)));
@@ -202,9 +190,6 @@ namespace mbts.spark {
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
public Brush badge_fill => _badge_fill;
// format
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
// 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;
@@ -236,12 +221,20 @@ namespace mbts.spark {
get => algo_root;
set => algo_root = value;
}
IAlgoVM IAlgoParamVM<IAlgoVM>.display_value {
get => algo_root;
set => algo_root = value;
}
bool IAlgoParamVM.has_value => _algo_root != null;
bool IAlgoParamVM.is_enabled { get; set; } = true;
object IAlgoParamVM.value {
get => algo_root;
set => algo_root = (IAlgoVM)value;
}
object IAlgoParamVM.display_value {
get => algo_root;
set => algo_root = (IAlgoVM)value;
}
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
AlgoParamPermissions IAlgoParamVM.permissions { get => _permissions; set => _permissions = value; }
@@ -262,17 +255,14 @@ namespace mbts.spark {
void IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)>.OnError(Exception error) { }
#endregion
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, is_expanded: true));
if (!try_select(orig_algo) && !try_select(default_algo))
algo_root = algo_roots.FirstOrDefault();
//
bool try_select(AlgoKey? algo_key) {
if (algo_key.HasValue && _algo_roots.TryGetValue(algo_key.Value, out IAlgoVM match)) {
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> data, AlgoKey? default_algo) {
Disposable.dispose(ref _loaded_data_hook);
_algo_roots.SyncWithDictionary(data, d => OrderTypeVMFactory.create(d, is_expanded: true));
_loaded_data_hook = ObservableAutoWrapper.connect_item_hooks(algo_roots, vm =>
ObservableAutoWrapper.connect_item_actions(vm.child_algo_selectors, s => s.load_algo_data(data, null)));
if (algo_root == null || !_algo_roots.ContainsKey(algo_root.id)) {
if (default_algo.HasValue && _algo_roots.TryGetValue(default_algo.Value, out IAlgoVM match))
algo_root = match;
return true;
} else return false;
}
}
@@ -338,7 +328,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, selected_format, editing_permissions_mask);
new AlgoDefinitionVM(_model, def, editing_permissions_mask);
public AlgoData create_algo_def() =>
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
@@ -360,7 +350,6 @@ namespace mbts.spark {
void _assign_from_data(Snapshot state) {
_assign_from_data(state.data);
_selected_format.set(state.format);
_abbreviation.set(state.abbreviation);
custom_badge_color.set_nullable(state.custom_badge_color);
}
@@ -421,41 +410,36 @@ namespace mbts.spark {
}
Snapshot _snapshot() =>
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable(), selected_format);
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable());
public struct Snapshot : IEquatable<Snapshot> {
public readonly AlgoData data;
public readonly string abbreviation;
public readonly Color? custom_badge_color;
public readonly AlgoFormats format;
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color, AlgoFormats format) {
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color) {
this.data = data;
this.abbreviation = abbreviation;
this.custom_badge_color = custom_badge_color;
this.format = format;
}
public bool Equals(Snapshot other) =>
data == other.data &&
abbreviation == other.abbreviation &&
custom_badge_color == other.custom_badge_color &&
format == other.format;
custom_badge_color == other.custom_badge_color;
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
data.GetHashCode(),
abbreviation?.GetHashCode() ?? 0,
custom_badge_color?.GetHashCode() ?? 0,
(int)format);
custom_badge_color?.GetHashCode() ?? 0);
#if DEBUG
public override string ToString() {
var sb = new System.Text.StringBuilder();
sb.Append($"name: {data.name}; ");
sb.Append($"abbrev.: {abbreviation}; ");
sb.Append($"badge: {custom_badge_color} ");
sb.Append($"fmt: {format} ");
sb.Append($"def: {data.value}");
return sb.ToString();
}
@@ -469,7 +453,6 @@ namespace mbts.spark {
public bool Equals(Snapshot x, Snapshot y) =>
x.abbreviation == y.abbreviation &&
x.custom_badge_color == y.custom_badge_color &&
x.format == y.format &&
x.data.key == y.data.key &&
x.data.name == y.data.name &&
x.data.value.Equals(y.data.value);
@@ -1,7 +1,11 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
@@ -37,7 +41,6 @@ namespace mbts.spark {
}
class AlgoStudioVM : DisposableViewModelBase {
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
readonly OrdersModel _model;
readonly Property<AlgoDefinitionVM> _selected_definition;
readonly Property<IAlgoVM> _editable_content;
@@ -70,12 +73,24 @@ namespace mbts.spark {
_revert_cmd_tooltip = backing(nameof(revert_cmd_tooltip), get_revert_tooltip);
//
_dispose = new CompositeDisposable(
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
var orig_selected_def = selected_definition;
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
_model.subscribe_complete_algos(allow_undefined_child: true).OnDispatcher().Subscribe(_definitions_update_buffer),
_definitions_update_buffer.OnDispatcher()
.SubscribeEach(u => {
u.FilterIncremental(a => a.key.is_composite, out var added, out var changes, out var removed);
AlgoCollectionHelper.update_persisted_definitions(
_definitions, added, changes.New(), removed.Select(AlgoData.key_fn), algo => AlgoDefinitionVM.create(_model, algo));
if (selected_definition == null)
selected_definition = definitions.FirstOrDefault();
// Load the algo building blocks (i.e., factory and dynamic algos):
// - Composite algos cannot reference other composite algos - that would create awkward dependency issues as composite algos can be modified/deleted.
// - Spreader algos are filtered out as well, because that don't actually mean anything to the algo structure.
// Synthetic securities in our system are defined by a set of contract legs, and a [single]strategy.
// Note: synthetic strategy templates, however, could be introduced at a later date (no reason why 1 synthetic security couldn't have several ExecutionStrategies).
var algo_building_blocks = new FilteredDictionary<AlgoKey, AlgoData>(
u.state,
a => a.key.mode == AlgoKey.Mode.Dynamic || (a.key.mode == AlgoKey.Mode.Factory && a.key.factory != CustomOrderDefinition.CustomOneofCase.Spreader));
return ObservableAutoWrapper.connect_item_actions(_definitions.AsCollection(),
d => ((IAlgoSelector)d).load_algo_data(algo_building_blocks, null));
}),
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
_persisted_meta = meta;
@@ -125,7 +140,7 @@ namespace mbts.spark {
}
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name);
_definitions.Add(new_def);
selected_definition = new_def;
request_view_focus_name?.Invoke(new_def);
@@ -1,214 +0,0 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace mbts.spark {
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
IObserver<SecurityMD?>,
IObserver<SecurityImmutable?>,
IObserver<OrdersModel>,
IObserver<AlgoEditingPermissions> {
static readonly HashSet<string>
_algo_root_echo_props = new HashSet<string> {
nameof(is_expanded),
nameof(editing_permissions),
nameof(definition),
nameof(display_name)
};
static readonly IReadOnlyDictionary<string, string>
_value_echo_props = new Dictionary<string, string> {
{ nameof(AlgoDefinitionVM.name), nameof(display_name) },
{ nameof(AlgoDefinitionVM.abbreviation), nameof(abbreviation) },
{ nameof(AlgoDefinitionVM.badge_fill), nameof(meta) }
};
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly DisposableContainer _value_hooks = new DisposableContainer();
Action _definition_changed = () => { };
_ObservedData? _data_cache;
AlgoDefinitionVM _value;
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
value = algo_definition_vm;
_dispose = new CompositeDisposable(_value_hooks) {
_child_algo_selectors.clear_collections,
_algo_params.clear_collections,
() => _value?.Dispose()
};
}
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
public AlgoDefinitionVM value {
get => _value;
set {
if (_value != value) {
_value?.Dispose();
_value = value;
_value_hooks.replace(_set_hooks(value));
RaisePropertyChanged(nameof(value));
}
}
}
public AlgoKey id => _value.key;
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
public OrderMetadata meta {
get {
if (_value != null) {
if (_value.custom_badge_color.enabled)
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
else if (_value.algo_root != null)
return _value.algo_root.meta;
}
return OrderDataHelper.get_metadata(order_type);
}
}
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
public bool can_expand => _algo_root?.can_expand ?? false;
public bool is_expanded {
get => _value.algo_root?.is_expanded ?? false;
set {
if (_value != null)
_value.is_expanded = value;
}
}
public string display_name => _value?.name ?? string.Empty;
public string abbreviation => _value?.abbreviation;
public AlgoDisplayGroup group => _value?.group ?? AlgoDisplayGroup.BTS;
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
[EditorBrowsable(EditorBrowsableState.Never)]
public ObservableCollection<IAlgoSelector> child_algo_selectors_view {
get {
_value?.algo_root?.ensure_init();
return _child_algo_selectors;
}
}
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
[EditorBrowsable(EditorBrowsableState.Never)]
public dynamic children_vm => _algo_root?.children_vm;
IAlgoVM _algo_root => _value?.algo_root;
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
IDisposable _set_hooks(AlgoDefinitionVM vm) {
var algo_root_subs = new DisposableContainer();
vm.PropertyChanged += on_value_prop_changed;
return new CompositeDisposable(
vm.observe_algo_root().Subscribe(r => {
algo_root_subs.replace(null);
_child_algo_selectors.clear_collections();
_algo_params.clear_collections();
if (r != null) {
if (_data_cache.HasValue) {
_data_cache.Value.forward_to(r);
}
_child_algo_selectors.add_collection(r.child_algo_selectors);
_algo_params.add_collection(r.algo_params);
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
algo_root_subs.replace(new CompositeDisposable(
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
});
}
RaisePropertyChanged(nameof(group));
}),
algo_root_subs) {
() => vm.PropertyChanged -= on_value_prop_changed
};
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_algo_root_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
}
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
RaisePropertyChanged(wrapper_prop);
}
}
// permissions
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
}
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
}
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnCompleted() { }
// security
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
}
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
void IObserver<SecurityImmutable?>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
struct _ObservedData {
public readonly AlgoEditingPermissions permissions;
public readonly OrdersModel model;
public readonly SecurityImmutable? security;
public readonly SecurityMD? security_md;
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
this.permissions = permissions;
this.model = model;
this.security = security;
this.security_md = security_md;
}
public void forward_to(IAlgoVM recipient) {
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
if (model != null)
(recipient as IObserver<OrdersModel>)?.OnNext(model);
if (security_md.HasValue)
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
if (security.HasValue)
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
}
public _ObservedData with(AlgoEditingPermissions? permissions) =>
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
public _ObservedData with(OrdersModel model) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityImmutable? security) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityMD? security_md) =>
new _ObservedData(permissions, model, security, security_md);
}
}
}
@@ -199,18 +199,30 @@
Grid.Column="1"
HorizontalAlignment="Center"
Foreground="{u:BTSColor MarketData.bid}"
Text="{Binding DataContext.bbo.bid_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Bid Size"
/>
>
<TextBlock.Text>
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
<Binding Path="DataContext.bbo.bid_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock
x:Name="ask_text"
Grid.Column="2"
HorizontalAlignment="Center"
Margin="4,0"
Foreground="{u:BTSColor MarketData.ask}"
Text="{Binding DataContext.bbo.ask_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Ask Size"
/>
>
<TextBlock.Text>
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
<Binding Path="DataContext.bbo.ask_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
@@ -272,6 +284,14 @@
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--TODO: replace all the param.value bindings with param.display_value.
The reason this has not already been done is that the display_value property is new,
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
<DataTemplate>
<TextBlock Text="{Binding param.display_value}"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -282,7 +302,7 @@
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource DoubleTextConverter}}"/>
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Enum-->
@@ -360,6 +380,22 @@
/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<!--TODO: replace all the param.value bindings with param.display_value.
The reason this has not already been done is that the display_value property is new,
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
<DataTemplate>
<c:TickUpDownNullableInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownNullableInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownNullableInt.CommandBindings>
<c:TickUpDownNullableInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownNullableInt.InputBindings>
</c:TickUpDownNullableInt>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
@@ -370,7 +406,7 @@
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownDouble.CommandBindings>
@@ -39,31 +39,6 @@ namespace mbts.spark {
}
}
static IDisposable _wrap_rows(AlgoDefinitionVM algo_def, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) {
AlgoParamRowVM root = null;
ObservableAggregateCollection<AlgoParamRowVM> algo_params;
if (include_algo_type_rows) {
// while normal params live and die with the selected algo-type (when the algo-type changes, they are removed, and replaced with params for the new algo type),
// the algo-type row is constant (it is the row with the algo-type dropdown, which the user may use to select different algos).
algo_params = new ObservableAggregateCollection<AlgoParamRowVM>();
rows.add_collection(new ObservableAppendCollection<AlgoParamRowVM>(algo_params, root = new AlgoSelectorParamRowVM(new AlgoVMWrapper(algo_def), algo_def, null)).AsReadOnly());
} else {
algo_params = rows;
}
return algo_def.observe_algo_root().SubscribeEach(a => {
algo_params.clear_collections();
if (a == null)
return Disposable.empty;
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
/// Note: the order of operations here matters
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
var hook = _wrap_rows(a, root, child_rows, include_algo_type_rows);
algo_params.add_collection(child_rows.AsReadOnly());
return hook;
});
}
static IDisposable _wrap_rows(IAlgoVM algo_root, AlgoParamRowVM parent, ObservableAggregateCollection<AlgoParamRowVM> all_rows, bool include_algo_type_rows) {
var wrapped_params = new ObservableAutoWrapper<AlgoParamRowVM, IAlgoParamVM>(algo_root.algo_params, pvm => new AlgoParamRowVM(algo_root, pvm, parent));
var selector_params = new ObservableCollection<AlgoParamRowVM>();
@@ -85,4 +60,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -106,7 +106,7 @@
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
Content="{Binding algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
@@ -123,7 +123,7 @@ namespace mbts.spark {
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.display_size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), new DoubleMinMax<FillTuple>(r => r.fill.LastPrice), DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), dv_filter(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill.FillKind), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
add_col(Columns.FillStatus, DefaultColumnHeaders.FillStatus, r => r.fill_status.ToString(), dv_filter(EnumHelper.get_values<FillStatus>().ToArray(), r => r.fill.FillStatus), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
@@ -287,6 +287,7 @@ namespace mbts.spark {
Color? _root_algo_badge_color;
OrderMetadata _order_metadata;
bool _is_selected;
double? _display_size;
public Row(FillTuple fill_tuple, OrdersModel model) :
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
@@ -307,6 +308,8 @@ namespace mbts.spark {
public Side side => _fill.Side;
public double price => _fill.LastPrice;
public double size => _fill.LastShares;
public double display_size => _display_size ??
(_display_size = size.safe_divide(_model.security_auxiliary_model.get_display_quantity_divisor(security))).Value;
public FillKind fill_kind => _fill.FillKind;
public FillStatus fill_status => _fill.FillStatus;
public string fill_key => _fill.FillKey;
@@ -6,6 +6,7 @@ using bts.utils;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility.text;
using ProtoBuf;
using System;
using System.Collections.Generic;
@@ -13,6 +14,7 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace mbts.spark {
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
@@ -87,6 +89,15 @@ namespace mbts.spark {
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
}
[ProtoContract]
class LadderOrderLimits : IEquatable<LadderOrderLimits> {
[ProtoMember(1)]
public ToggleInt32Proto max_settlement_cross_ticks { get; set; }
public bool Equals(LadderOrderLimits other) =>
other != null && max_settlement_cross_ticks == other.max_settlement_cross_ticks;
}
[ProtoContract]
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
// this class is 100% mutable (there are literally 0 readonly members),
@@ -157,6 +168,10 @@ namespace mbts.spark {
[ProtoContract]
class LadderMarketBreakLine : IEquatable<LadderMarketBreakLine> {
public enum RefreshMode {
Auto,
Manual
}
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderMarketBreakLine).GetHashCode();
@@ -164,6 +179,8 @@ namespace mbts.spark {
public bool show { get; set; }
[ProtoMember(2)]
public uint color { get; set; }
[ProtoMember(3)]
public RefreshMode refresh_mode { get; set; }
public uint? color_nullable {
get {
@@ -176,7 +193,8 @@ namespace mbts.spark {
public bool Equals(LadderMarketBreakLine other) =>
other != null &&
other.show == show &&
other.color == color;
other.color == color &&
other.refresh_mode == refresh_mode;
public override bool Equals(object obj) =>
Equals(obj as LadderMarketBreakLine);
@@ -185,6 +203,37 @@ namespace mbts.spark {
_type_hash;
}
[ProtoContract]
class FairValueLine : IEquatable<FairValueLine> {
public enum PriceType {
Bid,
Ask,
Mid
}
[ProtoMember(1)]
public uint security_key { get; set; }
[ProtoMember(2)]
public PriceType price_type { get; set; }
[ProtoMember(3)]
public LadderMarketBreakLine display { get; set; }
public bool Equals(FairValueLine other) {
return other != null &&
security_key == other.security_key &&
price_type == other.price_type &&
display.safe_equals(other.display);
}
public override bool Equals(object obj) {
return Equals(obj as FairValueLine);
}
public override int GetHashCode() {
return (int)security_key;
}
}
[Flags]
enum LadderFavoriteAlgoFlags {
Sticky = 1,
@@ -308,12 +357,147 @@ namespace mbts.spark {
public static bool operator !=(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => !a.Equals(b);
}
[ProtoContract]
class LadderPanelPricePointSettings : IEquatable<LadderPanelPricePointSettings> {
public static LadderPanelPricePointSettings historic_default =>
new LadderPanelPricePointSettings {
day_change = new DaySettings {
// (neutral)
font = _default_font,
background_color = Colors.Transparent.rgb_color(),
// negative
negative_font_color = Colors.White.rgb_color(),
negative_background_color = Colors.Red.rgb_color(),
// positive
positive_font_color = Colors.White.rgb_color(),
positive_background_color = Colors.Green.rgb_color()
},
session_low = _default_item,
session_high = _default_item,
open = _default_item,
settlement = _default_item,
volume = _default_item
};
public static LadderPanelPricePointSettings tt_default {
get {
var rv = historic_default;
rv.day_change.background_color = Color.FromRgb(113, 201, 69).rgb_color();
rv.day_change.font.font_color = Colors.Black.rgb_color();
return rv;
}
}
public static LadderPanelPricePointSettings Default => LadderSettings.mimic_tt ?
tt_default : historic_default;
[ProtoMember(1)]
public DaySettings day_change { get; set; }
[ProtoMember(2)]
public ItemSettings session_low { get; set; }
[ProtoMember(3)]
public ItemSettings session_high { get; set; }
[ProtoMember(4)]
public ItemSettings open { get; set; }
[ProtoMember(5)]
public ItemSettings settlement { get; set; }
[ProtoMember(6)]
public ItemSettings volume { get; set; }
public bool Equals(LadderPanelPricePointSettings other) =>
other != null &&
day_change.safe_equals(other.day_change) &&
session_high.safe_equals(other.session_high) &&
session_high.safe_equals(other.session_high) &&
open.safe_equals(other.open) &&
settlement.safe_equals(other.settlement) &&
volume.safe_equals(other.volume);
static FontParamsProto _default_font =>
new FontParamsProto {
font_color = Colors.Black.rgb_color(),
font_size = FontSizes.Regular,
typeface = new TypefaceProto(TextUtils.StandardFontFamilyName, FontWeights.Bold.ToOpenTypeWeight())
};
static ItemSettings _default_item =>
new ItemSettings {
font = _default_font,
background_color = Colors.Transparent.rgb_color()
};
[ProtoContract]
readonly struct SignedColorPair : IEquatable<SignedColorPair> {
[ProtoMember(1)]
public uint positive_color { get; }
[ProtoMember(2)]
public uint negative_color { get; }
public bool Equals(SignedColorPair other) =>
positive_color == other.positive_color &&
negative_color == other.negative_color;
}
[ProtoContract]
public class ItemSettings : IEquatable<ItemSettings> {
[ProtoMember(1)]
public FontParamsProto font { get; set; }
[ProtoMember(2)]
public uint background_color { get; set; }
[ProtoMember(3)]
public bool show_label { get; set; }
public bool Equals(ItemSettings other) =>
other != null &&
font.safe_equals(other.font) &&
background_color == other.background_color &&
show_label == other.show_label;
}
[ProtoContract]
public class DaySettings : IEquatable<DaySettings> {
[ProtoMember(1)]
public FontParamsProto font { get; set; }
// [neutral] background
[ProtoMember(2)]
public uint background_color { get; set; }
[ProtoMember(3)]
public bool show_label { get; set; }
// negative
[ProtoMember(4)]
public uint negative_font_color { get; set; }
[ProtoMember(5)]
public uint negative_background_color { get; set; }
// positive
[ProtoMember(6)]
public uint positive_font_color { get; set; }
[ProtoMember(7)]
public uint positive_background_color { get; set; }
public uint neutral_foreground => font?.font_color ?? 0;
public bool Equals(DaySettings other) =>
other != null &&
font.safe_equals(other.font) &&
background_color == other.background_color &&
show_label == other.show_label &&
negative_font_color == other.negative_font_color &&
negative_background_color == other.negative_background_color &&
positive_font_color == other.positive_font_color &&
positive_background_color == other.positive_background_color;
}
}
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
// 1 important reason at this time:
// From the settings dialog, default settings will only be persisted when they are NOT EQUAL to the current default settings.
// Settings are liable to change in the near future, so an internal IEqualityComparer simply won't do as it risks becoming out of date.
[ProtoContract]
class LadderSettings : IEquatable<LadderSettings> {
// RESERVED ProtoMember indices
// 900-999: dj settings
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
@@ -323,6 +507,13 @@ namespace mbts.spark {
// this class is 100% mutable (there are literally 0 readonly members),
// so GetHashCode must return the same hash for every instance.
static readonly int _type_hash = typeof(LadderSettings).GetHashCode();
uint _ticks_per_row;
List<FairValueLine> _fair_value_lines;
static LadderSettings() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
mimic_tt = knobs.mimic_tt;
}
// Default proto constructor
// Added this so that when clients upgrade to a version with new settings,
@@ -354,8 +545,10 @@ namespace mbts.spark {
enforce_max_clip = _default_enforce_max_clip;
status_message_visible = true;
}
public static bool mimic_tt { get; }
public static LadderSettings Default {
public static LadderSettings historic_default {
get {
LadderColumnSettings
orders_column = LadderColumnSettings.DefaultOrderColumnSettings,
@@ -365,12 +558,12 @@ namespace mbts.spark {
last_column = LadderColumnSettings.Default,
piq_column = LadderColumnSettings.DefaultPiqColumnSettings;
orders_column.background = BTSColors.LadderSpecial.default_column_background;
bid_column.foreground = 0xFFFFFF;
bid_column.foreground = 0xFFFFFFFF;
bid_column.background = BTSColors.MarketData.bid;
ask_column.foreground = 0xFFFFFF;
ask_column.foreground = 0xFFFFFFFF;
ask_column.background = BTSColors.MarketData.ask;
price_column.background = BTSColors.LadderSpecial.default_column_background;
last_column.background = 0xFFFFFF;
last_column.background = 0xFFFFFFFF;
piq_column.background = BTSColors.LadderSpecial.default_column_background;
var rv = new LadderSettings {
row_height = 22,
@@ -414,6 +607,7 @@ namespace mbts.spark {
auto_clear_visible = true,
clear_visible = true,
order_entry_panel_initial_width = _default_order_entry_panel_width,
panel_price_point_settings = LadderPanelPricePointSettings.Default,
zoom_slider_visible = false,
zoom_factor = _default_zoom_factor,
net_change = NetChangeTerms.None,
@@ -426,6 +620,28 @@ namespace mbts.spark {
return rv;
}
}
public static LadderSettings tt_default {
get {
var rv = historic_default;
rv.orders_column.background = Color.FromRgb(223, 223, 223).rgb_color();
// Since the price column sits atop the session range fill in tt
// mode (normally it is the other way around), the former is made
// partially translucent while the latter is fully opaque (obv the
// top layer cannot be opaque, since it would completely hide the
// layer beneath it).
rv.session_range_fill.color = rv.session_range_fill.color.rgb_color().with(255).rgb_color();
rv.price_column.background = Color.FromArgb(226, 166, 166, 166).rgb_color();
rv.price_column.foreground = Colors.White.rgb_color();
rv.bid_column.background = Color.FromRgb(0, 30, 160).rgb_color();
rv.ask_column.background = Color.FromRgb(160, 0, 0).rgb_color();
rv.ask_column.foreground = Colors.Black.rgb_color();
return rv;
}
}
public static LadderSettings Default => mimic_tt ?
tt_default : historic_default;
public enum BadgeStyle { Normal, Alternate }
@@ -592,6 +808,22 @@ namespace mbts.spark {
[ProtoMember(61), DefaultValue(true)]
public bool status_message_visible { get; set; }
[ProtoMember(62)]
public uint ticks_per_row {
get => Math.Max(_ticks_per_row, 1);
set => _ticks_per_row = value;
}
[ProtoMember(900)]
public LadderOrderLimits ladder_order_limits { get; set; }
[ProtoMember(901)]
public LadderPanelPricePointSettings panel_price_point_settings { get; set; }
[ProtoMember(902)]
public List<FairValueLine> fair_value_lines {
get => _fair_value_lines ?? (_fair_value_lines = new List<FairValueLine>());
set => _fair_value_lines = value;
}
public void sanitize() =>
sanitize(this);
@@ -625,6 +857,11 @@ namespace mbts.spark {
s.mini_ob = new LadderMiniOrderBookSettings(s.mini_ob.value.with(sorting_criterion: bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general.sorting_criterion));
#pragma warning restore CS0612 // Type or member is obsolete
s.row_height = LadderRender.measure_row_height(s);
if (s.panel_price_point_settings is null)
s.panel_price_point_settings = LadderPanelPricePointSettings.Default;
if (s.fair_value_lines is null) {
s.fair_value_lines = new List<FairValueLine>(0);
}
return s;
}
@@ -697,7 +934,11 @@ namespace mbts.spark {
a.enforce_max_clip == b.enforce_max_clip &&
a.max_clip == b.max_clip &&
a.price_display_format == b.price_display_format &&
a.status_message_visible == b.status_message_visible;
a.status_message_visible == b.status_message_visible &&
a.ladder_order_limits.safe_equals(b.ladder_order_limits) &&
a.panel_price_point_settings.safe_equals(b.panel_price_point_settings) &&
a._ticks_per_row == b._ticks_per_row &&
EqualityComparer.safe_sequence_equals(a.fair_value_lines, b.fair_value_lines);
public bool Equals(LadderSettings other) =>
profile_equals(this, other) &&
@@ -100,6 +100,7 @@ namespace bts.shell {
ShellWindowVM shell_window_vm = null;
IShellWindow shell_window = null;
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
LadderVM vm = null;
var view = new Lazy<UIElement>(() => {
// resources
var model = new LadderModel(md, _orders_model, _mbo);
@@ -113,7 +114,6 @@ namespace bts.shell {
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
bool initialized = false, needs_resync = false;
LadderVM vm = null;
var settings_subject = new Subject<LadderSettings>();
var settings_source = Observable.Create<LadderSettings>(o => {
ensure_synced();
@@ -208,6 +208,10 @@ namespace bts.shell {
s => {
var svm = new LadderSettingsVM(model.order_gateway, vm.security);
svm.load(s);
svm.SubscribeDisposed(
_bus.subscribe(SparkTags.SecuritiesTag)
.OnDispatcher()
.Subscribe(sec_map => svm.security_map = sec_map));
return svm;
},
_ => _,
@@ -272,10 +276,11 @@ namespace bts.shell {
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
icon: Icons.Ladder,
thread_id: _thread_id_provider.next_id(),
vm => {
shell_window_vm = vm;
swvm => {
shell_window_vm = swvm;
title_source.connect();
});
},
key_event_args => vm?.key_down(key_event_args));
}
async void _show_windows(IImmutableSet<uint> securities) {
@@ -330,4 +335,4 @@ namespace bts.shell {
[ProtoMember(3)]
public uint settings_id;
}
}
}
@@ -0,0 +1,93 @@
using mbts.ui;
using mbts.ui.controls.color_picker;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.spark {
class FairValueLineSettingsVM : ui.shell.ViewModelBase {
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
public FairValueLineSettingsVM() {
add_item_cmd = new DelegateCommand(() => items.Add(new FairValueLineItemSettingsVM()));
remove_item_cmd = new DelegateCommand<FairValueLineItemSettingsVM>(item => items.Remove(item));
}
public ObservableCollection<FairValueLineItemSettingsVM> items { get; } =
new ObservableCollection<FairValueLineItemSettingsVM>();
public IReadOnlyDictionary<uint, SecurityMD> security_map {
get => _security_map;
set => Set(ref _security_map, value);
}
public ICommand add_item_cmd { get; }
public ICommand remove_item_cmd { get; }
}
class FairValueLineItemSettingsVM : ui.shell.ViewModelBase {
uint _selected_security_key;
FairValueLine.PriceType _selected_price_type;
Color _color;
bool _show = true;
bool _auto_refresh = true;
public FairValueLineItemSettingsVM() {
palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
}
public uint selected_security_key {
get => _selected_security_key;
set => Set(ref _selected_security_key, value);
}
public IEnumerable<FairValueLine.PriceType> price_types =>
ui.utility.EnumHelper.get_values<FairValueLine.PriceType>();
public FairValueLine.PriceType selected_price_type {
get => _selected_price_type;
set => Set(ref _selected_price_type, value);
}
public Color color { get => _color; set => Set(ref _color, value); }
public bool show { get => _show; set => Set(ref _show, value); }
public bool auto_refresh {
get => _auto_refresh; set {
if (Set(ref _auto_refresh, value)) {
RaisePropertyChanged(nameof(refresh_mode));
}
}
}
public LadderMarketBreakLine.RefreshMode refresh_mode {
get => _auto_refresh ? LadderMarketBreakLine.RefreshMode.Auto : LadderMarketBreakLine.RefreshMode.Manual;
set => auto_refresh = value == LadderMarketBreakLine.RefreshMode.Auto;
}
public ObservableCollection<ColorPaletteVM> palettes { get; }
public void assign_from(FairValueLine settings) {
if (settings is null) {
settings = new FairValueLine();
}
selected_security_key = settings.security_key;
selected_price_type = settings.price_type;
var display = settings.display ?? new LadderMarketBreakLine();
show = display.show;
color = display.color.rgb_color();
refresh_mode = display.refresh_mode;
}
public FairValueLine create() {
return new FairValueLine {
security_key = selected_security_key,
price_type = _selected_price_type,
display = new LadderMarketBreakLine {
color = color.rgb_color(),
show = show,
refresh_mode = refresh_mode
}
};
}
}
}
@@ -0,0 +1,121 @@
using mbts.co2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace mbts.spark {
class FairValueLineVM {
NativePriceSize _bid;
NativePriceSize _ask;
double _settlement;
double _frozen;
ulong _last_update;
public FairValueLineVM(uint sec_id) {
this.sec_id = sec_id;
}
public FairValueLineVM(FairValueLine fvl, SecurityMD? sec_md = null) {
sec_id = fvl.security_key;
price_type = fvl.price_type;
settings = fvl.display;
this.sec_md = sec_md;
}
public FairValueLine.PriceType price_type { get; private set; }
public double price { get; private set; }
public double price_view {
get {
if (!settings.show) {
return double.NaN;
}
switch (settings.refresh_mode) {
case LadderMarketBreakLine.RefreshMode.Auto:
return price;
case LadderMarketBreakLine.RefreshMode.Manual:
return frozen;
default: return double.NaN;
}
}
}
public double frozen {
get {
if (double.IsNaN(_frozen)) {
_frozen = price;
}
return _frozen;
}
}
public Color color { get; set; }
public uint sec_id { get; }
SecurityMD? _sec_md;
public SecurityMD? sec_md {
get => _sec_md;
set {
if (_sec_md == value) {
return;
}
if (value.HasValue && value.Value.security.Id != sec_id) {
throw new ArgumentException("'sec_md' security id must be equal to 'sec_id'", nameof(sec_md));
}
_sec_md = value;
refresh(true);
}
}
public LadderMarketBreakLine settings { get; set; } = new LadderMarketBreakLine();
public void load(LadderMarketBreakLine settings, FairValueLine.PriceType price_type) {
this.settings = settings;
if (this.price_type != price_type) {
this.price_type = price_type;
refresh(true);
}
}
public void refresh(bool update_frozen) {
if (sec_md.HasValue) {
var old_settlement = _settlement;
_settlement = sec_md.Value.md.settlement_price;
if (sec_md.Value.md.get_bbo(ref _last_update, out _bid, out _ask) || _settlement != old_settlement) {
price = _read_price();
}
} else {
price = double.NaN;
}
if (update_frozen) {
_frozen = price;
}
}
double _read_price() {
if (!_sec_md.HasValue) {
return double.NaN;
}
var bid = _bid.price;
var ask = _ask.price;
switch (price_type) {
case FairValueLine.PriceType.Bid:
return LadderPriceLineViewAdapter.bid_line(
_bid.price, _ask.price, _settlement, sec_md.Value.security.ContractSpec);
case FairValueLine.PriceType.Ask:
return _ask.is_nan ? double.NaN : _ask.price;
case FairValueLine.PriceType.Mid:
if (_bid.is_nan) {
if (_ask.is_nan) {
return double.NaN;
}
return _ask.price;
}
if (_ask.is_nan) {
return _bid.price;
}
return (_bid.price + _ask.price) / 2;
default: return double.NaN;
}
}
}
}
@@ -1,12 +1,16 @@
using bts.api;
using bts.api.orderman;
using bts.shell.orderbook;
using bts.utils;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
@@ -51,9 +55,9 @@ namespace mbts.spark {
public event Action piq_changed;
LadderModel model => _owner.model;
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)>();
ImmutableDictionary<uint, OrderSecurity> _orders = ImmutableDictionary<uint, OrderSecurity>.Empty;
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoData?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
public LadderOrderVM(ImmutableDictionary<uint, OrderSecurity> orders, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
_owner = owner;
_bus = bus;
_bs = bs;
@@ -77,8 +81,7 @@ namespace mbts.spark {
double get_font_size(LadderSettings s) => s.orders_column.font_size;
Brush get_foreground(LadderSettings s) => s.orders_column.foreground.rgb_brush();
double get_row_height(LadderSettings s) => s.row_height;
//
add_order(op);
_orders = orders;
_dispose = Disposable.Create(
_bs.Subscribe(badge => {
@@ -143,7 +146,7 @@ namespace mbts.spark {
string candidate = string.Empty;
if (a.HasValue) {
var algo_name = a.Value.name;
var words = algo_name.Split(delim);
var words = algo_name.Split(delim, StringSplitOptions.RemoveEmptyEntries);
if (words.Length == 1) {
var single_word = words[0];
if (single_word.Length > 1) {
@@ -152,7 +155,7 @@ namespace mbts.spark {
candidate = single_word;
}
} else {
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
candidate = string.Join(string.Empty, words.Take(2).Select(w => w.Substring(0, 1)));
}
}
return candidate.ToUpper();
@@ -169,45 +172,11 @@ namespace mbts.spark {
}
private void _set_badge_text() {
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style);
var fmt = MDDisplayHelper.get_value_formatter(_owner.display_quantity_divisor);
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style, fmt);
RaisePropertyChanged(nameof(text_lines));
}
/// <summary>
/// Add Order performs an add the first time it's called via the constructor. When it's called, directly
/// it really serves as an update/replace. Also note that since the min_piq is a separate piece of state representing
/// the lowest piq of the orders the badge represents, we must reevaluate if that has changed on said update.
/// </summary>
public void add_order((OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta) new_op) {
_orders[new_op.order.Key] = new_op;
// reevaluate min piq if this order's piq is less
if (new_op.p.HasValue) {
// if this lovm only represents 1 order and the piq is now different, regardless of what it is that's our piq
if (_orders.Count == 1 && new_op.p.Value != min_piq) {
min_piq = new_op.p.Value;
} else if (new_op.p.Value < min_piq) {
min_piq = new_op.p.Value;
}
}
}
public void remove_order(uint order_key) {
_orders.Remove(order_key);
// on a remove, potentially the order w/ the lowest piq has been removed, so
// if the min() is now > than the current min_piq, update the min_piq
var prospective_piq = _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
if (prospective_piq > min_piq) {
min_piq = prospective_piq;
}
}
//public void update(OrderRecordImmutable neworder) {
// Log.assert(neworder.Key == id, $"OrderVM.update was called with an order with a different Key({neworder.Key}) from original({id})");
// using (detect_property_changes()) {
// _init(neworder);
// }
//}
public uint security_key => _owner.security.Id;
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
public uint? min_piq {
@@ -259,9 +228,13 @@ namespace mbts.spark {
public CustomOrderDefinition.CustomOneofCase algo_type { get => _algo_type.Value; private set => _algo_type.Value = value; }
public string algo_studio_abbreviation { get => _algo_studio_abbreviation.Value; private set => _algo_studio_abbreviation.Value = value; }
public IReadOnlyList<uint> order_keys => _orders.Keys.ToReadOnlyList();
public IEnumerable<OrderRecordImmutable> root_orders => _root_nodes();
public void sync_orders(ImmutableDictionary<uint, OrderSecurity> new_orders) {
_orders = new_orders;
min_piq = new_orders.Count == 0 ? null : new_orders.Values.Select(o => o.piq).Min();
}
void _cancel_order() {
foreach (var order in _root_nodes()) {
model.order_gateway.cancel_order(order).ignore();
@@ -294,14 +267,16 @@ namespace mbts.spark {
// I checked with Eric and he guesses that what is happening is that the Popup is throwing away the grid, but the binding doesn't remove the columns from that grid
//
// I think returning a new OrderbookVM is okay at the moment since this Popup window will not be saving any state at this time
var price_filter = new filters.DoubleMinMaxState();
price_filter.minimum.value = real_price;
price_filter.maximum.value = real_price;
var ticks = _owner.spec.full_ticks_from_zero(real_price);
if (!ticks.HasValue) {
// this is an unexpected code path, but not necessarily a reason to crash, so log a warning and return null
return null;
}
var min_price = _owner.spec.tickup(0, ticks.Value);
var max_price = _owner.spec.tickup(min_price, 1);
var state = new bts.shell.orderbook.OrderBookStartupData(
new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_owner.security),
price = price_filter,
security_criteria = SecurityCriteria.from_securities(_owner.security),
statuses = new[] { OrderStatus.Working },
order_types = AlgoID.exchange_algo_keys
},
@@ -309,7 +284,8 @@ namespace mbts.spark {
use_defaults: false
);
var settings_man = new WindowSettingsManager<OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true);
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true,
global_filter: o => o.order.order.ui_price() >= min_price - MathUtils.epsilon && o.order.order.ui_price() <= max_price - MathUtils.epsilon);
_owner.settings.Subscribe(s => settings_man.instance_settings = settings_man.instance_settings.with(s.mini_ob.value));
obvm.columns.Insert(0, new OrderBookVM.AttachDetachColumn(typeof(MiniOrderBookView)));
obvm.columns.Insert(0, new OrderBookVM.CommitColumn());
@@ -0,0 +1,116 @@
using bts.proto;
using mbts.ui;
using mbts.ui.shell;
using System.Windows;
using System.Windows.Media;
using mbts.ui.utility.text;
using FontFamily = System.Windows.Media.FontFamily;
namespace mbts.spark {
class LadderPanelPricePointVM : ViewModelBase, IValueFactoryVM<LadderPanelPricePointSettings.ItemSettings> {
Color _background_color = Colors.Transparent, _font_color = Colors.Black;
FontFamily _font_family = TextUtils.StandardFontFamily;
double _font_size = FontSizes.Small;
FontWeight _font_weight = FontWeights.Bold;
bool _show = true, _show_label = true;
public Color background_color { get => _background_color; set => Set(ref _background_color, value); }
public Color font_color { get => _font_color; set => Set(ref _font_color, value); }
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
public bool show {
get => _show;
set {
if (Set(ref _show, value))
RaisePropertyChanged(nameof(actual_show_label));
}
}
public bool show_label {
get => _show_label;
set {
if (Set(ref _show_label, value))
RaisePropertyChanged(nameof(actual_show_label));
}
}
public bool actual_show_label => _show && _show_label;
public LadderPanelPricePointSettings.ItemSettings create() =>
new LadderPanelPricePointSettings.ItemSettings {
font = new FontParamsProto {
font_color = font_color.rgb_color(),
font_size = font_size,
typeface = new TypefaceProto(font_family.name_or_default(), font_weight.ToOpenTypeWeight())
},
background_color = background_color.rgb_color(),
show_label = show_label
};
public void load_from(LadderPanelPricePointSettings.ItemSettings value) {
if (value is null)
return;
load_from(value.font);
show_label = value.show_label;
background_color = value.background_color.argb_color();
}
protected void load_from(FontParamsProto font) {
if (font is null) {
font_weight = FontWeights.Bold;
font_size = FontSizes.Small;
show_label = true;
} else {
font_family = string.IsNullOrEmpty(font.font_family) ?
TextUtils.StandardFontFamily :
new FontFamily(font.font_family);
font_weight = FontWeight.FromOpenTypeWeight(font.font_weight);
font_size = font.font_size;
font_color = font.font_color.argb_color();
}
}
}
class LadderPanelRelativePricePointVM : LadderPanelPricePointVM, IValueFactoryVM<LadderPanelPricePointSettings.DaySettings> {
Color
_negative_font_color = Colors.Red,
_negative_background_color = Colors.Transparent,
_positive_font_color = Colors.Green,
_positive_background_color = Colors.Transparent;
// negative
public Color negative_font_color { get => _negative_font_color; set => Set(ref _negative_font_color, value); }
public Color negative_background_color { get => _negative_background_color; set => Set(ref _negative_background_color, value); }
// positive
public Color positive_font_color { get => _positive_font_color; set => Set(ref _positive_font_color, value); }
public Color positive_background_color { get => _positive_background_color; set => Set(ref _positive_background_color, value); }
public void load_from(LadderPanelPricePointSettings.DaySettings value) {
if (value is null)
return;
load_from(value.font);
show_label = value.show_label;
background_color = value.background_color.argb_color();
// negative
negative_font_color = value.negative_font_color.argb_color();
negative_background_color = value.negative_background_color.argb_color();
// positive
positive_font_color = value.positive_font_color.argb_color();
positive_background_color = value.positive_background_color.argb_color();
}
public new LadderPanelPricePointSettings.DaySettings create() {
var base_settings = base.create();
return new LadderPanelPricePointSettings.DaySettings {
font = base_settings.font,
show_label = base_settings.show_label,
background_color = base_settings.background_color,
// negative
negative_font_color = negative_font_color.rgb_color(),
negative_background_color = negative_background_color.rgb_color(),
// positive
positive_font_color = positive_font_color.rgb_color(),
positive_background_color = positive_background_color.rgb_color()
};
}
}
}
@@ -3,9 +3,11 @@ using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.persist.algo;
using bts.proto;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.color_picker;
@@ -14,6 +16,7 @@ using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
@@ -29,6 +32,10 @@ using TextUtils = mbts.ui.utility.text.TextUtils;
namespace mbts.spark {
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
public static Type PanelPricePointType => typeof(PanelPricePointVM);
public static Type PanelRelativePricePointType => typeof(PanelRelativePricePointVM);
public static Type PanelVisibilityType => typeof(OrderEntryPanelItemVisibilityVM);
readonly Property<LadderSettings.BadgeStyle> _badge_style;
readonly Property<uint> _alternate_order_size, _max_clip;
readonly DelegateProperty<double> _row_height;
@@ -38,13 +45,19 @@ namespace mbts.spark {
readonly ObservableKeyedCollection<AccountTuple?, uint> _accounts = new ObservableKeyedCollection<AccountTuple?, uint>(AccountTuple.nullable_key_fn);
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
readonly OrdersModel _model;
readonly SecurityImmutable _security;
IReadOnlyDictionary<uint, SecurityMD> _security_map =
DictionaryHelper.empty<uint, SecurityMD>();
uint? _desired_account;
AlgoID? _desired_algo;
TimeInForce _default_time_in_force;
LadderSettings.NetChangeTerms _price_format;
LadderSettings.PriceDisplayFormat _price_display_format;
double _order_entry_panel_width, _zoom_factor;
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip;
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip, _enforce_max_settlement_cross_ticks;
int _max_settlement_cross_ticks;
uint _ticks_per_row;
LadderSettings _current;
LadderColumnSettingsVM
_orders = new OrdersColumnSettingsVM(),
@@ -58,17 +71,11 @@ namespace mbts.spark {
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
_ask_mkt_line = new LadderMarketBreakLineVM("Ask"),
_mid_mkt_line = new LadderMarketBreakLineVM("Mid"),
_session_low_mkt_line = new LadderMarketBreakLineVM("Session Low", 2, 1),
_session_high_mkt_line = new LadderMarketBreakLineVM("Session High", 2, 1, false),
_session_range_fill = new LadderMarketBreakLineVM("Session Range", 0, 0, false);
_session_low_mkt_line = new LadderMarketBreakLineVM("Session Low", 2, 1, can_edit_refresh_mode: false),
_session_high_mkt_line = new LadderMarketBreakLineVM("Session High", 2, 1, false, can_edit_refresh_mode: false),
_session_range_fill = new LadderMarketBreakLineVM("Session Range", 0, 0, false, can_edit_refresh_mode: false);
OrderEntryPanelItemVisibilityVM
_zoom_slider_visbility = new OrderEntryPanelItemVisibilityVM("Zoom Slider"),
_day_visibility = new OrderEntryPanelItemVisibilityVM("Day Change"),
_high_visibility = new OrderEntryPanelItemVisibilityVM("High"),
_low_visibility = new OrderEntryPanelItemVisibilityVM("Low"),
_open_visibility = new OrderEntryPanelItemVisibilityVM("Open"),
_settle_visibility = new OrderEntryPanelItemVisibilityVM("Settle"),
_volume_visibility = new OrderEntryPanelItemVisibilityVM("Volume"),
_account_visibility = new OrderEntryPanelItemVisibilityVM("Account"),
_tif_visibility = new OrderEntryPanelItemVisibilityVM("Time In Force"),
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
@@ -82,10 +89,18 @@ namespace mbts.spark {
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All"),
_status_message_visibility = new OrderEntryPanelItemVisibilityVM("Status Message");
PanelRelativePricePointVM
_day_change = new PanelRelativePricePointVM("Day Change");
PanelPricePointVM
_session_high = new PanelPricePointVM("High"),
_session_low = new PanelPricePointVM("Low"),
_open = new PanelPricePointVM("Open"),
_settlement = new PanelPricePointVM("Settle"),
_volume_panel_item = new PanelPricePointVM("Volume");
public LadderSettingsVM(OrdersModel model, SecurityImmutable security) {
_model = model;
is_synthetic = security.ContractCase == Security.ContractOneofCase.Synthetic;
_security = security;
favorite_algos_vm = new FavoriteAlgosVM(_algo_definitions);
size_presets = new ObservableEditableCollection<IMutableValueWrapperVM<uint>>(_create_size_preset_vm);
size_presets.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
@@ -107,29 +122,46 @@ namespace mbts.spark {
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
CustomSort = Comparer<AccountTuple>.Default.to_nullable().to_non_generic()
};
// cmds
tick_up_ticks_per_row_cmd = new DelegateCommand(increment_ticks_per_row);
tick_down_ticks_per_row_cmd = new DelegateCommand(decrement_ticks_per_row);
// update
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
mini_ob.result_changed += _update.execute;
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
_algo_definitions.AddRange(_model.get_permitted_algos(security).factory_types
.Select(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable)
.Select(AlgoData.from_custom_def)
.Select(algo_factory));
_algo_definitions.AddRange(_model.get_permitted_algos(security).dynamic_types
.Select(dyn_type => new CustomOrderDefinitionImmutable(new CustomOrderDefinition {
Dynamic = _model.dynamic_helper.create_def(dyn_type)
}))
.Select(AlgoData.from_custom_def)
.Select(algo_factory));
_dispose = new CompositeDisposable(
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
_try_select_desired_account();
}),
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, connect_favorite_algo),
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, observe_prop_changes),
_model.algo_model.definitions.OnDispatcher().Subscribe(update => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, algo_factory);
_try_select_desired_algo();
}),
ObservableAutoWrapper.connect_item_hooks(fair_value_line_settings_vm.items, item => {
_update.execute();
var prop_changes_sub = observe_prop_changes(item);
return Disposable.Create(_update.execute, prop_changes_sub.Dispose);
})
) { dispose_resources };
// local methods
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
void on_prop_changed(object sender, PropertyChangedEventArgs e) => _update.execute();
void on_favorite_algo_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => _update.execute();
IDisposable connect_favorite_algo(FavoriteAlgoItem item) {
IDisposable observe_prop_changes(INotifyPropertyChanged item) {
item.PropertyChanged += on_prop_changed;
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
}
@@ -142,7 +174,27 @@ namespace mbts.spark {
c.PropertyChanged -= on_prop_changed;
}
AccountTuple? account_factory(uint key, string name) => new AccountTuple(key, name);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, is_synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
void increment_ticks_per_row() {
var tpr = ticks_per_row;
for (uint i = 1; i < 100; i++) {
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security.ContractSpec, ticks_per_row + i);
if (new_tpr != tpr) {
ticks_per_row = new_tpr;
break;
}
}
}
void decrement_ticks_per_row() {
var tpr = ticks_per_row;
for (uint i = 1; i < tpr; i++) {
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security.ContractSpec, tpr - i);
if (new_tpr != tpr) {
ticks_per_row = new_tpr;
break;
}
}
}
}
public event Action result_changed;
@@ -165,6 +217,16 @@ namespace mbts.spark {
get => _alternate_order_size.Value;
set => _alternate_order_size.Value = value;
}
public uint ticks_per_row {
get => _ticks_per_row;
set => Set(ref _ticks_per_row, value, dependent_props: nameof(rounded_ticks_per_row));
}
public ICommand tick_up_ticks_per_row_cmd { get; }
public ICommand tick_down_ticks_per_row_cmd { get; }
// since settings profiles may be shared among ladders
// of different securities/tick sizes, the ticks per row setting
// may need to be rounded to a number that is a factor of $1.00
public uint? rounded_ticks_per_row => TickUtils.fit_ticks_per_row_to_dollars(_security.ContractSpec, ticks_per_row);
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
@@ -190,12 +252,22 @@ namespace mbts.spark {
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
public bool enforce_max_clip { get => _enforce_max_clip; set => Set(ref _enforce_max_clip, value); }
public uint max_clip { get => _max_clip.Value; set => _max_clip.Value = value; }
public bool enforce_max_settlement_cross_ticks { get => _enforce_max_settlement_cross_ticks; set => Set(ref _enforce_max_settlement_cross_ticks, value); }
public int max_settlement_cross_ticks { get => _max_settlement_cross_ticks; set => Set(ref _max_settlement_cross_ticks, value); }
// favorites
public FavoriteAlgosVM favorite_algos_vm { get; }
public double row_height => _row_height.Value;
public bool is_synthetic { get; }
public bool is_synthetic => _security.ContractCase == Security.ContractOneofCase.Synthetic;
public IReadOnlyDictionary<uint, SecurityMD> security_map {
get => _security_map;
set {
Set(ref _security_map, value);
fair_value_line_settings_vm.security_map = value;
}
}
public IEnumerable<LadderColumnSettingsVM> column_items {
get {
@@ -234,15 +306,18 @@ namespace mbts.spark {
}
}
public IEnumerable<OrderEntryPanelItemVisibilityVM> order_entry_panel_items {
public FairValueLineSettingsVM fair_value_line_settings_vm { get; } = new
FairValueLineSettingsVM();
public IEnumerable order_entry_panel_items {
get {
yield return _zoom_slider_visbility;
yield return _day_visibility;
yield return _high_visibility;
yield return _low_visibility;
yield return _open_visibility;
yield return _settle_visibility;
yield return _volume_visibility;
yield return _day_change;
yield return _session_high;
yield return _session_low;
yield return _open;
yield return _settlement;
yield return _volume_panel_item;
yield return _account_visibility;
yield return _tif_visibility;
yield return _order_type_visibility;
@@ -281,12 +356,12 @@ namespace mbts.spark {
yield return _session_range_fill;
// left panel
yield return _zoom_slider_visbility;
yield return _day_visibility;
yield return _high_visibility;
yield return _low_visibility;
yield return _open_visibility;
yield return _settle_visibility;
yield return _volume_visibility;
yield return _day_change;
yield return _session_high;
yield return _session_low;
yield return _open;
yield return _settlement;
yield return _volume_panel_item;
yield return _account_visibility;
yield return _tif_visibility;
yield return _order_type_visibility;
@@ -312,6 +387,7 @@ namespace mbts.spark {
_current = settings;
badge_style = settings.badge_style;
alternate_order_size = settings.alternate_order_size;
ticks_per_row = settings.ticks_per_row;
favorite_algos_vm.load_from(settings.favorite_algos);
size_display.load_from(settings.size_display);
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
@@ -331,6 +407,14 @@ namespace mbts.spark {
_session_low_mkt_line.assign_from(_current.session_low_line);
_session_high_mkt_line.assign_from(_current.session_high_line);
_session_range_fill.assign_from(_current.session_range_fill);
// fair value lines
fair_value_line_settings_vm.items.SetItems(
_current.fair_value_lines.Select(fvl => {
var fvl_vm = new FairValueLineItemSettingsVM();
fvl_vm.assign_from(fvl);
fvl_vm.selected_security_key = fvl.security_key;
return fvl_vm;
}));
// left-panel (defaults)
default_time_in_force = settings.default_time_in_force;
// - account
@@ -345,12 +429,18 @@ namespace mbts.spark {
zoom_factor = settings.zoom_factor;
// left-panel (show/hide)
_zoom_slider_visbility.show = settings.zoom_slider_visible;
_day_visibility.show = settings.day_visible;
_high_visibility.show = settings.high_visible;
_low_visibility.show = settings.low_visible;
_open_visibility.show = settings.open_visible;
_settle_visibility.show = settings.settle_visible;
_volume_visibility.show = settings.volume_visible;
_day_change.show = settings.day_visible;
_day_change.load_from(settings.panel_price_point_settings.day_change);
_session_high.show = settings.high_visible;
_session_high.load_from(settings.panel_price_point_settings.session_high);
_session_low.show = settings.low_visible;
_session_low.load_from(settings.panel_price_point_settings.session_low);
_open.show = settings.open_visible;
_open.load_from(settings.panel_price_point_settings.open);
_settlement.show = settings.settle_visible;
_settlement.load_from(settings.panel_price_point_settings.settlement);
_volume_panel_item.show = settings.volume_visible;
_volume_panel_item.load_from(settings.panel_price_point_settings.volume);
_account_visibility.show = settings.account_visible;
_tif_visibility.show = settings.tif_visible;
_order_type_visibility.show = settings.order_type_visible;
@@ -370,6 +460,10 @@ namespace mbts.spark {
show_warn_on_cross = settings.show_warn_on_cross;
enforce_max_clip = settings.enforce_max_clip;
max_clip = settings.max_clip;
if (settings.ladder_order_limits != null) {
enforce_max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.is_enabled;
max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.value;
}
}
}
@@ -389,6 +483,7 @@ namespace mbts.spark {
}
clone.badge_style = badge_style;
clone.alternate_order_size = alternate_order_size;
clone.ticks_per_row = ticks_per_row;
clone.favorite_algos = favorite_algos_vm.create();
clone.size_display = size_display.create();
clone.calculator_buttons = size_presets.Select(vm => (int)vm.value).ToArray();
@@ -408,6 +503,20 @@ namespace mbts.spark {
clone.session_low_line = _session_low_mkt_line.get_settings();
clone.session_high_line = _session_high_mkt_line.get_settings();
clone.session_range_fill = _session_range_fill.get_settings();
// fair value lines
clone.fair_value_lines = fair_value_line_settings_vm.items
.Select(fvl => fvl.create())
.Where(fvl => fvl.security_key != 0)
.ToList();
// left-panel price items
clone.panel_price_point_settings = new LadderPanelPricePointSettings {
day_change = _day_change.create(),
open = _open.create(),
session_low = _session_low.create(),
session_high = _session_high.create(),
volume = _volume_panel_item.create(),
settlement = _settlement.create(),
};
// left-panel (defaults)
clone.default_time_in_force = default_time_in_force;
// - account
@@ -422,12 +531,12 @@ namespace mbts.spark {
clone.order_entry_panel_initial_width = _order_entry_panel_width;
clone.zoom_factor = zoom_factor;
// left-panel (components)
clone.day_visible = _day_visibility.show;
clone.high_visible = _high_visibility.show;
clone.low_visible = _low_visibility.show;
clone.open_visible = _open_visibility.show;
clone.settle_visible = _settle_visibility.show;
clone.volume_visible = _volume_visibility.show;
clone.day_visible = _day_change.show;
clone.high_visible = _session_high.show;
clone.low_visible = _session_low.show;
clone.open_visible = _open.show;
clone.settle_visible = _settlement.show;
clone.volume_visible = _volume_panel_item.show;
clone.account_visible = _account_visibility.show;
clone.tif_visible = _tif_visibility.show;
clone.order_type_visible = _order_type_visibility.show;
@@ -448,6 +557,10 @@ namespace mbts.spark {
clone.show_warn_on_cross = show_warn_on_cross;
clone.enforce_max_clip = enforce_max_clip;
clone.max_clip = max_clip;
if (clone.ladder_order_limits == null)
clone.ladder_order_limits = new LadderOrderLimits();
clone.ladder_order_limits.max_settlement_cross_ticks =
new ToggleInt32Proto(max_settlement_cross_ticks, enforce_max_settlement_cross_ticks);
return clone;
}
@@ -547,8 +660,8 @@ namespace mbts.spark {
font_size = settings.font_size;
width = settings.width;
show = settings.show;
foreground = settings.foreground.rgb_color();
background = settings.background.rgb_color();
foreground = settings.foreground.argb_color();
background = settings.background.argb_color();
}
public LadderColumnSettings get_settings() {
@@ -558,8 +671,8 @@ namespace mbts.spark {
font_weight = font_weight.ToOpenTypeWeight(),
width = width,
show = show,
foreground = new BTSColor(foreground).ToUint(),
background = new BTSColor(background).ToUint()
foreground = foreground.rgb_color(),
background = background.rgb_color()
};
}
}
@@ -579,12 +692,14 @@ namespace mbts.spark {
public class LadderMarketBreakLineVM : ViewModelBase {
readonly Property<Color> _color;
readonly Property<bool> _show;
LadderMarketBreakLine.RefreshMode _refresh_mode;
public LadderMarketBreakLineVM(string name, int column_index = 0, int column_span = 5, bool show_session_range = true) {
public LadderMarketBreakLineVM(string name, int column_index = 0, int column_span = 5, bool show_session_range = true, bool can_edit_refresh_mode = true) {
this.name = name;
this.column_index = column_index;
this.column_span = column_span;
this.show_session_range = show_session_range;
this.can_edit_refresh_mode = can_edit_refresh_mode;
palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
_color = backing(nameof(color), Colors.Black);
_show = backing(nameof(show), false);
@@ -596,21 +711,25 @@ namespace mbts.spark {
public Color color { get => _color.Value; set => _color.Value = value; }
public bool show { get => _show.Value; set => _show.Value = value; }
public bool show_session_range { get; }
public bool can_edit_refresh_mode { get; }
public LadderMarketBreakLine.RefreshMode refresh_mode { get => _refresh_mode; set => Set(ref _refresh_mode, value); }
public ObservableCollection<ColorPaletteVM> palettes { get; }
public void assign_from(LadderMarketBreakLine settings) {
color = settings.color.rgb_color();
show = settings.show;
refresh_mode = settings.refresh_mode;
}
public LadderMarketBreakLine get_settings() =>
new LadderMarketBreakLine {
color = new BTSColor(color).ToUint(),
show = show
show = show,
refresh_mode = refresh_mode
};
}
public class OrderEntryPanelItemVisibilityVM : ViewModelBase {
public class OrderEntryPanelItemVisibilityVM : ViewModelBase, ITemplateSelectorKey {
readonly Property<bool> _show;
public OrderEntryPanelItemVisibilityVM(string name) {
@@ -621,6 +740,63 @@ namespace mbts.spark {
public string name { get; }
public bool show { get => _show.Value; set => _show.Value = value; }
string ITemplateSelectorKey.key => LadderSettingsPanelTemplateKeys.VisibilityItem;
}
public class PanelPricePointVM : LadderPanelPricePointVM, ITemplateSelectorKey {
ObservableCollection<ColorPaletteVM>
_font_color_palettes,
_background_color_palettes;
public PanelPricePointVM(string name) =>
this.name = name;
public string name { get; }
public ObservableCollection<ColorPaletteVM> font_color_palettes => _font_color_palettes ??
(_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
public ObservableCollection<ColorPaletteVM> background_color_palettes => _background_color_palettes ??
(_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
string ITemplateSelectorKey.key => LadderSettingsPanelTemplateKeys.PricePoint;
}
public class PanelRelativePricePointVM : LadderPanelRelativePricePointVM, ITemplateSelectorKey {
ObservableCollection<ColorPaletteVM>
_negative_font_color_palettes,
_negative_background_color_palettes,
_neutral_font_color_palettes,
_neutral_background_color_palettes,
_positive_font_color_palettes,
_positive_background_color_palettes;
public PanelRelativePricePointVM(string name) =>
this.name = name;
public string name { get; }
//negative
public ObservableCollection<ColorPaletteVM> negative_font_color_palettes => _negative_font_color_palettes ??
(_negative_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
public ObservableCollection<ColorPaletteVM> negative_background_color_palettes => _negative_background_color_palettes ??
(_negative_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
// neutral
public ObservableCollection<ColorPaletteVM> neutral_font_color_palettes => _neutral_font_color_palettes ??
(_neutral_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
public ObservableCollection<ColorPaletteVM> neutral_background_color_palettes => _neutral_background_color_palettes ??
(_neutral_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
// positive
public ObservableCollection<ColorPaletteVM> positive_font_color_palettes => _positive_font_color_palettes ??
(_positive_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
public ObservableCollection<ColorPaletteVM> positive_background_color_palettes => _positive_background_color_palettes ??
(_positive_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
string ITemplateSelectorKey.key => LadderSettingsPanelTemplateKeys.RelativePricePoint;
}
public class VolumeColumnVM : ViewModelBase {
@@ -1,13 +1,18 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.proto;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.co2;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.user.settings;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -26,15 +31,25 @@ namespace mbts.spark {
readonly IBus _bus;
readonly LadderModel _model;
readonly Property<double> _high, _low, _change, _open, _settlement, _left_panel_width, _zoom_factor;
//readonly Property<double> _window_width, _window_height;
readonly Property<double> _session_high, _session_low, _day_change, _open, _settlement, _left_panel_width, _zoom_factor;
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width, _piq_column_width;
readonly Property<ulong> _volume;
readonly Property<bool> _auto_clear;
readonly PropertyAutoWrapper<LadderSettings, bool> _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom, _show_status_message;
readonly PropertyAutoWrapper<LadderSettings, bool>
_show_account,
_show_tif,
_show_order_type,
_show_size_preset,
_show_auto_clear,
_show_clear,
_show_cancel_buys,
_show_cancel_sells,
_show_cancel_all,
_show_zoom,
_show_status_message;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly ObservableCollection<FairValueLine> _fair_value_line_inputs =
new ObservableCollection<FairValueLine>();
double
_position,
_day_position,
@@ -48,8 +63,11 @@ namespace mbts.spark {
ulong _last_trade_time = 0;
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
ImmutableSortedDictionary<double, LadderOrderVM> _order_price_index;
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
ContractSpecificationImmutable? _spec;
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
@@ -64,13 +82,16 @@ namespace mbts.spark {
_bus = bus;
_model = model;
current_settings = init;
_order_price_index = ImmutableSortedDictionary.Create<double, LadderOrderVM>(spec.price_comparer());
this.settings = settings;
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
display_quantity_divisor = model.order_gateway.security_auxiliary_model.get_display_quantity_divisor(model.security);
size_calculator.display_divisor = display_quantity_divisor;
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
this.order_properties_vm = order_properties_vm;
_high = backing(nameof(high), double.NaN);
_low = backing(nameof(low), double.NaN);
_change = backing(nameof(change), double.NaN);
_session_high = backing(nameof(session_high), double.NaN);
_session_low = backing(nameof(session_low), double.NaN);
_day_change = backing(nameof(day_change), double.NaN);
_open = backing(nameof(open), double.NaN);
_settlement = backing(nameof(settlement), double.NaN);
// init window-specific settings: these properties should not observe settings updates; they are independent from the settings dialog, so it doesn't make sense that the settings dialog should cause them to change.
@@ -101,12 +122,6 @@ namespace mbts.spark {
(_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background)).set,
(_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground)).set,
// bool visibility props
(_show_day = bool_prop(nameof(show_day), s => s.day_visible)).set,
(_show_high = bool_prop(nameof(show_high), s => s.high_visible)).set,
(_show_low = bool_prop(nameof(show_low), s => s.low_visible)).set,
(_show_open = bool_prop(nameof(show_open), s => s.open_visible)).set,
(_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible)).set,
(_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible)).set,
(_show_account = bool_prop(nameof(show_account), s => s.account_visible)).set,
(_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible)).set,
(_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible)).set,
@@ -119,6 +134,8 @@ namespace mbts.spark {
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
(_show_status_message = bool_prop(nameof(show_status_message), s => s.status_message_visible)).set,
s => {
// invalidate spec in case 'ticks per row' changed
_spec = null;
if (
Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) |
Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position)) |
@@ -136,8 +153,33 @@ namespace mbts.spark {
_piq_column_width.set(s.piq_column.width);
_volume_column_width.set(s.volume_column.general.width);
// left panel/zoom
day_change_style.load_from(s.panel_price_point_settings.day_change);
day_change_style.show = s.day_visible;
// open
open_style.load_from(s.panel_price_point_settings.open);
open_style.show = s.open_visible;
// session low
session_low_style.load_from(s.panel_price_point_settings.session_low);
session_low_style.show = s.low_visible;
// session high
session_high_style.load_from(s.panel_price_point_settings.session_high);
session_high_style.show = s.high_visible;
// settlement
settlement_style.load_from(s.panel_price_point_settings.settlement);
settlement_style.show = s.settle_visible;
// volume
volume_style.load_from(s.panel_price_point_settings.volume);
volume_style.show = s.volume_visible;
// left panel gen display
_left_panel_width.set(s.order_entry_panel_initial_width);
_zoom_factor.set(s.zoom_factor);
RaisePropertyChanged(nameof(day_change_font_color), nameof(day_change_background_color));
// fair value lines
var new_fvls = s.fair_value_lines
.DistinctOn(fvl => fvl.security_key)
.OrderBy(fvl => fvl.security_key)
.ToList();
_fair_value_line_inputs.sync_with(new_fvls);
}
};
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
@@ -156,71 +198,46 @@ namespace mbts.spark {
settings_command = new DelegateCommand(() => request_settings_dialog?.Invoke());
orders = new ObservableCollection<LadderOrderVM>();
_day_change.Changed += (o, n) => RaisePropertyChanged(nameof(day_change_font_color), nameof(day_change_background_color));
var orders_source = settings.Select(s => s.ticks_per_row).DistinctUntilChanged()
.SelectManyInSeries(tpr => {
var spec = TickUtils.multiply_tick_size(security.ContractSpec, tpr);
var price_comp = Comparer<double>.Create((a, b) =>
CompareUtils.nullable_compare(spec.full_ticks_from_zero(a), spec.full_ticks_from_zero(b)));
return _order_price_source(model.orders, price_comp, p => TickUtils.normalize_price(spec, p));
});
_dispose = Disposable.Create(
settings.OnDispatcher().Subscribe(settings_observer),
get_order_properties_editor_hooks(),
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
// we need to hook this up to model.orders which is filtered for just the security
model.orders.OnDispatcher().Subscribe(working => {
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_security_map),
ObservableAutoWrapper.connect(
_fair_value_line_inputs,
fair_value_lines,
fvl => new FairValueLineVM(fvl, _find_sec_md(fvl.security_key))),
orders_source.OnDispatcher().Subscribe(tup => {
// Here we manage N orders to 1 price slot in the ladder
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
_cache = working.state;
foreach (var os in _cache.Values) {
var wo = os.order;
var slot_price_target = wo.ui_price();
var order_type = wo.order_type();
var filled = wo.filled();
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
} else {
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, settings.Select(s => s.badge_style).OnDispatcher());
_slot_price_vs_orders[slot_price_target] = slot_vm;
}
// keep track of slot prices vs orders; we use this below to remove orders from the LadderOrderVMs
// that are not found in this callback run
if (!orders_used_in_this_update.TryGetValue(slot_price_target, out HashSet<uint> orders_at_level)) {
orders_at_level = new HashSet<uint>();
}
orders_at_level.Add(wo.Key);
orders_used_in_this_update[slot_price_target] = orders_at_level;
_cache = tup.map;
/// remove empty price slots (the vm will be disposed automatically when it is removed from <see cref="orders"/>)
var removed_prices = _slot_price_vs_orders.Keys.Except(tup.price_index.Keys).ToList();
foreach (var p in removed_prices)
_slot_price_vs_orders.Remove(p);
// add or update current price slots
foreach (var kv in tup.price_index) {
_slot_price_vs_orders.AddOrUpdate(kv.Key,
order_vm => order_vm.sync_orders(kv.Value),
() => new LadderOrderVM(kv.Value, kv.Key, this, _bus, settings.Select(s => s.badge_style).OnDispatcher()));
}
// remove any orders from the lovm that aren't in here
foreach(var kvp in orders_used_in_this_update) {
var slot_vm = _slot_price_vs_orders[kvp.Key];
foreach(var existing_order_key in slot_vm.order_keys) {
if (!kvp.Value.Contains(existing_order_key)) {
slot_vm.remove_order(existing_order_key);
}
}
}
// update dirty brushes and text
foreach (var lovm in _slot_price_vs_orders.Values) {
lovm.update();
}
// complete removal of slots that were not updated
// orders_used_in_this_update.Keys are the price levels in the update
foreach (var slot_price in _slot_price_vs_orders.Keys.ToList()) {
if (!orders_used_in_this_update.Keys.Contains(slot_price)) {
_slot_price_vs_orders.TryGetValue(slot_price, out LadderOrderVM removal_candidate);
if (removal_candidate != null) {
removal_candidate.Dispose();
}
_slot_price_vs_orders.Remove(slot_price);
}
}
// sync the collection
orders.sync_with(_slot_price_vs_orders.Values);
// update badge colors, text, etc.
foreach (var lovm in orders)
lovm.update();
}),
WorkLoop.Start($"Ladder '{security}'", _tick),
ObservableAutoWrapper.dispose_on_removed(orders, true),
Disposable.Create(() => _on_disposed_impl?.Invoke(true))
);
IDisposable get_order_properties_editor_hooks() {
@@ -253,11 +270,24 @@ namespace mbts.spark {
RaisePropertyChanged(nameof(position_display_text));
}));
}
void on_security_map(IReadOnlyDictionary<uint, SecurityMD> security_map) {
_security_map = security_map;
foreach (var fvl_vm in fair_value_lines) {
if (fvl_vm.sec_md.HasValue) {
continue;
}
fvl_vm.sec_md = _find_sec_md(fvl_vm.sec_id);
}
}
}
public IBus bus => _bus;
public event Action on_tick, request_settings_dialog, settings_invalidated;
public event Action
on_tick,
request_settings_dialog,
request_recenter,
settings_invalidated;
public MDConsumerHandle md => _model.md;
// we return only entries who have piqs and whose piqs are not uint.Max
@@ -292,16 +322,34 @@ namespace mbts.spark {
remove => _on_disposed_impl -= value;
}
public SecurityImmutable security => _model.security;
public ContractSpecificationImmutable spec => _spec ?? (
_spec = TickUtils.multiply_tick_size(security.ContractSpec, current_settings?.ticks_per_row ?? 1)).Value;
public uint display_quantity_divisor { get; }
public LadderSettings current_settings { get; private set; }
public double high => _high.Value;
public double low => _low.Value;
public double change => _change.Value;
// left panel values
public double session_high => _session_high.Value;
public double session_low => _session_low.Value;
public double day_change => _day_change.Value;
public double open => _open.Value;
public double settlement => _settlement.Value;
public double position => _position;
public double day_position => _day_position;
public double raw_day_position => _raw_day_position;
public ulong volume => _volume.Value;
// left panel styles
public LadderPanelRelativePricePointVM day_change_style { get; } = new LadderPanelRelativePricePointVM();
public Color day_change_font_color => day_change > 0 ?
day_change_style.positive_font_color :
(day_change < 0 ? day_change_style.negative_font_color : day_change_style.font_color);
public Color day_change_background_color => day_change > 0 ?
day_change_style.positive_background_color :
(day_change < 0 ? day_change_style.negative_background_color : day_change_style.background_color);
public LadderPanelPricePointVM open_style { get; } = new LadderPanelPricePointVM();
public LadderPanelPricePointVM settlement_style { get; } = new LadderPanelPricePointVM();
public LadderPanelPricePointVM session_high_style { get; } = new LadderPanelPricePointVM();
public LadderPanelPricePointVM session_low_style { get; } = new LadderPanelPricePointVM();
public LadderPanelPricePointVM volume_style { get; } = new LadderPanelPricePointVM();
//
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
/// Important: <see cref="order_properties_vm"/> can be null.
@@ -316,12 +364,6 @@ namespace mbts.spark {
public Brush bid_foreground => _bid_foreground.view;
public Brush ask_background => _ask_background.view;
public Brush ask_foreground => _ask_foreground.view;
public bool show_day => _show_day.view;
public bool show_high => _show_high.view;
public bool show_low => _show_low.view;
public bool show_open => _show_open.view;
public bool show_settle => _show_settle.view;
public bool show_volume => _show_volume.view;
public bool show_account => _show_account.view;
public bool show_tif => _show_tif.view;
public bool show_order_type => _show_order_type.view;
@@ -336,6 +378,11 @@ namespace mbts.spark {
public bool show_cancel_all => _show_cancel_all.view;
public bool show_zoom => _show_zoom.view;
public bool show_status_message => _show_status_message.view;
public TickDisplaySettings day_change_display_settings =>
TickDisplaySettings.Default.with(
pad_with_leading_zeros: false,
show_zero_integer: false);
// left panel scroll props
public double left_panel_view_port_height {
get => _left_panel_view_port_height;
@@ -369,18 +416,22 @@ namespace mbts.spark {
get {
string rv = null;
if (show_day_position)
rv = SparkUtils.fmt_position(day_position);
rv = SparkUtils.fmt_position(day_position / display_quantity_divisor);
if (show_raw_day_position) {
var raw_day_text = SparkUtils.fmt_position(raw_day_position);
var raw_day_text = SparkUtils.fmt_position(raw_day_position / display_quantity_divisor);
rv = rv == null ? raw_day_text : $"{rv} / {raw_day_text}";
}
if (show_account_position) {
var acc_text = SparkUtils.fmt_position(position);
rv = rv == null ? SparkUtils.fmt_position(position) : $"{rv} ({acc_text})";
var acc_text = SparkUtils.fmt_position(position / display_quantity_divisor);
rv = rv == null ? acc_text : $"{rv} ({acc_text})";
}
return rv ?? "";
}
}
public ObservableCollection<FairValueLineVM> fair_value_lines { get; } =
new ObservableCollection<FairValueLineVM>();
//public double window_height { get => _window_height.Value; set => _window_height.set(value); }
//public double window_width { get => _window_width.Value; set => _window_width.set(value); }
public uint orders_column_width { get => _orders_column_width.Value; set => _orders_column_width.set(value); }
@@ -390,7 +441,8 @@ namespace mbts.spark {
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
public uint actual_alternate_order_size =>
(current_settings?.alternate_order_size ?? 0) * Math.Max(1, display_quantity_divisor);
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
public bool enforce_max_clip_from_settings => current_settings.enforce_max_clip;
public uint max_clip_from_settings => current_settings.max_clip;
@@ -400,11 +452,11 @@ namespace mbts.spark {
double _max_left_panel_scroll_position =>
Math.Max(0, _left_panel_extent_height - _left_panel_view_port_height);
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
internal OrderRecordImmutable get_root_at_price(OrderSecurity op) {
var order = op.order;
double p = order.ui_price();
double p = _normalize_price(order.ui_price());
foreach (var o in lineage(order.ParentKey)) {
if (_equality.Equals(p, o.ui_price()))
if (_equality.Equals(p, _normalize_price(o.ui_price())))
order = o;
}
return order;
@@ -438,21 +490,41 @@ namespace mbts.spark {
if (order_properties_vm.selected_algo_definition != null)
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key);
}
rv.panel_price_point_settings = new LadderPanelPricePointSettings {
day_change = day_change_style.create(),
open = open_style.create(),
settlement = settlement_style.create(),
session_low = session_low_style.create(),
session_high = session_high_style.create(),
volume = volume_style.create()
};
return rv;
}
public void key_down(KeyEventArgs e) {
if (order_properties_vm == null)
return;
if (KeyboardExtensions.IsDown(Key.Space)) {
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
if (!KeyboardExtensions.IsCtrlDown()) return;
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
if (new_algo != null) {
order_properties_vm.selected_algo_definition = new_algo;
e.Handled = true;
switch (e.Key) {
case Key.Space:
if (KeyboardExtensions.IsShiftDown()) {
// only recenter this ladder
request_recenter?.Invoke();
} else {
// recenter all ladders
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
e.Handled = true;
break;
default:
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
if (KeyboardExtensions.IsCtrlDown()) {
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
if (new_algo != null) {
order_properties_vm.selected_algo_definition = new_algo;
e.Handled = true;
}
}
break;
}
}
@@ -469,11 +541,11 @@ namespace mbts.spark {
public Task send_order_async((double price, Side? side, MouseButton button) args) {
if (order_properties_vm != null && args.button != MouseButton.Middle) {
var size_from_alternate = args.button == MouseButton.Right;
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || current_settings.alternate_order_size > 0)) {
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || actual_alternate_order_size > 0)) {
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
} else {
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
uint size = size_from_alternate ? actual_alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
// enforce max clip
if (enforce_max_clip_from_settings && size > max_clip_from_settings) {
size = max_clip_from_settings;
@@ -486,6 +558,10 @@ namespace mbts.spark {
}
public async Task send_order_async(Side side, uint size, double price) {
if (!_validate_price(side, price, out var invalid_price_msg)) {
status_update(new Activity { security = security, message = invalid_price_msg, category = ActivityLogCategory.order });
return;
}
var order = order_properties_vm.create(side);
if (order.CustomDef != null) {
if (order.CustomDef.Stop != null) {
@@ -506,7 +582,7 @@ namespace mbts.spark {
if (fav_algo != null) {
if (_favorite_algo_map.TryGetValue(fav_algo.key, out var d)) {
if (!d.flags.HasFlag(LadderFavoriteAlgoFlags.Sticky) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
var key = new AlgoKey(OrderDataHelper.get_default_algo(security.ContractCase));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
@@ -517,20 +593,95 @@ namespace mbts.spark {
}
}
static IObservable<(ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderSecurity>> price_index, IReadOnlyDictionary<uint, OrderSecurity> map)> _order_price_source(
IObservable<TableUpdate<OrderSecurity, uint>> source,
IComparer<double> price_comp,
Func<double, double> normalize_price) {
//
return Observable.Create<(ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderSecurity>>, IReadOnlyDictionary<uint, OrderSecurity>)>(o => {
var state = ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderSecurity>>(price_comp);
var sub = source.Subscribe(table => {
var bd = state.ToBuilder();
// removes
var removes = table.removed.Concat(table.changes.Where(price_changed).Old());
foreach (var old_order in removes) {
var price = get_price(old_order);
if (bd.TryGetValue(price, out var index)) {
var new_index = index.Remove(old_order.order.Key);
if (new_index.Count == 0) {
bd.Remove(price);
} else {
bd[price] = new_index;
}
}
}
// changes
var changes = table.changes.Where(ch => !price_changed(ch)).New();
foreach (var changed_order in changes) {
var price = get_price(changed_order);
bd[price] = bd[price].SetItem(changed_order.order.Key, changed_order);
}
// adds
var adds = table.added.Concat(table.changes.Where(price_changed).New());
foreach (var new_order in adds) {
var price = get_price(new_order);
if (bd.TryGetValue(price, out var index)) {
bd[price] = index.Add(new_order.order.Key, new_order);
} else {
bd[price] = ImmutableDictionary.CreateRange(
new[] { new KeyValuePair<uint, OrderSecurity>(new_order.order.Key, new_order) });
}
}
o.OnNext((state = bd.ToImmutable(), table.state));
});
return sub;
});
bool price_changed(Change<OrderSecurity> ch) =>
price_comp.Compare(ch.New.order.ui_price(), ch.Old.order.ui_price()) != 0;
double get_price(OrderSecurity order) {
var p = order.order.ui_price();
if (!(normalize_price is null))
p = normalize_price(p);
return p;
}
}
double _normalize_price(double price) =>
TickUtils.normalize_price(spec, price);
bool _validate_price(Side side, double price, out string msg) {
msg = null;
if (!price.is_real()) {
msg = "not a real number";
} else if (settlement.is_real()) {
var ticks_from_settle = security.ContractSpec.tickdiff(price, settlement);
if (ticks_from_settle.HasValue) {
var max_cross = (current_settings?.ladder_order_limits?.max_settlement_cross_ticks)?.as_nullable();
if (max_cross.HasValue) {
if (side == Side.Sell ? ticks_from_settle < (-1 * max_cross.Value) : ticks_from_settle > max_cross.Value)
msg = $"exceeds settle cross ticks limit ({ticks_from_settle.Value})";
}
}
}
return msg == null;
}
void _update_header() {
bool last_updated = md.get_last_trade(ref _last_trade_time, out NativePriceSize last, out DateTime time, out ulong volume);
if (_misc_time != _model.md.misc_timestamp || last_updated) {
_high.set(md.session_high);
_low.set(md.session_low);
_session_high.set(md.session_high);
_session_low.set(md.session_low);
_open.set(md.open_price);
_settlement.set(md.settlement_price);
if (!double.IsNaN(last.price)) {
_change.set(last.price - md.settlement_price);
_day_change.set(last.price - md.settlement_price);
} else {
ulong unused = 0;
_model.md.get_bbo(ref unused, out NativePriceSize bid, out NativePriceSize ask);
double mid = TickUtils.mid_tick(security.ContractSpec, bid.price, ask.price);
_change.set(mid - md.settlement_price);
_day_change.set(mid - md.settlement_price);
}
_volume.set(volume);
_misc_time = _model.md.misc_timestamp;
@@ -541,7 +692,7 @@ namespace mbts.spark {
void _show_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
security_criteria = SecurityCriteria.from_securities(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
@@ -555,5 +706,12 @@ namespace mbts.spark {
_update_header();
on_tick?.Invoke();
}
SecurityMD? _find_sec_md(uint sec_key) {
if (_security_map.TryGetValue(sec_key, out var sec_md)) {
return sec_md;
}
return null;
}
}
}
@@ -46,12 +46,12 @@ namespace mbts.spark {
void _reset_ladder_vm() {
if (_value == null && !_security_md.HasValue)
return;
Disposable.replace(ref _value,
_security_md.HasValue ?
LadderVM.create_readonly(_bus,
new LadderModel(security_md.Value, _model, _model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
_value?.Dispose();
_value = _security_md.HasValue ?
LadderVM.create_readonly(_bus,
new LadderModel(security_md.Value, _model, _model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null;
RaisePropertyChanged(nameof(value));
}
}
@@ -0,0 +1,127 @@
<UserControl
x:Class="mbts.ui.spark.FairValueLineSettingsView"
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:mbts="clr-namespace:mbts"
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:stg="clr-namespace:mbts.ui.settings"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<Thickness x:Key="CellPadding">10,6</Thickness>
<Style x:Key="DisabledColumnHeader" TargetType="GridViewColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewColumnHeader">
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{x:Static u:BTSColors.header_brush}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<c:DIYGrid
Grid.Row="0"
Grid.IsSharedSizeScope="True"
BorderBrush="Black"
BorderThickness="1"
Rows="{Binding items}"
RowsHorizontalScrollBarVisibility="Collapsed"
SelectionMode="Single"
>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<usp:FairValueLineSettingsColumn key="Delete" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="23" is_visible="True"/>
<usp:FairValueLineSettingsColumn key="Security" Header="Security" Width="130" is_visible="True"/>
<usp:FairValueLineSettingsColumn key="Show" Header="Show" Width="40" is_visible="True"/>
<usp:FairValueLineSettingsColumn key="AutoRefresh" Header="Auto Refresh" Width="80" is_visible="True"/>
<usp:FairValueLineSettingsColumn key="Color" Header="Color" Width="80" is_visible="True"/>
<usp:FairValueLineSettingsColumn key="PriceType" Header="Type" Width="80" is_visible="True"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate DataType="{x:Type sp:FairValueLineItemSettingsVM}">
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<!--Delete-->
<Button
c:DIYGrid.ColumnKey="Delete"
HorizontalAlignment="Center"
Command="{Binding DataContext.remove_item_cmd,
RelativeSource={RelativeSource AncestorType=usp:FairValueLineSettingsView}}"
CommandParameter="{Binding}"
Style="{StaticResource RoundCloseButton}"
ToolTip="Delete"
/>
<!--Security-->
<c:ContractPickerPopup
c:DIYGrid.ColumnKey="Security"
Grid.Column="0"
Margin="{StaticResource CellPadding}"
CloseOnOK="True"
CloseOnCancel="True"
IsEditMode="False"
MaxWidth="180"
SecurityMap="{Binding DataContext.security_map,
RelativeSource={RelativeSource AncestorType=usp:FairValueLineSettingsView}}"
SelectedSecurityKey="{Binding selected_security_key}"
Header="{Binding SelectedSecurity,
RelativeSource={RelativeSource Self},
Converter={StaticResource SelectedSecurityTextConverter}}"
/>
<!--Show-->
<CheckBox
c:DIYGrid.ColumnKey="Show"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding show}"
/>
<!--AutoRefresh-->
<CheckBox
c:DIYGrid.ColumnKey="AutoRefresh"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding auto_refresh}"
/>
<!--Color-->
<c:ColorPickerPopup
c:DIYGrid.ColumnKey="Color"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Margin="{StaticResource CellPadding}"
Palettes="{Binding color_palettes}"
SelectedColor="{Binding color}"
/>
<!--PriceType-->
<ComboBox
c:DIYGrid.ColumnKey="PriceType"
Margin="{StaticResource CellPadding}"
ItemsSource="{Binding price_types}"
SelectedItem="{Binding selected_price_type}"
/>
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="3" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
<Rectangle Grid.Column="4" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<TextBlock Grid.Row="1" HorizontalAlignment="Left" Margin="4,8">
<Hyperlink Command="{Binding add_item_cmd}">
Click to Add Item
</Hyperlink>
</TextBlock>
</Grid>
</UserControl>
@@ -0,0 +1,40 @@
using mbts.ui.controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for FairValueLineSettingsView.xaml
/// </summary>
partial class FairValueLineSettingsView : UserControl {
public FairValueLineSettingsView() =>
InitializeComponent();
public enum Columns {
None = 0,
Delete,
Security,
Show,
AutoRefresh,
Color,
PriceType
}
}
class FairValueLineSettingsColumn : DIYGrid.ColumnBase<FairValueLineSettingsView.Columns> {
protected override void on_key_initialized(FairValueLineSettingsView.Columns key) =>
id = key.ToString();
}
}
@@ -29,6 +29,10 @@ using System.Windows.Media.Animation;
namespace mbts.spark {
partial class LadderGridHost : Control {
const int _min_row_count = 501, _max_row_count = 2001;
static readonly bool _show_scrollbar;
static readonly bool _lighten_market_cells;
static readonly Color? _bid_market_cell_background;
static readonly Color? _ask_market_cell_background;
int _row_count = _min_row_count;
@@ -53,7 +57,7 @@ namespace mbts.spark {
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container =
new RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>>();
IDisposable _settings_sub, _volume_sub, _tick_sub;
IDisposable _settings_sub, _volume_sub, _tick_sub, _fair_value_lines_sub, _vm_sub;
ScrollViewer _scrollViewer;
LadderSettings _settings;
LadderVM _vm;
@@ -66,7 +70,8 @@ namespace mbts.spark {
double _last_origin_price;
MarketBreakLines _mkt_break_lines;
NativePriceSize _last_trade;
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
Func<uint, string> _fmt_mkt_quantity_impl = v => v.ToString();
int _old_tooltip_row = int.MaxValue;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
@@ -74,6 +79,19 @@ namespace mbts.spark {
ToolTip tooltip_view = new ToolTip();
IDisposable _parameterized_order_popup;
static LadderGridHost() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
_show_scrollbar = knobs.show_scrollbar;
_lighten_market_cells = knobs.lighten_market_cells;
// For market cell backgrounds, 0 means null. Note that the full
// range of colors can still be expressed, as zero is merely one of
// 2^24 possible representations of 'transparent.'
if (knobs.bid_market_cell_background != 0)
_bid_market_cell_background = knobs.bid_market_cell_background.argb_color();
if (knobs.ask_market_cell_background != 0)
_ask_market_cell_background = knobs.ask_market_cell_background.argb_color();
}
public LadderGridHost() {
AllowDrop = true;
UseLayoutRounding = true;
@@ -81,6 +99,8 @@ namespace mbts.spark {
_ordershost = new LadderOrdersHost(this) { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Stretch };
var scrollViewerFactory = new FrameworkElementFactory(typeof(ScrollViewer));
scrollViewerFactory.SetValue(FocusableProperty, false);
if (!_show_scrollbar)
scrollViewerFactory.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Hidden);
Template = new ControlTemplate() {
VisualTree = scrollViewerFactory
};
@@ -295,6 +315,18 @@ namespace mbts.spark {
Column[] columns { get; set; } = new Column[0];
IEnumerable<Column> _visible_columns => columns.Where(c => c.settings.show);
ContractSpecificationImmutable? _spec_backing;
ContractSpecificationImmutable _spec {
get {
if (_spec_backing.HasValue) {
return _spec_backing.Value;
} else if (_vm != null) {
_spec_backing = TickUtils.multiply_tick_size(security.ContractSpec, _settings?.ticks_per_row ?? 1);
return _spec_backing.Value;
}
return SecurityDataHelper.Defaults.ContractSpecificationImmutable;
}
}
public void drag_out_of_bounds() =>
_tactile_adorner?.drag_out_of_bounds();
@@ -302,7 +334,7 @@ namespace mbts.spark {
public void scroll_to_price(double price) {
if (_scrollViewer == null || _price_list.is_null_or_empty())
return;
if (security.ContractSpec.tickdiff(_price_list[0], price) is int target_row) {
if (_spec.tickdiff(_price_list[0], price) is int target_row) {
int visible_row_count = (int)(_scrollViewer.ActualHeight / _settings.row_height);
int curr_row = visible_row_count + (int)(_scrollViewer.VerticalOffset / _settings.row_height);
if ((target_row > curr_row && target_row < _row_count) || (target_row < (curr_row - visible_row_count) && target_row >= 0))
@@ -352,7 +384,9 @@ namespace mbts.spark {
return;
}
} else if (_vm != null) {
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
uint size = e.ChangedButton == MouseButton.Right ?
_vm.actual_alternate_order_size :
_vm.size_calculator.value ?? 0;
// does the size violate the local max clip
if (_vm.enforce_max_clip_from_settings && size > _vm.max_clip_from_settings) {
@@ -468,7 +502,7 @@ namespace mbts.spark {
security_id: _vm.model.security.Id,
price_level: price_for_point(point)
);
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query, fmt_mbo_qty);
// Load the first set of orders so we know if there are any before showing the popup
var result = await model.load_orders();
if (result.success && result.value.orders.Count > 0) {
@@ -489,6 +523,8 @@ namespace mbts.spark {
};
view.RequestClose += (o, _) => popup.IsOpen = false;
}
string fmt_mbo_qty(int value) =>
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
@@ -528,20 +564,50 @@ namespace mbts.spark {
_price_list == null ? double.NaN : _price_list[row];
uint? _bid_size_from_row(int row) =>
_size_from_row(row, _price_list, _bids, _vm.security.ContractSpec);
_size_from_row(row, _price_list, _bids, _spec);
uint? _ask_size_from_row(int row) =>
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
_size_from_row(row, _price_list, _asks, _spec);
uint _volume_from_row(int row) {
if (_price_list != null) {
var price = _price_list[row];
var vapls = _volume_container.read_new_value();
if (vapls != null) {
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
}
const int max_tries = 3;
var errors = new List<string>(0);
int num_tries = 0;
for (; num_tries < max_tries; num_tries++) {
try {
var rv = impl();
if (num_tries > 0) {
log_errors(true);
}
return rv;
} catch (Exception ex) {
errors.Add(ex.ToString());
}
}
log_errors(false);
return 0;
void log_errors(bool succeeded) {
// Only log distinct errors to avoid useless noise.
// It's likely that the exception will always be the same (IndexOutOfRange),
// however, this bug is not fully understood, so assume nothing.
var error_msg = string.Join("; ", errors.Distinct());
if (succeeded) {
Log.Ui.error($"Ladder '{security}' got volume for row at index '{row}' after {num_tries + 1} attempts (price_list array length is {_price_list.Length}). The following exceptions were observed: {error_msg}");
} else {
Log.Ui.error($"Ladder '{security}' failed to get volume for row at index '{row}' after {max_tries} attempts (price_list array length is {_price_list.Length}): {error_msg}");
}
}
uint impl() {
if (_price_list.SafeTryGetAt(row, out var price)) {
var vapls = _volume_container.read_new_value();
if (vapls != null) {
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
}
}
return 0u;
}
return 0u;
}
@@ -587,7 +653,9 @@ namespace mbts.spark {
if (_price_list != null) {
switch (column.column_type) {
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
case ColumnType.Bid: render_market_col(_bids); break;
case ColumnType.Bid:
render_market_col(_bids, _bid_market_cell_background);
break;
case ColumnType.Price:
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
LadderRender.render_prices(
@@ -599,7 +667,7 @@ namespace mbts.spark {
_vm.md.settlement_price,
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
column.settings,
security.ContractSpec);
_spec);
} else {
LadderRender.render_prices(
column.visual_tup.visual,
@@ -608,11 +676,13 @@ namespace mbts.spark {
_settings.row_height,
_price_list,
column.settings,
security.ContractSpec,
_spec,
_settings.price_display_format);
}
break;
case ColumnType.Ask: render_market_col(_asks); break;
case ColumnType.Ask:
render_market_col(_asks, _ask_market_cell_background);
break;
case ColumnType.Volume:
_volume_container.mark_dirty();
_refresh_volume_at_price();
@@ -624,9 +694,9 @@ namespace mbts.spark {
if (_mkt_break_lines.high_render_index.HasValue)
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
}
void render_market_col(NativePriceSize[] depth) {
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, security.ContractSpec, column.settings, _fmt_size);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
}
}
_apply_column_visual_offset();
@@ -661,13 +731,32 @@ namespace mbts.spark {
} else {
_vm.OnDisposed += _on_vm_disposed;
_prices_updated = 0;
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
// invalidate the contract spec here, as well as in the 'settings update' callback.
// While it is assumed that the settings IObservable will always fire at least once (on subscribe),
// this serves as a redundancy/fail-safe; it ensures that the vm's security's
// contract spec will be used, even if the settings sequence is empty.
_invalidate_spec();
_price_comparer = Comparer<double>.Create((x, y) => _spec.tick_equals(_normalize_price(x), _normalize_price(y)) ? 0 : y.CompareTo(x));
_price_dirty = true;
_piq_dirty = true;
_vm.on_tick += _tick;
// **use local vm to protect against re-assignment,
// so the events are unhooked from the same instance to which they were added
// when the subs are disposed.
var local_vm = _vm;
local_vm.on_tick += _tick;
local_vm.request_recenter += on_request_recenter;
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
_volume_sub = _vm.model.order_gateway.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
_tick_sub = Disposable.Create(() => _vm.on_tick -= _tick);
// **unhook from local_vm.request_recenter.
// todo: organize these subs into a single entity, prob _vm_subs.
// This hasn't already been done bc the ladder a bit fragile, so changes must be carefully implemented,
// with meticulous attention to detail.
_tick_sub = Disposable.Create(() => local_vm.on_tick -= _tick);
_vm_sub = Disposable.Create(() => local_vm.request_recenter -= on_request_recenter);
void on_request_recenter() {
RecenterCommand?.ExecuteIfCan(null);
_refresh_frozen_mkt_lines();
}
}
}
@@ -704,7 +793,7 @@ namespace mbts.spark {
_hide_tool_tip();
}
tooltip_view.Content = $"{volume}";
tooltip_view.Content = _fmt_mkt_quantity(volume);
tooltip_view.IsOpen = true;
this.ToolTip = tooltip_view;
} else {
@@ -736,6 +825,15 @@ namespace mbts.spark {
#endregion
void _refresh_frozen_mkt_lines() {
if (_price_list is null)
return;
_mkt_break_lines.refresh_frozen();
_refresh_bid_mkt_break();
_refresh_ask_mkt_break();
_refresh_mid_mkt_break();
_refresh_fair_value_lines(true);
}
void _on_vm_disposed(bool obj) =>
_dispose_resources();
@@ -744,6 +842,7 @@ namespace mbts.spark {
Disposable.dispose(ref _settings_sub);
Disposable.dispose(ref _volume_sub);
Disposable.dispose(ref _tick_sub);
Disposable.dispose(ref _fair_value_lines_sub);
if (_vm != null)
_vm.OnDisposed -= _on_vm_disposed;
}
@@ -767,6 +866,7 @@ namespace mbts.spark {
} else {
_scroll_to_row(_row_count / 2);
}
_refresh_frozen_mkt_lines();
}
}
@@ -799,6 +899,7 @@ namespace mbts.spark {
_refresh_bid_mkt_break();
_refresh_ask_mkt_break();
_refresh_mid_mkt_break();
_refresh_fair_value_lines(true);
_refresh_session_low_line();
_refresh_session_high_line();
_refresh_session_range();
@@ -811,6 +912,7 @@ namespace mbts.spark {
_refresh_ask_mkt_break();
if (_mkt_break_lines.mid_dirty)
_refresh_mid_mkt_break();
_refresh_fair_value_lines(false);
if (_mkt_break_lines.session_low_dirty)
_refresh_session_low_line();
if (_mkt_break_lines.session_high_dirty)
@@ -820,7 +922,7 @@ namespace mbts.spark {
if (last_trade_changed) {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
}
if (depth_changed)
_refresh_sizes();
@@ -841,13 +943,36 @@ namespace mbts.spark {
}
void _refresh_bid_mkt_break() =>
_render_mkt_break(_bid_mkt_break_vis, _mkt_break_lines.bid, _settings.bid_break_line.color);
_render_mkt_break(
_bid_mkt_break_vis,
_settings.bid_break_line.refresh_mode == LadderMarketBreakLine.RefreshMode.Manual ?
_mkt_break_lines.frozen_bid :
_mkt_break_lines.bid,
_settings.bid_break_line.color);
void _refresh_ask_mkt_break() =>
_render_mkt_break(_ask_mkt_break_vis, _mkt_break_lines.ask, _settings.ask_break_line.color);
_render_mkt_break(
_ask_mkt_break_vis,
_settings.ask_break_line.refresh_mode == LadderMarketBreakLine.RefreshMode.Manual ?
_mkt_break_lines.frozen_ask :
_mkt_break_lines.ask,
_settings.ask_break_line.color);
void _refresh_mid_mkt_break() =>
_render_mkt_break(_mid_mkt_break_vis, _mkt_break_lines.mid, _settings.mid_break_line.color);
_render_mkt_break(
_mid_mkt_break_vis,
_settings.mid_break_line.refresh_mode == LadderMarketBreakLine.RefreshMode.Manual ?
_mkt_break_lines.frozen_mid :
_mkt_break_lines.mid,
_settings.mid_break_line.color);
void _refresh_fair_value_lines(bool force) {
foreach (var fvl in _mkt_break_lines.fair_value_line_views) {
if (force || fvl.is_dirty) {
_render_mkt_break(fvl.vis, fvl.price_view, fvl.color);
}
}
}
void _refresh_session_low_line() {
if (_settings.session_low_line.show && _mkt_break_lines.low_render_index.HasValue)
@@ -862,21 +987,21 @@ namespace mbts.spark {
void _refresh_last_trade() {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
}
void _refresh_piq() {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
}
void _refresh_sizes() {
Log.assert_d(_price_list != null);
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, security.ContractSpec, _settings.bid_column, _fmt_size);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, security.ContractSpec, _settings.ask_column, _fmt_size);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
}
/// <summary>
@@ -898,7 +1023,17 @@ namespace mbts.spark {
if (vapls != null) {
Log.assert_d(_price_list != null);
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
LadderRender.render_volumes(
_volume_vis_tup.visual,
_volume_vis_tup.glyph_context,
_price_list,
vapls,
_settings.row_height,
_spec,
_settings.volume_column,
last_index,
_get_last_arrow_string(),
_fmt_mkt_quantity);
_old_last_trade = _last_trade;
}
@@ -921,7 +1056,7 @@ namespace mbts.spark {
}
// regardless of if we've arrived here via a new Last (and new direction arrow)
// or we're reusing the old one, this format bit is the same
return $"{_direction_arrow} {_last_trade.size} ";
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
}
}
@@ -931,8 +1066,8 @@ namespace mbts.spark {
void _refresh_prices() {
var prev_price_list = _price_list;
_price_list = (!(_settings.net_change == LadderSettings.NetChangeTerms.None)) ?
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec) :
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec, _settings.price_display_format);
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, _spec) :
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, _spec, _settings.price_display_format);
_ordershost.update_settings(_price_list, _price_comparer, _settings.row_height, _settings.orders_column);
if (prev_price_list != null) {
// maintain previous visible price range if possible
@@ -960,8 +1095,8 @@ namespace mbts.spark {
SparkUtils.lazy_warn(!double.IsNaN(_mkt_break_lines.origin) && _mkt_break_lines.origin >= low && _mkt_break_lines.origin <= high,
() => fmt_log_msg($"market break ({_mkt_break_lines.origin}) is unacceptable for the market range ({low} - {high})"));
int prev_row_count = _row_count;
int n_ticks_to_low = security.ContractSpec.tickdiff(_mkt_break_lines.origin, low) ?? 0;
int n_ticks_to_high = security.ContractSpec.tickdiff(high, _mkt_break_lines.origin) ?? 0;
int n_ticks_to_low = _spec.tickdiff(_mkt_break_lines.origin, low) ?? 0;
int n_ticks_to_high = _spec.tickdiff(high, _mkt_break_lines.origin) ?? 0;
int desired_row_count = Math.Max(n_ticks_to_low, n_ticks_to_high) * 2 + 1;
_row_count = Math.Min(_max_row_count, desired_row_count);
if (_row_count != prev_row_count) {
@@ -977,7 +1112,7 @@ namespace mbts.spark {
_last_price_range_calc_time = now;
//
string fmt_log_msg(string msg) =>
$"Ladder {security} (id:{security.Id}, low/high tick:{security.ContractSpec.LowTick}/{security.ContractSpec.HighTick}): {msg}.";
$"Ladder {security} (id:{security.Id}, low/high tick:{_spec.LowTick}/{_spec.HighTick}): {msg}.";
}
return prices_changed;
}
@@ -1010,6 +1145,7 @@ namespace mbts.spark {
_refresh_bid_mkt_break();
_refresh_ask_mkt_break();
_refresh_mid_mkt_break();
_refresh_fair_value_lines(true);
_refresh_session_low_line();
_refresh_session_high_line();
_refresh_session_range();
@@ -1025,29 +1161,29 @@ namespace mbts.spark {
double _get_bid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return double.IsNaN(best_bid) ?
double.IsNaN(best_ask) ? _vm.low : security.ContractSpec.tickdown(best_ask) :
best_bid;
return LadderPriceLineViewAdapter.bid_line(best_bid, best_ask, _vm.settlement, _spec);
}
double _normalize_price(double price) =>
TickUtils.normalize_price(_spec, price);
double _get_ask_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return double.IsNaN(best_ask) ?
double.IsNaN(best_bid) ? security.ContractSpec.tickdown(_vm.high) : best_bid :
security.ContractSpec.tickdown(best_ask);
return LadderPriceLineViewAdapter.ask_line(best_bid, best_ask, _vm.settlement, _spec);
}
double _get_mid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return double.IsNaN(best_bid) ?
double.IsNaN(best_ask) ? 0.5 * (_vm.low + _vm.high) : security.ContractSpec.tickdown(best_ask) :
double.IsNaN(best_ask) ? best_bid : TickUtils.mid_tick(security.ContractSpec, best_bid, best_ask);
return LadderPriceLineViewAdapter.mid_line(best_bid, best_ask, _vm.settlement, _spec);
}
double _get_session_low() => _vm.md.session_low;
double _get_session_high() => _vm.md.session_high;
double _get_session_low() => _normalize_price(_get_session_low_raw());
double _get_session_high() => _normalize_price(_get_session_high_raw());
double _get_session_low_raw() => _vm.md.session_low;
double _get_session_high_raw() => _vm.md.session_high;
void _render_mkt_break(DrawingVisual vis, double price, uint color) =>
LadderRender.render_mkt_break(vis, _row_for_price(price), _settings.row_height, color);
@@ -1056,6 +1192,9 @@ namespace mbts.spark {
LadderRender.render_single_grid_line(vis, row, _settings.row_height, color, _price_column.x_left, _settings.price_column.width);
#endregion
void _invalidate_spec() =>
_spec_backing = null;
void _settings_updated(LadderSettings s) {
bool init;
double prev_row_height;
@@ -1069,6 +1208,7 @@ namespace mbts.spark {
/// The viewmodel will always have the most up-to-date settings, so ignore <see cref="s"/> and just get it from <see cref="_vm"/>.
/// This is a workaround for the global settings-defaults issue: when defaults are updated, some properties are inadvertantly overwritten, such as column-widths, etc.
_settings = _vm.get_save_state();
_invalidate_spec();
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
_price_vis_tup.glyph_context = glyph_context(s.price_column);
@@ -1079,14 +1219,30 @@ namespace mbts.spark {
GlyphContext glyph_context(LadderColumnSettings cs) =>
GlyphContext.create(cs.font_family, cs.font_weight);
_fmt_size = MDDisplayHelper.get_formatter_nps(_settings.size_display);
var mkt_qty_display_divisor = _vm?.display_quantity_divisor ?? 1;
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(_settings.size_display, mkt_qty_display_divisor);
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(mkt_qty_display_divisor);
_set_columns();
_apply_column_visual_offset();
if (prev_row_height != _settings.row_height)
_reset_height();
_price_dirty = _settings.price_column.show;
Disposable.dispose(ref _fair_value_lines_sub);
_mkt_break_lines.fair_value_line_views.Clear();
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
_visualHost.Clear();
if (LadderSettings.mimic_tt) {
// The session_range_vis sits beneath the grid backgrounds (on the
// z-axis) to avoid obfuscating the font color (dj wants it to be
// pure white). Historically, it sat atop the price column, with a
// low opacity to slightly darken the range of prices inside of the
// session range (i.e., the price-band of the day's trades). In
// conjuction with this change, the price column's background
// opacity is made slightly less than 1 (in the default settings),
// while the font color remains fully opaque, so this layer will
// darken the background without affecting the font color.
_visualHost.AddVisualChild(_session_range_vis);
}
// Renders the grid lines and column backgrounds
_visualHost.AddVisualChild(_gridvis);
// Orders column
@@ -1111,12 +1267,23 @@ namespace mbts.spark {
}
}
}
_refresh_frozen_mkt_lines();
_visualHost.AddVisualChild(_mid_mkt_break_vis);
_visualHost.AddVisualChild(_ask_mkt_break_vis);
_visualHost.AddVisualChild(_bid_mkt_break_vis);
_fair_value_lines_sub = ui.utility.ObservableAutoWrapper.connect(
_vm.fair_value_lines,
_mkt_break_lines.fair_value_line_views,
vm => {
var with_vis = new FairValueLineWithVis(vm);
_visualHost.AddVisualChild(with_vis.vis);
return with_vis;
},
with_vis => _visualHost.RemoveVisualChild(with_vis.vis));
_visualHost.AddVisualChild(_session_low_vis);
_visualHost.AddVisualChild(_session_high_vis);
_visualHost.AddVisualChild(_session_range_vis);
if (!LadderSettings.mimic_tt)
_visualHost.AddVisualChild(_session_range_vis);
_render_grid_background();
_init_tactile_adorner();
// this effectively sets the background of the "Last" column
@@ -1128,6 +1295,22 @@ namespace mbts.spark {
}
visible_columns_changed?.Invoke();
}
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
string _fmt_mkt_depth_item(NativePriceSize value) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_depth_item_impl;
return impl is null ? MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default())?.ToString() ?? "Error" : impl(value);
}
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
string _fmt_mkt_quantity(uint value) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_quantity_impl;
return impl is null ? value.ToString() : impl(value);
}
void _init_tactile_adorner() {
Disposable.dispose(ref _tactile_adorner);
@@ -1186,11 +1369,30 @@ namespace mbts.spark {
!(x == y);
}
class FairValueLineWithVis {
readonly FairValueLineVM _vm;
double _price_view = double.NaN;
public FairValueLineWithVis(FairValueLineVM vm) =>
_vm = vm ?? throw new ArgumentNullException(nameof(vm));
public DrawingVisual vis { get; } = new DrawingVisual();
public double price_view => _price_view;
public uint color => _vm.settings.color;
public bool is_dirty { get; private set; }
public void refresh(bool update_frozen) {
_vm.refresh(update_frozen);
is_dirty = ConvertUtils.update_value_type(ref _price_view, _vm.price_view);
}
}
// This class represents interesting price points as they should be rendered on the LadderGridHost
// Note: The values contained here do NOT necessarily represent true data, as they will be NaN/null if their respective visual is hidden in LadderSettings
class MarketBreakLines {
readonly LadderGridHost _owner;
double _bid, _ask, _mid, _origin, _session_low, _session_high;
double _bid = double.NaN, _ask = double.NaN, _mid = double.NaN, _origin, _session_low, _session_high;
double _frozen_bid = double.NaN, _frozen_ask = double.NaN, _frozen_mid = double.NaN, _frozen_origin = double.NaN;
int? _low_index, _high_index;
public MarketBreakLines(LadderGridHost owner) =>
@@ -1204,6 +1406,11 @@ namespace mbts.spark {
public bool session_range_dirty => session_low_dirty || session_high_dirty;
public bool origin_dirty { get; private set; }
public double frozen_bid => double.IsNaN(_frozen_bid) ? (_frozen_bid = _bid) : _frozen_bid;
public double frozen_ask => double.IsNaN(_frozen_ask) ? (_frozen_ask = _ask) : _frozen_ask;
public double frozen_mid => double.IsNaN(_frozen_mid) ? (_frozen_mid = _mid) : _frozen_mid;
public double frozen_origin => double.IsNaN(_frozen_origin) ? (_frozen_origin = _origin) : _frozen_origin;
public double bid => _bid;
public double ask => _ask;
public double mid => _mid;
@@ -1242,17 +1449,30 @@ namespace mbts.spark {
}
}
public ObservableCollection<FairValueLineWithVis> fair_value_line_views { get; } =
new ObservableCollection<FairValueLineWithVis>();
int _get_session_range_size() =>
_owner.security.spec_or_default().tickdiff(_session_high, _session_low) ?? 0;
_owner._spec.tickdiff(_session_high, _session_low) ?? 0;
LadderSettings _settings => _owner._settings;
public void refresh_frozen() {
_refresh(true);
}
public void refresh() {
_refresh(false);
}
void _refresh(bool update_frozen) {
bid_dirty = ConvertUtils.update_value_type(ref _bid, _settings.bid_break_line.show ? _owner._get_bid_market_break() : double.NaN);
ask_dirty = ConvertUtils.update_value_type(ref _ask, _settings.ask_break_line.show ? _owner._get_ask_market_break() : double.NaN);
mid_dirty = ConvertUtils.update_value_type(ref _mid, _settings.mid_break_line.show ? _owner._get_mid_market_break() : double.NaN);
session_low_dirty = ConvertUtils.update_value_type(ref _session_low, (_settings.session_low_line.show || _settings.session_range_fill.show) ? _owner._get_session_low() : double.NaN);
session_high_dirty = ConvertUtils.update_value_type(ref _session_high, (_settings.session_high_line.show || _settings.session_range_fill.show) ? _owner._get_session_high() : double.NaN);
foreach (var fvlv in fair_value_line_views) {
fvlv.refresh(update_frozen);
}
if (session_low_dirty)
_low_index = null;
if (session_high_dirty)
@@ -1267,6 +1487,11 @@ namespace mbts.spark {
if (double.IsNaN(new_origin))
new_origin = _owner._get_bid_market_break();
origin_dirty = ConvertUtils.update_value_type(ref _origin, new_origin);
if (update_frozen) {
_frozen_bid = _bid;
_frozen_ask = _ask;
_frozen_mid = _mid;
}
}
}
}
@@ -130,6 +130,8 @@ namespace mbts.spark {
if (e.ChangedButton == MouseButton.Middle) {
// create the new popup window in here
var obvm = order.create_orderbook_vm();
if (obvm is null)
return;
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
var view = new MiniOrderBookView(obvm);
var popup = new System.Windows.Controls.Primitives.Popup {
@@ -0,0 +1,60 @@
using bts.api;
namespace mbts.spark {
class LadderPriceLineViewAdapter {
public static double bid_line(
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (!double.IsNaN(bid)) {
return bid;
}
if (!double.IsNaN(ask)) {
return _try_tick_down(spec, ask);
}
return settlement;
}
}
public static double ask_line(
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (!double.IsNaN(ask)) {
return _try_tick_down(spec, ask);
}
if (!double.IsNaN(bid)) {
return bid;
}
return settlement;
}
}
public static double mid_line(
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (double.IsNaN(bid)) {
if (double.IsNaN(ask)) {
return settlement;
}
return _try_tick_down(spec, ask);
}
if (double.IsNaN(ask)) {
return bid;
}
if (spec.HasValue) {
return TickUtils.mid_tick(spec.Value, bid, ask);
}
return bid;
}
}
static double _try_tick_down(ContractSpecificationImmutable? spec, double value) {
if (spec.HasValue) {
return spec.Value.tickdown(value);
}
return value;
}
}
}
@@ -12,20 +12,24 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using bts.utils;
namespace mbts.spark {
static class BadgeRender {
// IMPORTANT: the return values for this method MUST BE IN SYNC with the "format" method, else "measure_height" and "LadderRender.measure_row_height" will be broken.
static int _line_count_for_style(LadderSettings.BadgeStyle badge_style) => badge_style == LadderSettings.BadgeStyle.Alternate ? 2 : 1;
// IMPORTANT: at the risk of being redundant, see above comment.
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style) {
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style, Func<uint, string> fmt_value = null) {
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
switch (style) {
case LadderSettings.BadgeStyle.Alternate:
var working_qty = quantity > filled ? quantity - filled : 0;
return new[] {
$"{side.abbreviate()}:{filled}",
$"W:{quantity - filled}"
$"{side.abbreviate()}:{fmt_value(filled)}",
$"W:{fmt_value(working_qty)}"
};
default: return new[] { $"{filled}/{quantity}" };
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
}
}
@@ -39,9 +43,12 @@ namespace mbts.spark {
static class LadderRender {
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
// (see how it is incorporated in measure_row_height() below)
public static readonly Thickness row_padding = new Thickness(6);
public static Thickness row_padding => LadderSettings.mimic_tt ?
new Thickness(4, 2, 4, 2) : new Thickness(6);
const string tick_separator_period = ".";
const string tick_separator_dash = "-";
const int GRID_LINE_HEIGHT = 1;
// IMPORTANT: this method ONLY WORKS if Badges use the BadgeRender.margin/border_thickness/padding values.
// TODO: elminate the dependency on BadgeRender.margin/border_thickness/padding by integrating them into settings.
@@ -54,11 +61,20 @@ namespace mbts.spark {
public static int index_of(double[] prices, double price, IComparer<double> comparer) =>
Array.BinarySearch(prices, price, comparer);
public static GlyphRun format_text(string text, int row, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) =>
public static GlyphRun format_text(
string text,
int row,
double row_height,
LadderColumnSettings settings,
GlyphContext glyph_context,
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) =>
glyph_context.get_glyph_run(
// long term there is probably a better way to get rid of the separator, such as in the TickExtensions._format_tick
price_display_format == LadderSettings.PriceDisplayFormat.Integer ? text.Replace(tick_separator_period, string.Empty).Replace(tick_separator_dash, string.Empty) : text,
settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height, true);
price_display_format == LadderSettings.PriceDisplayFormat.Integer ?
text.Replace(tick_separator_period, string.Empty).Replace(tick_separator_dash, string.Empty) :
text,
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
-4, VerticalAlignment.Center, row * row_height, true);
public static double[] gen_prices(double mid, int row_count, ContractSpecificationImmutable spec) {
// require odd number
@@ -81,7 +97,7 @@ namespace mbts.spark {
public static GlyphRun[] format_prices(double[] prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec) {
var result = new GlyphRun[prices.Length];
Parallel.For(0, prices.Length, i =>
result[i] = format_text(spec.format(prices[i], reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
return result;
}
public static GlyphRun[] format_prices(double[] prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
@@ -145,14 +161,13 @@ namespace mbts.spark {
using (var dc = dv.RenderOpen()) {
// draw column backgrounds
foreach (var col in columns) {
var background = col.settings.background.rgb_brush();
background.Freeze();
var background = col.settings.background.argb_brush();
dc.DrawRectangle(background, null, new Rect(col.x_left, 0, col.settings.width, rowcount * row_height));
}
// draw grid lines
for (int row = 1; row < rowcount; ++row) {
double y = row * row_height;
dc.DrawRectangle(brush, null, new Rect(0, y, 500, 1));
dc.DrawRectangle(brush, null, new Rect(0, y, 500, GRID_LINE_HEIGHT));
}
}
}
@@ -166,9 +181,18 @@ namespace mbts.spark {
double row_height,
ContractSpecificationImmutable spec,
LadderColumnSettings settings,
Func<NativePriceSize, string> fmt
) {
Func<NativePriceSize, string> fmt,
bool lighten_market_cells,
Color? market_cell_background) {
//var background = new SolidColorBrush(settings.background.rgb_color());
Brush market_cell_background_brush;
if (market_cell_background.HasValue) {
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
} else if (lighten_market_cells) {
market_cell_background_brush = settings.background.rgb_color().blend_with(Colors.White).to_brush();
} else {
market_cell_background_brush = null;
}
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Length * row_height));
@@ -177,15 +201,32 @@ namespace mbts.spark {
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
for (int i = 0; i < rungs.Length; i++) {
if (rungs[i].is_nan) break;
double price = rungs[i].price;
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
var row_tick_groups = rungs
.TakeWhile(nps => !nps.is_nan)
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
(aggr, rung) => rung.with(
direct_size: aggr.direct_size + rung.direct_size,
implied_size: aggr.implied_size + rung.implied_size),
rung => {
//spec.round_to_tick(rung.price);
var ticks = spec.full_ticks_from_zero(rung.price);
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
});
foreach (var rung in row_tick_groups) {
//double price = spec.round_to_tick(rung.price);
var ticks = spec.full_ticks_from_zero(rung.price);
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
// prices are in descending order, so get diff from curr_price - price
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
curr_index += tick_diff;
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Length) {
var gr = format_text(fmt(rungs[i]), curr_index, row_height, settings, glyph_context);
if (market_cell_background_brush != null)
dc.DrawRectangle(market_cell_background_brush, null,
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
settings.width, row_height - GRID_LINE_HEIGHT));
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
@@ -193,16 +234,17 @@ namespace mbts.spark {
}
public static void render_volumes(
DrawingVisual dv,
GlyphContext glyph_context,
double[] prices,
IEnumerable<VolumeAtPriceLevelImmutable> data,
double row_height,
ContractSpecificationImmutable spec,
VolumeColumnSettings settings,
int last_index,
string last_size_and_direction
) {
DrawingVisual dv,
GlyphContext glyph_context,
double[] prices,
IEnumerable<VolumeAtPriceLevelImmutable> data,
double row_height,
ContractSpecificationImmutable spec,
VolumeColumnSettings settings,
int last_index,
string last_size_and_direction,
Func<uint, string> fmt_value) {
//
if (!data.Any()) {
_clear_visual(dv);
return;
@@ -224,6 +266,8 @@ namespace mbts.spark {
_clear_visual(dv);
return;
}
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
// inflate the max so that the graph never fills the available space
double max = data.Max(v => v.Volume) * 1.1;
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
@@ -255,11 +299,11 @@ namespace mbts.spark {
}
x_bar += width;
if (show_numbers)
numbers_text.Add(v.ToString());
numbers_text.Add(fmt_value(v));
}
if (settings.total.show_numbers) {
numbers_text.Add(vap.Volume.ToString());
numbers_text.Add(fmt_value(vap.Volume));
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
@@ -309,19 +353,33 @@ namespace mbts.spark {
}
}
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings) {
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings, Func<uint, string> fmt_value) {
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
using (var dc = dv.RenderOpen()) {
var gr = format_text(last.size.ToString(), index, row_height, settings, glyph_context);
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
}
}
// this is basically a copy of redner_sizes up above
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, IEnumerable<(double price, uint piq)> pvp, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
internal static void render_piqs(
DrawingVisual dv,
GlyphContext glyph_context,
int origin_index,
double[] prices,
IEnumerable<(double price, uint piq)> pvp,
double row_height,
ContractSpecificationImmutable spec,
LadderColumnSettings settings,
Func<uint, string> fmt_value) {
//
using (var dc = dv.RenderOpen()) {
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
return;
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<uint>();
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
@@ -332,7 +390,7 @@ namespace mbts.spark {
curr_index += tick_diff;
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Length) {
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
var gr = format_text(fmt_value(entry.piq), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
@@ -353,7 +411,7 @@ namespace mbts.spark {
public static void render_session_range(DrawingVisual dv, uint color, LadderColumnSettings settings, int low_index, int high_index, double[] prices, double row_height) {
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, prices.Length))
dc.DrawRectangle(new SolidColorBrush(color.rgb_color()) { Opacity = 0.2 }, null, new Rect(0, high_index * row_height, settings.width, (low_index - high_index) * row_height));
dc.DrawRectangle(new SolidColorBrush(color.argb_color()), null, new Rect(0, high_index * row_height, settings.width, (low_index - high_index) * row_height));
}
static GlyphRun[] _format_empty_prices(int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
@@ -371,4 +429,4 @@ namespace mbts.spark {
using (dv.RenderOpen()) { }
}
}
}
}
@@ -50,6 +50,18 @@
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
</cv:ValueMap>
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
<cv:ValueMap x:Key="RefreshModeToBooleanConverter">
<cv:KeyValue Key="{x:Static Manual, MemberType={u:Nested sp:LadderMarketBreakLine.RefreshMode}}">
<cv:KeyValue.Value>
<sys:Boolean>false</sys:Boolean>
</cv:KeyValue.Value>
</cv:KeyValue>
<cv:KeyValue Key="{x:Static Auto, MemberType={u:Nested sp:LadderMarketBreakLine.RefreshMode}}">
<cv:KeyValue.Value>
<sys:Boolean>true</sys:Boolean>
</cv:KeyValue.Value>
</cv:KeyValue>
</cv:ValueMap>
<Style x:Key="ColumnBackground" TargetType="Rectangle">
<Setter Property="Grid.RowSpan" Value="3"/>
@@ -161,6 +173,191 @@
</Setter>
</Style>
<Style x:Key="LeftPanelPriceFontColorPopup" TargetType="c:PopupControl">
<Setter Property="Width" Value="60"/>
<Setter Property="Margin" Value="4,2"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style x:Key="LeftPanelPricePopupLabel" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="2,0"/>
</Style>
<Style x:Key="LeftPanelPricePopup" TargetType="c:PopupControl">
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="6,2"/>
<Setter Property="PopupContainerStyle">
<Setter.Value>
<Style TargetType="Border" BasedOn="{StaticResource PopupContainerStyleBase}">
<Setter Property="Padding" Value="4"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="MinHeight" Value="24"/>
<Setter Property="Header" Value="..."/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ShowExpanderArrow" Value="False"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<c:FontPicker
Grid.ColumnSpan="2"
SelectedFontFamily="{Binding font_family}"
SelectedFontSize="{Binding font_size}"
SelectedFontWeight="{Binding font_weight}"
/>
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}" Margin="4,0">Font Color</TextBlock>
<c:ColorPickerPopup
Grid.Row="2"
Grid.Column="1"
Palettes="{Binding font_color_palettes}"
SelectedColor="{Binding font_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
<TextBlock Grid.Row="4" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
<c:ColorPickerPopup
Grid.Row="4"
Grid.Column="1"
Palettes="{Binding background_color_palettes}"
SelectedColor="{Binding background_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
<CheckBox Grid.Row="6" IsChecked="{Binding show_label}">Show Label</CheckBox>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LeftPanelRelativePricePopup" TargetType="c:PopupControl" BasedOn="{StaticResource LeftPanelPricePopup}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<c:FontPicker
SelectedFontFamily="{Binding font_family}"
SelectedFontSize="{Binding font_size}"
SelectedFontWeight="{Binding font_weight}"
/>
<GroupBox Grid.Row="2" Header="Negative">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource LeftPanelPricePopupLabel}">Font Color</TextBlock>
<c:ColorPickerPopup
Grid.Column="1"
Palettes="{Binding negative_font_color_palettes}"
SelectedColor="{Binding negative_font_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
<c:ColorPickerPopup
Grid.Row="2"
Grid.Column="1"
Palettes="{Binding negative_background_color_palettes}"
SelectedColor="{Binding negative_background_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
</Grid>
</GroupBox>
<GroupBox Grid.Row="4" Header="Zero">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource LeftPanelPricePopupLabel}">Font Color</TextBlock>
<c:ColorPickerPopup
Grid.Column="1"
Palettes="{Binding font_color_palettes}"
SelectedColor="{Binding font_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
<c:ColorPickerPopup
Grid.Row="2"
Grid.Column="1"
Palettes="{Binding background_color_palettes}"
SelectedColor="{Binding background_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
</Grid>
</GroupBox>
<GroupBox Grid.Row="6" Header="Positive">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource LeftPanelPricePopupLabel}">Font Color</TextBlock>
<c:ColorPickerPopup
Grid.Column="1"
Palettes="{Binding positive_font_color_palettes}"
SelectedColor="{Binding positive_font_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
<c:ColorPickerPopup
Grid.Row="2"
Grid.Column="1"
Palettes="{Binding positive_background_color_palettes}"
SelectedColor="{Binding positive_background_color}"
Style="{StaticResource LeftPanelPriceFontColorPopup}"
/>
</Grid>
</GroupBox>
<CheckBox Grid.Row="8" IsChecked="{Binding show_label}">Show Label</CheckBox>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LadderColumnSample" TargetType="Border">
<Setter Property="Grid.Column" Value="6"/>
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
@@ -455,6 +652,68 @@
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
<u:KeyedTemplateSelector x:Key="OrderEntryPanelItemTemplateSelector">
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsPanelTemplateKeys.VisibilityItem}">
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
<ColumnDefinition Width="*" SharedSizeGroup="B"/>
</Grid.ColumnDefinitions>
<Border Margin="0,5,5,0">
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</CheckBox.Resources>
</CheckBox>
</Border>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsPanelTemplateKeys.PricePoint}">
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
<ColumnDefinition Width="*" SharedSizeGroup="B"/>
</Grid.ColumnDefinitions>
<Border Margin="0,5,5,0">
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</CheckBox.Resources>
</CheckBox>
</Border>
<c:PopupControl Grid.Column="1" Margin="4,2" Style="{StaticResource LeftPanelPricePopup}"/>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsPanelTemplateKeys.RelativePricePoint}">
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
<ColumnDefinition Width="*" SharedSizeGroup="B"/>
</Grid.ColumnDefinitions>
<Border Margin="0,5,5,0">
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</CheckBox.Resources>
</CheckBox>
</Border>
<c:PopupControl Grid.Column="1" Margin="4,2" Style="{StaticResource LeftPanelRelativePricePopup}"/>
</Grid>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
</UserControl.Resources>
<TabControl>
<TabControl.ItemContainerStyle>
@@ -525,10 +784,12 @@
<ColumnDefinition SharedSizeGroup="c2"/>
<ColumnDefinition SharedSizeGroup="c3"/>
<ColumnDefinition SharedSizeGroup="c4"/>
<ColumnDefinition SharedSizeGroup="c5"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Color" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="6,0" Text="Sample" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Auto Refresh" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Color" VerticalAlignment="Center"/>
<TextBlock Grid.Column="4" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="6,0" Text="Sample" VerticalAlignment="Center"/>
</Grid>
<ItemsControl Grid.Row="1" ItemsSource="{Binding mkt_break_items}" Margin="8,0">
<ItemsControl.ItemTemplate>
@@ -540,20 +801,28 @@
<ColumnDefinition SharedSizeGroup="c2"/>
<ColumnDefinition SharedSizeGroup="c3"/>
<ColumnDefinition SharedSizeGroup="c4"/>
<ColumnDefinition SharedSizeGroup="c5"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
</Border>
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show}"/>
<c:ColorPickerPopup
<CheckBox
Grid.Column="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding refresh_mode, Converter={StaticResource RefreshModeToBooleanConverter}, Mode=TwoWay}"
IsEnabled="{Binding can_edit_refresh_mode}"
/>
<c:ColorPickerPopup
Grid.Column="3"
BorderBrush="Transparent"
BorderThickness="0"
PopupPlacement="Left"
Palettes="{Binding palettes}"
SelectedColor="{Binding color}"
/>
<Grid Grid.Column="3" Grid.ColumnSpan="2" Height="27">
<Grid Grid.Column="4" Grid.ColumnSpan="2" Height="27">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
@@ -623,13 +892,16 @@
</Rectangle.Fill>
</Rectangle>
</Grid>
<Rectangle Grid.Column="1" Grid.ColumnSpan="5" Fill="DimGray" Height="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
<Rectangle Grid.Column="1" Grid.ColumnSpan="6" Fill="DimGray" Height="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</TabItem>
<TabItem Header="Fair Value Lines" VerticalAlignment="Stretch">
<usp:FairValueLineSettingsView HorizontalAlignment="Left" MaxHeight="300" DataContext="{Binding fair_value_line_settings_vm}"/>
</TabItem>
<!-- Market Price & Size Tab -->
<TabItem Header="Market Price and Size Display">
<Grid Margin="6" Grid.IsSharedSizeScope="True">
@@ -656,7 +928,7 @@
ItemsSource="{Binding format_options}"
SelectedItem="{Binding selected_format}"
/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal">
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" VerticalAlignment="Top">
<TextBlock
Margin="{StaticResource MarginIndentChild}"
Text="when implied is > 0"
@@ -670,6 +942,7 @@
IsEnabled="{Binding can_choose_alternate_implied_format}"
ItemsSource="{Binding alternate_implied_format_options}"
SelectedItem="{Binding selected_alternate_implied_format}"
VerticalAlignment="Top"
/>
</StackPanel>
<Border Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="12,4,0,0" Background="Transparent" BorderBrush="DimGray" BorderThickness="1" ToolTip="Sample Output">
@@ -728,7 +1001,38 @@
Visibility="{Binding is_synthetic, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}"
Padding="4"
>
<c:RadioButtonsControl ItemsSource="{Binding price_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_format}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock>Ticks Per Row</TextBlock>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<c:TickUpDownUInt
Maximum="100"
TickUpCommand="{Binding DataContext.tick_up_ticks_per_row_cmd, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
TickDownCommand="{Binding DataContext.tick_down_ticks_per_row_cmd, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
Value="{Binding DataContext.ticks_per_row, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
/>
<TextBlock
Grid.Column="1"
Margin="2,0,0,0"
Text="{Binding DataContext.rounded_ticks_per_row, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}, StringFormat='({0})'}"
ToolTip="The ticks per row parameter may need to be adjusted to fit evenly into dollars."
/>
</Grid>
<c:RadioButtonsControl Grid.Row="2" Grid.ColumnSpan="3" ItemsSource="{Binding price_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_format}"/>
</Grid>
</GroupBox>
<GroupBox
Grid.Row="1"
@@ -909,27 +1213,23 @@
<TextBlock VerticalAlignment="Center">Left Panel Width</TextBlock>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="{Binding Source={StaticResource SliderValueTextWidth}}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Slider Grid.Column="2" Minimum="{x:Static sp:LadderView.MinLeftPanelWidth}" Maximum="{x:Static sp:LadderView.MaxLeftPanelWidth}" Value="{Binding order_entry_panel_width}"/>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Text="{Binding order_entry_panel_width, StringFormat='0.00'}"/>
<Slider Grid.Column="0" Minimum="{x:Static sp:LadderView.MinLeftPanelWidth}" Maximum="{x:Static sp:LadderView.MaxLeftPanelWidth}" Value="{Binding order_entry_panel_width}"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding order_entry_panel_width, StringFormat='0.00'}"/>
</Grid>
<!--Zoom Factor-->
<TextBlock Grid.Row="2" VerticalAlignment="Center">Zoom Factor</TextBlock>
<Grid Grid.Row="2" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
<ColumnDefinition Width="{Binding Source={StaticResource SliderValueTextWidth}}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Slider Grid.Column="2" Minimum="{x:Static sp:LadderView.MinZoomFactor}" Maximum="{x:Static sp:LadderView.MaxZoomFactor}" Value="{Binding zoom_factor}"/>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Text="{Binding zoom_factor, StringFormat='0.00'}"/>
<Slider Grid.Column="0" Minimum="{x:Static sp:LadderView.MinZoomFactor}" Maximum="{x:Static sp:LadderView.MaxZoomFactor}" Value="{Binding zoom_factor}"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding zoom_factor, StringFormat='0.00'}"/>
</Grid>
<!--
- Note the 'Delays' in the 'SelectedItem' bindings; this is a workaround for a wpf quirk when the DataContext switches (i.e., when the selected Profile changes in the ShellSettingsDialog).
@@ -944,7 +1244,7 @@
<ComboBox Grid.Row="6" Grid.Column="2" ItemsSource="{Binding time_in_force_options}" SelectedItem="{Binding default_time_in_force, Delay=1}"/>
<!--Algo-->
<TextBlock Grid.Row="8" Grid.Column="0" VerticalAlignment="Center">Algo</TextBlock>
<ComboBox Grid.Row="8" Grid.Column="6" ItemsSource="{Binding Source={StaticResource AlgoDefinitionsViewSource}}" SelectedItem="{Binding default_algo, Delay=1}">
<ComboBox Grid.Row="8" Grid.Column="2" ItemsSource="{Binding Source={StaticResource AlgoDefinitionsViewSource}}" SelectedItem="{Binding default_algo, Delay=1}">
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
</ComboBox.GroupStyle>
@@ -966,30 +1266,16 @@
</Grid>
</GroupBox>
<GroupBox Grid.Column="2" Header="Show/Hide" Padding="8,4">
<ItemsControl Grid.IsSharedSizeScope="True" ItemsSource="{Binding order_entry_panel_items}">
<ItemsControl
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding order_entry_panel_items}"
ItemTemplateSelector="{StaticResource OrderEntryPanelItemTemplateSelector}"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" Focusable="False"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
</Grid.ColumnDefinitions>
<Border Margin="0,5,5,0">
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
</CheckBox.Resources>
</CheckBox>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<!--
@@ -1019,45 +1305,47 @@
<Grid HorizontalAlignment="Left" Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- market cross -->
<CheckBox Name="warn" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
</CheckBox.Resources>
<CheckBox Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding show_warn_on_cross}">
<TextBlock
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding show_warn_on_cross}"
Text="Warn on Market Cross"
/>
</CheckBox>
<TextBlock Grid.Column="1"
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding ElementName=warn, Path=IsChecked}"
Text="Warn on Market Cross" />
<!-- max order qty -->
<CheckBox Name="enforce_max_clip" Grid.Row="1" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding enforce_max_clip}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
</CheckBox.Resources>
<CheckBox Grid.Row="2" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_clip}">
<TextBlock
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding enforce_max_clip}"
Text="Max Order Quantity"
/>
</CheckBox>
<TextBlock Grid.Row="1" Grid.Column="1"
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
MinWidth="{Binding ElementName=warn, Path=ActualWidth}"
Text="Max Order Quantity"/>
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2"
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
Value="{Binding max_clip}" />
<c:TickUpDownUInt
Grid.Row="2"
Grid.Column="2"
IsEnabled="{Binding enforce_max_clip}"
Value="{Binding max_clip}"
Width="60"
/>
<CheckBox Grid.Row="4" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_settlement_cross_ticks}">
<TextBlock
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding enforce_max_settlement_cross_ticks}"
Text="Max Ticks from Settlement"
/>
</CheckBox>
<c:TickUpDownInt Grid.Row="4" Grid.Column="2" IsEnabled="{Binding enforce_max_settlement_cross_ticks}" Value="{Binding max_settlement_cross_ticks}"/>
</Grid>
</TabItem>
<TabItem Header="Mini Order Book">
@@ -37,4 +37,10 @@ namespace mbts.ui.spark {
public static uint GetVolumeComponentValue(DependencyObject d) => (uint)d.GetValue(VolumeComponentValueProperty);
public static void SetVolumeComponentValue(DependencyObject d, uint value) => d.SetValue(VolumeComponentValueProperty, value);
}
class LadderSettingsPanelTemplateKeys {
public static string VisibilityItem = nameof(VisibilityItem);
public static string PricePoint = nameof(PricePoint);
public static string RelativePricePoint = nameof(RelativePricePoint);
}
}
@@ -126,9 +126,12 @@ namespace mbts.spark {
return Cursors.Arrow;
switch (column.column_type) {
case ColumnType.Bid:
case ColumnType.Ask: return _can_send_orders ? Cursors.Hand : Cursors.No;
default: return Cursors.Arrow;
case ColumnType.Ask:
if (!_can_send_orders)
return Cursors.No;
break;
}
return Cursors.Arrow;
}
#region UI Event Handlers
@@ -37,7 +37,7 @@
<converters:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
<converters:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
<converters:ResponsiveTextConverter x:Key="OpenTextConverter" LongText="Open:" ShortText="O:" Threshold="99" />
<converters:ResponsiveTextConverter x:Key="SettleTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
<converters:ResponsiveTextConverter x:Key="SettlementTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
<converters:ResponsiveTextConverter x:Key="VolumeTextConverter" LongText="Volume:" ShortText="V:" Threshold="99" />
<converters:DoubleSubtractionConverter x:Key="DoubleSubtractionConverter" Value="4" />
<converters:DoubleSubtractionConverter x:Key="HeightDoubleSubtractionConverter" Value="6" />
@@ -141,7 +141,61 @@
<Setter Property="Margin" Value="2,0,2,5"/>
<Setter Property="Padding" Value="2"/>
</Style>
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{mbts:BTSColor UI.Button.normal_border}"
Background="{mbts:BTSColor UI.Button.normal_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="{mbts:BTSColor UI.Button.mouseover_background}" />
<Setter TargetName="Border" Property="BorderBrush" Value="{mbts:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="0"/>
</Style>
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Cancel Buys</TextBlock>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Cancel Sells</TextBlock>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">Cancel Buys</TextBlock>
@@ -177,6 +231,7 @@
</Style>
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Background" Value="{mbts:BTSColor MarketData.ask}" />
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">Cancel Sells</TextBlock>
@@ -210,39 +265,6 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{mbts:BTSColor UI.Button.normal_border}"
Background="{mbts:BTSColor UI.Button.normal_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content"
/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="{mbts:BTSColor UI.Button.mouseover_background}" />
<Setter TargetName="Border" Property="BorderBrush" Value="{mbts:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Content">
<Setter.Value>
@@ -277,6 +299,23 @@
</Setter.Value>
</Setter>
</Style>
<!-- Left Panel Price Points -->
<Style x:Key="LeftPanelPricePointLabel" TargetType="TextBlock">
<Setter Property="Margin" Value="0,1,0,4"/>
<Setter Property="VerticalAlignment" Value="Top"/>
</Style>
<Style x:Key="LeftPanelPricePointBorder" TargetType="Border">
<Setter Property="Grid.Column" Value="2"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Padding" Value="1"/>
</Style>
<Style x:Key="LeftPanelPricePointText" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="{mbts:BTSColor UI.default_text}"/>
</Style>
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
</ResourceDictionary>
@@ -345,90 +384,188 @@
<ColumnDefinition Width="6"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Day -->
<TextBlock Grid.Column="0" Grid.Row="0"
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
Margin="0,0,0,4"
Visibility="{Binding show_day, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Grid.Column="2" Grid.Row="0" FontWeight="Bold"
Margin="0,0,0,4"
Foreground="{Binding change, Converter={StaticResource NumericSignBrushConverter}}"
Visibility="{Binding show_day, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="change"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- High -->
<TextBlock Grid.Column="0" Grid.Row="1"
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
Margin="0,0,0,4"
Visibility="{Binding show_high, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Grid.Column="2" Grid.Row="1" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold"
Margin="0,0,0,4"
Visibility="{Binding show_high, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="high"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- Low -->
<TextBlock Grid.Column="0" Grid.Row="2"
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
Margin="0,0,0,4"
Visibility="{Binding show_low, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Grid.Column="2" Grid.Row="2" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold"
Margin="0,0,0,4"
Visibility="{Binding show_low, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="low"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- Open -->
<TextBlock Grid.Column="0" Grid.Row="3"
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
Margin="0,0,0,4"
Visibility="{Binding show_open, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Grid.Column="2" Grid.Row="3" FontWeight="Bold"
Margin="0,0,0,4"
Visibility="{Binding show_open, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="open"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- Settle -->
<TextBlock Grid.Column="0" Grid.Row="4"
Text="{Binding left_panel_width, Converter={StaticResource SettleTextConverter}}"
Margin="0,0,0,4"
Visibility="{Binding show_settle, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Grid.Column="2" Grid.Row="4" FontWeight="Bold"
Margin="0,0,0,4"
Visibility="{Binding show_settle, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="settlement"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- Day Change -->
<TextBlock
Grid.Row="0"
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
Visibility="{Binding day_change_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}"
/>
<Border
Grid.Row="0"
Background="{Binding day_change_background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Visibility="{Binding day_change_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Day Change"
Style="{StaticResource LeftPanelPricePointBorder}"
>
<TextBlock
FontFamily="{Binding day_change_style.font_family}"
FontSize="{Binding day_change_style.font_size}"
FontWeight="{Binding day_change_style.font_weight}"
Foreground="{Binding day_change_font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}"
>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="day_change"/>
<Binding Path="security.ContractSpec"/>
<Binding Path="day_change_display_settings"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
<!-- Volume -->
<TextBlock Grid.Column="0" Grid.Row="5"
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
Margin="0,0,0,4"
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Grid.Column="2" Grid.Row="5" Text="{Binding volume, Mode=OneWay}" FontWeight="Bold"
Margin="0,0,0,4"
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock
Grid.Column="0"
Grid.Row="1"
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
Visibility="{Binding volume_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}"
/>
<Border
Grid.Row="1"
Background="{Binding volume_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Visibility="{Binding volume_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Volume"
Style="{StaticResource LeftPanelPricePointBorder}"
>
<TextBlock
FontFamily="{Binding volume_style.font_family}"
FontSize="{Binding volume_style.font_size}"
FontWeight="{Binding volume_style.font_weight}"
Foreground="{Binding volume_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}"
>
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Converter>
<converters:MultiValueConverterPipe>
<x:Static MemberType="{x:Type converters:DynamicDoubleDivisorConverter}" Member="Instance"/>
<x:Static MemberType="{x:Type converters:ValueConverters}" Member="SimpleString"/>
</converters:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="volume" Mode="OneWay"/>
<Binding Path="display_quantity_divisor" Mode="OneTime"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
<!-- High -->
<TextBlock
Grid.Row="2"
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
Visibility="{Binding session_high_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}"
/>
<Border
Grid.Row="2"
Background="{Binding session_high_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Visibility="{Binding session_high_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Session High"
Style="{StaticResource LeftPanelPricePointBorder}"
>
<TextBlock
FontFamily="{Binding session_high_style.font_family}"
FontSize="{Binding session_high_style.font_size}"
FontWeight="{Binding session_high_style.font_weight}"
Foreground="{Binding session_high_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}"
>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_high"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
<!-- Low -->
<TextBlock
Grid.Row="3"
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
Visibility="{Binding session_low_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}"
/>
<Border
Grid.Row="3"
Background="{Binding session_low_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Visibility="{Binding session_low_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Session Low"
Style="{StaticResource LeftPanelPricePointBorder}"
>
<TextBlock
FontFamily="{Binding session_low_style.font_family}"
FontSize="{Binding session_low_style.font_size}"
FontWeight="{Binding session_low_style.font_weight}"
Foreground="{Binding session_low_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}"
>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_low"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
<!-- Open -->
<TextBlock
Grid.Row="4"
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
Visibility="{Binding open_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}"
/>
<Border
Grid.Row="4"
Background="{Binding open_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Visibility="{Binding open_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Open"
Style="{StaticResource LeftPanelPricePointBorder}"
>
<TextBlock
FontFamily="{Binding open_style.font_family}"
FontSize="{Binding open_style.font_size}"
FontWeight="{Binding open_style.font_weight}"
Foreground="{Binding open_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}"
>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="open"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
<!-- Settlement -->
<TextBlock
Grid.Row="5"
Text="{Binding left_panel_width, Converter={StaticResource SettlementTextConverter}}"
Visibility="{Binding settlement_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}"
/>
<Border
Grid.Row="5"
Background="{Binding settlement_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Visibility="{Binding settlement_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Settlement"
Style="{StaticResource LeftPanelPricePointBorder}"
>
<TextBlock
FontFamily="{Binding settlement_style.font_family}"
FontSize="{Binding settlement_style.font_size}"
FontWeight="{Binding settlement_style.font_weight}"
Foreground="{Binding settlement_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}"
>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="settlement"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Grid>
<!-- Account combobox -->
<ComboBox DataContext="{Binding order_properties_vm}" Margin="2,10,2,2"
@@ -507,7 +644,7 @@
mbts:TextBoxBehavior.IncrementDecrementOnMouseClick="True"
HorizontalContentAlignment="Right"
Margin="2"
Text="{Binding size_calculator.value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding size_calculator.display_value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ContextMenu="{x:Null}"
/>
<!-- Qty quick buttons -->
@@ -522,7 +659,14 @@
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Content="{Binding}" Focusable="False"/>
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" Content="{Binding}" Focusable="False">
<Button.CommandParameter>
<MultiBinding Converter="{x:Static converters:DynamicDoubleMultiplierConverter.Instance}">
<Binding/>
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=spark:LadderView}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
@@ -547,15 +691,20 @@
</ItemsPanelTemplate>
</uag:AlgoTemplateFavoriteButtonsView.ItemsPanel>
</uag:AlgoTemplateFavoriteButtonsView>
<StackPanel Margin="0,17,0,0" VerticalAlignment="Top">
<Grid Margin="0,17,0,0" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Cancel Buttons -->
<Button Command="{Binding cancel_command}" CommandParameter="Bids" Style="{StaticResource CancelBuys}"
<Button Command="{Binding cancel_command}" CommandParameter="Bids" Style="{StaticResource {x:Static spark:LadderView.CancelBuysButtonStyleKey}}"
Visibility="{Binding show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Button Command="{Binding cancel_command}" CommandParameter="Asks" Style="{StaticResource CancelSells}"
<Button Command="{Binding cancel_command}" CommandParameter="Asks" Style="{StaticResource {x:Static spark:LadderView.CancelSellsButtonStyleKey}}"
Visibility="{Binding show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Button Command="{Binding cancel_command}" CommandParameter="All" Style="{StaticResource CancelAll}"
<Button Command="{Binding cancel_command}" CommandParameter="All" Style="{StaticResource {x:Static spark:LadderView.CancelAllButtonStyleKey}}"
Visibility="{Binding show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
</Grid>
<StackPanel Grid.Row="1" Margin="0,17,0,0" VerticalAlignment="Bottom">
<!-- Settings Button -->
<Button Command="{Binding settings_command}" Style="{StaticResource SettingsButton}"/>
@@ -22,6 +22,22 @@ namespace mbts.spark {
MaxLeftPanelWidth = 150,
MinZoomFactor = 0.5,
MaxZoomFactor = 1.5;
static LadderView() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
MimicTT = knobs.mimic_tt;
}
public static bool MimicTT { get; }
public static string CancelBuysButtonStyleKey =>
MimicTT ? "TT_CancelBuys" : "CancelBuys";
public static string CancelSellsButtonStyleKey =>
MimicTT ? "TT_CancelSells" : "CancelSells";
public static string CancelAllButtonStyleKey =>
MimicTT ? "TT_CancelAll" : "CancelAll";
public static IMultiValueConverter PositionToolTipConverter =
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
@@ -93,6 +109,7 @@ namespace mbts.spark {
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
this.PreviewMouseDown += LadderView_PreviewMouseDown;
PreviewKeyDown += (s, e) => vm.key_down(e);
}
IDisposable _connect_left_panel_scroll() {
@@ -252,4 +269,4 @@ namespace mbts.spark {
}
}
}
}
@@ -3,6 +3,7 @@ using bts.api.position;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
@@ -25,24 +26,27 @@ namespace bts.shell.manual_fills {
readonly Property<bool> _is_dirty;
readonly Property<EditableItemState> _editable_item_state;
readonly DelegateProperty<bool> _is_valid;
readonly DisplayQuantityMultiplier<uint> _size = DisplayQuantityMultiplier.create_uint();
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
SecurityImmutable? _security;
AccountTuple? _account;
UserTuple? _user;
Side _side;
uint _size;
double _price;
bool _is_selected;
ManualFillData? _data;
IDisposable _committed_snapshot;
public ManualFillRow(ManualFillData data) {
public ManualFillRow(ManualFillData data, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
_data = data;
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
key = data.key;
_security = data.value.security;
_account = data.value.account;
_user = data.value.user;
_side = data.value.manual_fill.Side;
_size = data.value.manual_fill.Quantity;
_size.actual_value = data.value.manual_fill.Quantity;
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(data.value.security), QuantityConversionData.OpType.DivideActual);
_price = data.value.manual_fill.Price;
if (placeholder_key.HasValue) {
_is_dirty = backing(nameof(is_dirty), false);
@@ -53,6 +57,7 @@ namespace bts.shell.manual_fills {
_editable_item_state = backing(nameof(editable_item_state), _is_dirty, ch => ch.newval ? EditableItemState.Modified : EditableItemState.None);
}
_is_valid = backing(nameof(is_valid), () => _validate(this));
_dispose = _size.connect_owner(this, nameof(size), nameof(display_size));
}
public event Action<bool> is_selected_changed;
@@ -65,8 +70,10 @@ namespace bts.shell.manual_fills {
public SecurityImmutable? security {
get => _security;
set {
if (_set(ref _security, value))
if (_set(ref _security, value)) {
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(value), QuantityConversionData.OpType.DivideActual);
RaisePropertyChanged(nameof(spec));
}
}
}
public ContractSpecificationImmutable? spec => security.spec_or_default();
@@ -75,7 +82,8 @@ namespace bts.shell.manual_fills {
public UserTuple? user { get => _user; set => _set(ref _user, value); }
// side/size/price
public Side side { get => _side; set => _set(ref _side, value); }
public uint size { get => _size; set => _set(ref _size, value); }
public uint size { get => _size.actual_value; set => _size.actual_value = value; }
public uint display_size { get => _size.display_value; set => _size.display_value = value; }
public double price { get => _price; set => _set(ref _price, value); }
// is selected
public bool is_selected {
@@ -71,7 +71,7 @@
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding display_size}"/>
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
</Grid>
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
@@ -1,4 +1,5 @@
using bts.api.core;
using bts.api;
using bts.api.core;
using bts.api.position;
using bts.collections;
using mbts.spark;
@@ -103,5 +104,8 @@ namespace bts.shell.manual_fills {
public async Task<bool> update_fills(IEnumerable<ManualFill> fills) =>
await _model.update_manual_fills(fills);
public uint get_display_qty_divisor(SecurityImmutable? security) =>
_model.security_auxiliary_model.get_display_quantity_divisor(security);
}
}
@@ -2,6 +2,7 @@ using bts.api;
using bts.api.core;
using bts.api.position;
using bts.collections;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
@@ -67,7 +68,7 @@ namespace bts.shell.manual_fills {
RXCollection.connect(_persisted_rows,
_model.manual_fills.OnDispatcher(),
Observable.Return(Comparer<ManualFillData>.Create((a, b) => a.key.CompareTo(b.key))),
mfd => new ManualFillRow(mfd),
mfd => new ManualFillRow(mfd, model.get_display_qty_divisor),
mfr => mfr.data),
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
@@ -125,7 +126,8 @@ namespace bts.shell.manual_fills {
new ManualFillKey(_placeholder_id_counter++),
new ManualFillTuple(new ManualFillImmutable(new ManualFill {
Side = Side.Buy
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())));
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())),
_model.get_display_qty_divisor);
void _add_fill(ManualFillRow new_row) =>
_placeholder_rows.InsertSorted((new_row ?? (new_row = row_factory())).placeholder_key.HasValue ?
@@ -209,11 +211,20 @@ namespace bts.shell.manual_fills {
} else {
add_imported_fills();
}
void add_imported_fills() =>
_placeholder_rows.InsertSorted(parsed_fills.Select(pmf => new ManualFillRow(new ManualFillData(new ManualFillKey(_placeholder_id_counter++), new ManualFillTuple(new ManualFillImmutable(pmf.result),
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
users.FirstOrDefault(a => a?.id == pmf.result.UserId))))), _row_key_comparer);
void add_imported_fills() {
_placeholder_rows.InsertSorted(parsed_fills.Select(mk_row), _row_key_comparer);
ManualFillRow mk_row(ParsedManualFill pmf) {
var data = new ManualFillData(
new ManualFillKey(_placeholder_id_counter++),
new ManualFillTuple(
new ManualFillImmutable(pmf.result),
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
users.FirstOrDefault(a => a?.id == pmf.result.UserId)));
return new ManualFillRow(data, _model.get_display_qty_divisor);
}
}
} catch (Exception ex) {
BTSMessageBox.Error($"Error importing fills: {ex}.", ManualFillsWindowFactory.window_name);
}
@@ -212,12 +212,12 @@
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Size, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Qty" SortMemberPath="side">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding size}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding display_size}" VerticalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<c:TickUpDownUInt MinWidth="40" Value="{Binding size, UpdateSourceTrigger=PropertyChanged}"/>
<c:TickUpDownUInt MinWidth="40" Value="{Binding display_size, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
@@ -597,19 +597,19 @@ namespace mbts.spark {
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
: base(Columns.Size, "Qty", r => r.display_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,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.display_quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
: base(Columns.Filled, "Filled Qty", r => r.display_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,
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.display_filled_quantity.ToString(), position, row_height,
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
}
@@ -1163,7 +1163,7 @@ namespace mbts.spark {
public override FrameworkElement view_factory() {
var tick_up_down = new TickUpDownNullableUIntCommit();
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.SourceValueProperty, nameof(OrderBookRowVM.prospective_quantity));
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.SourceValueProperty, nameof(OrderBookRowVM.prospective_display_quantity));
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.OnCommitProperty, nameof(OrderBookRowVM.modify_order_quantity_andor_price));
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Enter, nameof(OrderBookRowVM.modify_order_quantity_andor_price)));
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Escape, nameof(OrderBookRowVM.revert_prospective_order_quantity_andor_price)));
@@ -31,16 +31,16 @@ namespace mbts.spark {
int _depth_level;
SecurityImmutable? _security => order_tuple.order.security;
// for mini-ob
uint _prospective_quantity;
DisplayQuantityMultiplier<uint> _prospective_qty_translator_backing;
uint _display_qty_divisor_backing;
bool _display_qty_divisor_dirty = true;
double _prospective_price;
Action<bool> _on_selection_changed;
public OrderBookRowVM(OrderBookVM owner, OrderSecurityRootPair order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
_owner = owner;
this.order_tuple = order_tuple;
is_visible = !is_child;
_is_expanded = is_expanded;
prospective_quantity = this.quantity;
prospective_price = this.price;
_on_selection_changed = on_selection_changed;
}
@@ -126,14 +126,18 @@ namespace mbts.spark {
public string last_price_text => _fmt_price(last_price);
public string avg_price_text => _fmt_price(avg_price);
public uint quantity => order.quantity();
public uint display_quantity => quantity.safe_divide(_display_qty_divisor);
public uint filled_quantity => order.filled();
public uint display_filled_quantity => filled_quantity.safe_divide(_display_qty_divisor);
public string last_msg => order.last_msg();
// piq
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.safe_divide(_display_qty_divisor).ToString();
public bool is_non_terminal => order.is_non_terminal();
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
public string filled_status_desc => order.filled_status_description(security.spec_or_default(), _display_qty_divisor);
// Root Algo
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
@@ -144,7 +148,6 @@ namespace mbts.spark {
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
public bool is_child => order.has_parent();
public bool is_visible { get; set; }
public bool is_expanded {
get => _is_expanded;
set {
@@ -162,12 +165,12 @@ namespace mbts.spark {
}
}
public uint prospective_quantity {
get => _prospective_quantity;
set {
if (Set(ref _prospective_quantity, value)) {
RaisePropertyChanged(nameof(is_qty_dirty));
}
}
get => _prospective_qty_translator.actual_value;
set => _prospective_qty_translator.actual_value = value;
}
public uint prospective_display_quantity {
get => _prospective_qty_translator.display_value;
set => _prospective_qty_translator.display_value = value;
}
public double prospective_price {
get => _prospective_price;
@@ -257,6 +260,28 @@ namespace mbts.spark {
}
}
uint _display_qty_divisor {
get {
_sync_display_qty_if_dirty();
return _display_qty_divisor_backing;
}
}
IDisplayTranslator<uint> _prospective_qty_translator {
get {
if (_prospective_qty_translator_backing == null) {
_prospective_qty_translator_backing = DisplayQuantityMultiplier.create_uint();
_prospective_qty_translator_backing.conversion_data = new QuantityConversionData(_display_qty_divisor, QuantityConversionData.OpType.DivideActual);
_prospective_qty_translator_backing.actual_value = quantity;
_prospective_qty_translator.value_changed += () =>
RaisePropertyChanged(nameof(is_qty_dirty));
} else {
_sync_display_qty_if_dirty();
}
return _prospective_qty_translator_backing;
}
}
#region IHierarchicalRow
bool IHierarchicalRow.is_parent => is_parent;
bool IHierarchicalRow.is_expanded => is_expanded;
@@ -300,22 +325,35 @@ namespace mbts.spark {
if ((Columns.OrderType & dirty) != 0)
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
// quantity
if ((Columns.Size & dirty) != 0) {
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
// via the Mini OB update column mechanism (vs the EditableQty column)
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
// row will no longer match the filter
prospective_quantity = quantity;
}
// price
if ((Columns.Price & dirty) != 0) {
RaisePropertyChanged(nameof(price));
RaisePropertyChanged(nameof(is_price_dirty));
}
// security
if ((Columns.Security & dirty) != 0)
RaisePropertyChanged(nameof(security), nameof(security_text));
if ((Columns.Security & dirty) != 0) {
_display_qty_divisor_dirty = true;
RaisePropertyChanged(
// security props
nameof(security),
nameof(security_text),
// qty props
nameof(display_quantity),
nameof(prospective_quantity),
nameof(prospective_display_quantity),
nameof(is_qty_dirty),
//
nameof(piq_text),
nameof(display_filled_quantity));
}
if ((Columns.Size & dirty) != 0) {
RaisePropertyChanged(nameof(quantity), nameof(display_quantity));
if (_prospective_qty_translator_backing != null)
_prospective_qty_translator_backing.actual_value = quantity;
}
if ((Columns.Filled & dirty) != 0) {
RaisePropertyChanged(nameof(filled_quantity), nameof(display_filled_quantity));
}
// badge fill
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
// invalidate by setting to null (will be re-instantiated in the public getter)
@@ -392,6 +430,15 @@ namespace mbts.spark {
security.Value.ContractSpec.format(v) :
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
void _sync_display_qty_if_dirty() {
if (_display_qty_divisor_dirty) {
_display_qty_divisor_dirty = false;
_display_qty_divisor_backing = _owner.get_display_qty_divisor(security);
if (_prospective_qty_translator_backing != null)
_prospective_qty_translator_backing.conversion_data = new QuantityConversionData(_display_qty_divisor_backing, QuantityConversionData.OpType.DivideActual);
}
}
void RaisePropertyChanged(params string[] props) {
var pc = PropertyChanged;
if (pc == null)
@@ -430,6 +477,7 @@ namespace mbts.spark {
Columns.LastMessage |
Columns.Piq,
filled_status_desc_mask =
Columns.Security |
Columns.Filled |
Columns.AvgFilled |
Columns.Status;
@@ -51,6 +51,8 @@ namespace mbts.spark {
readonly OrderParamsAggregator _selected_params_mon;
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
readonly RightPanelModel _right_panel = new RightPanelModel();
readonly CustomStateSynchronizer _custom_state_sync;
readonly Func<OrderSecurityRootPair, bool> _global_filter;
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
double
_raw_right_panel_width = OrderBookStartupData.Default.right_panel_width,
@@ -70,8 +72,8 @@ namespace mbts.spark {
FilterData _cached_filter_data;
public OrderBookVM(
IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false)
: this(bus, notification_service, model, OrdersModel.piq_enabled ? model.on_order_tuples_with_root_and_piq : model.on_order_tuples_with_root, settings_manager, rows_are_initially_expanded) { }
IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false, Func<OrderSecurityRootPair, bool> global_filter = null)
: this(bus, notification_service, model, OrdersModel.piq_enabled ? model.on_order_tuples_with_root_and_piq : model.on_order_tuples_with_root, settings_manager, rows_are_initially_expanded, global_filter) { }
public OrderBookVM(
IBus bus,
@@ -79,13 +81,15 @@ namespace mbts.spark {
OrdersModel model,
IObservable<TableUpdate<OrderSecurityRootPair, uint>> orders_source,
IWindowSettingsManager<OrderBookGridSettings> settings_manager,
bool rows_are_initially_expanded = false) {
bool rows_are_initially_expanded = false,
Func<OrderSecurityRootPair, bool> global_filter = null) {
_bus = bus;
_model = model;
_global_filter = global_filter;
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle, k => _cached_orders.TryGetValue(k, out var o) ? o.order : (OrderSecurityAlgo?)null);
ladder_vm = new LadderVMContainer(bus, model);
order_mod_vm = new OrderParamsVM(this, AlgoFormats.Exchange, AlgoEditingPermissions.AllowEditValues);
order_mod_vm = new OrderParamsVM(this, AlgoEditingPermissions.AllowEditValues);
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
order_mod_vm.algo_vm.algo_root = null;
_show_order_params = backing(nameof(show_order_params), () => (_right_panel.visible_components & OrderBookRightPanelSettings.Components.Params) != 0);
@@ -98,7 +102,7 @@ namespace mbts.spark {
};
_can_attach_selected_orders = backing(nameof(can_attach_selected_orders), () => _attachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
_can_detach_selected_orders = backing(nameof(can_detach_selected_orders), () => _detachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
attach_vm = new OrderParamsVM(this, AlgoFormats.Attachable, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
attach_vm = new OrderParamsVM(this, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
_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
@@ -124,6 +128,8 @@ namespace mbts.spark {
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
_current_row = backing<OrderBookRowVM>(nameof(current_row));
_columns.filter_changed += _on_filter_changed;
_custom_state_sync = new CustomStateSynchronizer(this);
// commands
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
var modify_selected_orders_async_cmd = new AsyncCommand(_modify_selected_orders_async, () => _selected_params_mon.all_active && order_mod_vm.is_dirty);
@@ -188,6 +194,7 @@ namespace mbts.spark {
security_plurality = p;
RaisePropertyChanged(nameof(security_plurality));
}),
_current_row.Subscribe(_ => _custom_state_sync.sync()),
orders_source.OnDispatcher().Subscribe(t => {
if (_selected_rows.Count == 0) {
_cached_orders = t.state;
@@ -207,6 +214,8 @@ namespace mbts.spark {
}
}
}
_custom_state_sync.sync(true);
orders_changed?.Invoke();
}),
model.on_orders_can_attach.OnDispatcher().Subscribe(table => {
_cached_attachable = new MappedDictionary<uint, KeyValuePair<uint, bool>, bool>(table.state, kv => kv.Value);
@@ -238,7 +247,22 @@ namespace mbts.spark {
}),
grid_settings_manager,
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
order_mod_vm.algo_vm.connect_data(
/// This is not the same security source used to populate <see cref="OrderParamsVM.security_md"/>.
/// The latter is always synced with the root of the selected row(s),
/// while this source is synced with that row with one exception:
/// If the root row's algo has a spreader algo (possibly nested),
/// the synthetic security is taken instead of the root security.
/// This has the affect of passing the synthetic security to the spreader algo viewmodel,
/// which then uses the security to populate the synthetic params ui.
/// TODO (maybe): clean this design up; the different security sources make it precarious.
/// One idea is to pass a special data type to the algo vm (which contains the spreader
/// security and execution strategy), which is only observed by the spreader algo vm.
_selected_params_mon.subscribe_security().StartWith((SecurityImmutable?)null).CombineLatest(
_selected_params_mon.subscribe_spreader_security().StartWith((SecurityImmutable?)null),
(root_sec, spreader_sec) => (root_sec, spreader_sec))
.Select(tup => tup.spreader_sec ?? tup.root_sec)
.OnDispatcher()),
order_mod_vm,
attach_vm,
_show_fills.observe_current(true)
@@ -323,32 +347,32 @@ namespace mbts.spark {
}
void on_begin_attaching() {
Disposable.replace(ref attach_vm_hooks,
Disposable.Create(
connect_common_selected_params(attach_vm),
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
AlgoParamPermissions bool_to_perm(bool b) =>
b ? AlgoParamPermissions.ReadWrite : AlgoParamPermissions.None;
}),
// custom templates (created by user)
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_algo_def_vm))));
// vm factory
AlgoDefinitionVM mk_algo_def_vm(AlgoData fd) {
var vm = AlgoDefinitionVM.create(_model, fd, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
return vm;
}
Disposable.dispose(ref attach_vm_hooks);
attach_vm_hooks = Disposable.Create(
_selected_security_mon.SubscribeThrottled(_selection_change_throttle).to_security_md(bus).OnDispatcher().SubscribeEach(newval => {
attach_vm.security_md = newval;
if (newval.HasValue) {
return _model.subscribe_algos(newval.Value.security, true)
.OnDispatcher()
.Subscribe(t => ((IAlgoSelector)attach_vm.algo_vm).load_algo_data(t.state, null));
}
return null;
}),
connect_common_selected_params(attach_vm),
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
AlgoParamPermissions bool_to_perm(bool b) =>
b ? AlgoParamPermissions.ReadWrite : AlgoParamPermissions.None;
}));
}
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
@@ -359,11 +383,11 @@ namespace mbts.spark {
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
revert_selected_orders_cmd.RaiseCanExecuteChanged();
}),
_selected_params_mon.subscribe_security().to_security_md(bus).OnDispatcher().Subscribe(newval => order_mod_vm.security_md = newval),
connect_common_selected_params(order_mod_vm));
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
@@ -375,7 +399,7 @@ namespace mbts.spark {
}
public event Action<OrderRecordImmutable?> selected_order_changed;
public event Action begin_attaching, end_attaching;
public event Action begin_attaching, end_attaching, orders_changed;
public event Action<(IEnumerable, bool)> request_selection;
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
@@ -395,6 +419,7 @@ namespace mbts.spark {
}
}
public OrderRecordImmutable? current_order => current_row?.order;
public ObservableCollection<NamedValueVM> current_algo_custom_state { get; } = new ObservableCollection<NamedValueVM>();
public AlgoDefinitionVM algo_vm => order_mod_vm.algo_vm;
public OrderParamsVM order_mod_vm { get; }
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
@@ -474,6 +499,28 @@ namespace mbts.spark {
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
public IObservable<bool> subscribe_show_order_params() => _show_order_params.observe_current(true);
public IObservable<OrderSecurityAlgo?> subscribe_single_selected_order() =>
Observable.Create<OrderSecurityAlgo?>(o => {
OrderSecurityAlgo? last_fired = null;
orders_changed += notify;
_current_row.Changed += on_current_row_changed;
notify();
return Disposable.Create(dispose);
// local methods
void notify() {
var next = current_row == null ? (OrderSecurityAlgo?)null : current_row.order_tuple.order;
if (next != last_fired)
o.OnNext(last_fired = next);
}
void on_current_row_changed(OrderBookRowVM oldval, OrderBookRowVM newval) =>
notify();
void dispose() {
orders_changed -= notify;
_current_row.Changed -= on_current_row_changed;
}
});
public void row_selection_changed(SelectionChangedEventArgs e) {
try {
@@ -503,6 +550,7 @@ namespace mbts.spark {
_can_attach_selected_orders.refresh();
_can_detach_selected_orders.refresh();
selected_order_changed?.Invoke(current_order);
_custom_state_sync.sync();
} 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}");
@@ -566,7 +614,7 @@ namespace mbts.spark {
// resume column filter updates
_columns.filter_changed += _on_filter_changed;
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
var new_filter = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
var new_filter = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states(), _global_filter);
var new_sort = _columns.get_sorting_data();
if (!new_filter.Equals(_cached_filter_data)) {
_cached_filter_data = new_filter;
@@ -609,6 +657,9 @@ namespace mbts.spark {
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
internal uint get_display_qty_divisor(SecurityImmutable? security) =>
_model.security_auxiliary_model.get_display_quantity_divisor(security);
internal void show_order_modify(OrderBookRowVM row) =>
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
@@ -638,7 +689,7 @@ namespace mbts.spark {
}
void _on_filter_changed() {
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states(), _global_filter);
if (!fd.Equals(_cached_filter_data)) {
_cached_filter_data = fd;
_row_collection.filter = fd.build_predicate();
@@ -723,24 +774,62 @@ namespace mbts.spark {
}
}
class CustomStateSynchronizer {
readonly OrderBookVM _vm;
OrderBookRowVM _synced_row;
public CustomStateSynchronizer(OrderBookVM vm) =>
_vm = vm;
public void sync(bool force_refresh = false) {
var row = _vm.current_row != null &&
_vm.selected_rows.Select(r => r.order_tuple.root.order.Key).Distinct().TryGetSingle(out uint droot) &&
_vm.current_row.order_tuple.root.order.Key == droot ?
_vm.current_row : null;
if (force_refresh || row != _synced_row) {
_synced_row = row;
_refresh();
}
}
void _refresh() {
_vm.current_algo_custom_state.Clear();
if (_synced_row == null)
return;
var dcs = _synced_row.order.dynamic_custom_state();
// while dcs is not expected to be null, best to play it safe and double check.
if (dcs.is_null_or_empty()) {
var descendent_rows = _vm._row_collection.get_descendent_rows(_synced_row.order_key);
if (_vm._row_collection.filter != null)
descendent_rows = descendent_rows.Where(child => _vm._row_collection.filter(child.order_tuple));
foreach (var dr in descendent_rows) {
dcs = dr.order.dynamic_custom_state();
if (!dcs.is_null_or_empty())
break;
}
}
foreach (var p in dcs.empty_if_null().OrderBy(kv => kv.Key))
_vm.current_algo_custom_state.Add(new NamedValueVM(p.Key, DynamicOrderHelper.fmt_param(p.Value)));
}
}
public class OrderParamsVM : DisposableViewModelBase {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly OrderBookVM _owner;
readonly Property<bool> _is_dirty;
IRaiseCanExecuteChangedCommand _payup_bid_cmd, _payup_ask_cmd;
IAlgoParamVM<double> _price_param;
IAlgoParamVM<uint?> _size_param;
AlgoParamVM<uint?> _size_param;
AlgoParamPermissions
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
SecurityMD? _security_md;
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
public OrderParamsVM(OrderBookVM owner, AlgoEditingPermissions init_permissions) {
_owner = owner;
algo_vm = AlgoDefinitionVM.create(
owner._model,
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
init_format: init_format,
editing_permissions: init_permissions);
_accounts = backing(
nameof(selected_account),
@@ -766,7 +855,9 @@ namespace mbts.spark {
_price_param = null;
_size_param = null;
} else {
_size_param = new AlgoParamVM<uint?>("size", "Size", owner._selected_params_mon.size) {
_size_param = new AlgoParamVM<uint?>("size", "Size", owner._selected_params_mon.size, EqualityComparer<uint?>.Default,
to_display_value: v => v.HasValue ? v.Value / display_quantity_divisor : v,
from_display_value: v => v.HasValue ? v.Value * display_quantity_divisor : v) {
permissions = _size_permissions & _param_permissions
};
var price_param = new AlgoParamVM<double>("price", "Price", owner._selected_params_mon.price, spec?.tick_equality_comparer() ?? MathUtils.epsilon_eq_comparer) {
@@ -805,14 +896,16 @@ namespace mbts.spark {
public ReadOnlyObservableCollection<AccountTuple> accounts => _accounts.items;
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
public SecurityImmutable? security => _owner._selected_params_mon.security;
/// <see cref="security_md"/> should be synced with the root of the selected row(s),
public SecurityMD? security_md {
get => _security_md;
set {
if (Set(ref _security_md, value)) {
RaisePropertyChanged(nameof(security), nameof(spec));
RaisePropertyChanged(nameof(security), nameof(spec), nameof(display_quantity_divisor));
bbo.security_md = value;
_payup_bid_cmd.RaiseCanExecuteChanged();
_payup_ask_cmd.RaiseCanExecuteChanged();
_size_param?.refresh_display_value();
}
}
}
@@ -834,6 +927,7 @@ namespace mbts.spark {
public AlgoDefinitionVM algo_vm { get; }
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
public BBOVM bbo { get; }
public uint display_quantity_divisor => _owner.get_display_qty_divisor(security);
public ICommand send_order { get; }
public ICommand payup_bid_cmd => _payup_bid_cmd;
public ICommand payup_ask_cmd => _payup_ask_cmd;
@@ -879,13 +973,25 @@ namespace mbts.spark {
struct FilterData : IEquatable<FilterData> {
public readonly AlgoTreeDisplayMode algo_tree_display_mode;
public readonly FilterStates column_filters;
public readonly Func<OrderSecurityRootPair, bool> global_filter;
public FilterData(AlgoTreeDisplayMode algo_tree_display_mode, FilterStates column_filters) {
public FilterData(AlgoTreeDisplayMode algo_tree_display_mode, FilterStates column_filters, Func<OrderSecurityRootPair, bool> global_filter) {
this.algo_tree_display_mode = algo_tree_display_mode;
this.column_filters = column_filters;
this.global_filter = global_filter;
}
public Func<OrderSecurityRootPair, bool> build_predicate() {
if (global_filter == null)
return _build_predicate_core();
var core_pred = _build_predicate_core();
if (core_pred == null)
return global_filter;
var glbf = global_filter;
return new Func<OrderSecurityRootPair, bool>(o => glbf(o) && core_pred(o));
}
Func<OrderSecurityRootPair, bool> _build_predicate_core() {
var pred = column_filters.build_predicate();
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
if (pred == null) {
@@ -1,15 +1,18 @@
using bts.api;
using bts;
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.utils.csv;
using bts.view;
using bts.utils.csv;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
@@ -25,7 +28,10 @@ namespace mbts.spark {
const string _window_type = "OrderBook", _default_title = "Order Book";
readonly INotificationService _notification_service;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly bool _can_edit_specific_algo_params;
IDisposable _attachment_exit_sub;
// initialized in thread_init
List<IDisposable> _thread_init_resources;
OrderBookStartupData _state;
OrdersModel _model;
OrderBookVM _vm;
@@ -35,13 +41,14 @@ namespace mbts.spark {
SparkCommand[] _commands;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool can_edit_specific_algo_params)
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
_model = model;
_factory = factory;
_notification_service = notification_service;
_state = state;
_settings_manager = settings_manager;
_can_edit_specific_algo_params = can_edit_specific_algo_params;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
@@ -65,12 +72,14 @@ namespace mbts.spark {
public string thread_id { get; }
public override void Dispose() {
_commands.OfType<IDisposable>().DisposeAll();
Disposable.dispose(_thread_init_resources);
Disposable.dispose(ref _attachment_exit_sub);
Disposable.dispose(ref _vm);
}
public void thread_init() {
Log.assert(_thread_init_resources == null, $"{nameof(_thread_init_resources)} must be initialized in thread_init.");
_thread_init_resources = new List<IDisposable>();
_menu_display_vm = new SparkMenuDisplayVM();
if (_state != null)
_menu_display_vm.load_from(_state.menu_display);
@@ -148,6 +157,15 @@ namespace mbts.spark {
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
);
recreate_selected_orders_command.group = CommandGroups.Modify;
ICommand edit_algo_params_cmd;
if (_can_edit_specific_algo_params) {
_thread_init_resources.Add(CommandHelper.bind_param_source(
new DialogCommand<OrderSecurityAlgo?, Window>(mk_algo_params_dialog, has_algo_params),
_vm.subscribe_single_selected_order(),
out edit_algo_params_cmd, true));
} else {
edit_algo_params_cmd = DelegateCommand.empty;
}
var attach_selected_orders_command = new SparkCommandCommand(
"Attach To Selected",
@@ -195,26 +213,37 @@ namespace mbts.spark {
show_grid_settings_command.group = CommandGroups.Auxiliary;
_commands = new SparkCommand[] {
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
// modify commands
toggle_show_right_panel_command,
attach_selected_orders_command,
detach_selected_orders_command,
recreate_selected_orders_command,
new SparkCommandCommand("Algo Params...", Icons.Algo, edit_algo_params_cmd) {
group = CommandGroups.Modify,
show_in_toolbar = false,
show_in_context_menu = _can_edit_specific_algo_params
},
// cancel commands
cancel_selected_orders_command,
cancel_buy_orders_command,
cancel_sell_orders_command,
cancel_all_orders_command,
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command,
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
// hidden cmds (i.e., keyboard shortcuts at window level)
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
_menu_display_vm.spark_cmd
};
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
_thread_init_resources.AddRange(_commands.OfType<IDisposable>());
_thread_init_resources.Add(_vm.as_demuxer()
.subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title()));
_vm.selected_order_changed += vm_selected_order_changed;
_thread_init_resources.Add(Disposable.Create(() => _vm.selected_order_changed -= vm_selected_order_changed));
// local methods
string get_toggle_show_right_panel_cmd_title() =>
_vm.show_right_panel ? OrderBookView.CommandDescriptions.HideRightPanel : OrderBookView.CommandDescriptions.ShowRightPanel;
@@ -236,6 +265,36 @@ namespace mbts.spark {
attach_selected_orders_command.raiseCanExecuteChanged();
detach_selected_orders_command.raiseCanExecuteChanged();
}
bool has_algo_params(OrderSecurityAlgo? order) =>
order.HasValue && !OrderDataHelper.get_metadata(order.Value.algo.value.CustomCase).NoProps;
Window mk_algo_params_dialog(OrderSecurityAlgo? order) {
if (!has_algo_params(order))
return null;
var algo_def = AlgoDefinitionVM.create(_model, order.Value.algo, editing_permissions: AlgoEditingPermissions.AllowEditValues);
var algo_params_vm = new OrderBookAlgoParamsVM(algo_def, order.Value.order.is_non_terminal());
var dialog = new OrderBookAlgoParamsDialog {
Content = algo_params_vm
};
ui.WindowStyle.SetTitleContent(dialog, algo_def);
dialog.Closed += (s, e) => {
Disposable.dispose(ref algo_params_vm);
dialog = null;
};
algo_params_vm.RequestCommit += commit;
algo_params_vm.RequestClose += (s, e) => close_dialog();
return dialog;
void close_dialog() {
dialog?.Close();
dialog = null;
}
void commit(CustomOrderDefinitionImmutable value) {
_model.update_order(order.Value.order, new_algo: value).ignore();
close_dialog();
}
}
}
protected override void OnParentViewCreated(SparkWindowView parent_view) {
@@ -1,6 +1,7 @@
using bts.api;
using bts.shell.orderbook;
using bts.spark;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui;
@@ -15,8 +16,11 @@ namespace bts.shell {
readonly INotificationServiceDataCache _notification_service_data_cache;
readonly OrdersModel _model;
readonly UserSettingsHub _settings_hub;
readonly OrderBookConfig _config;
public OrderBookWindowFactory(IShellModule owner, IBus bus, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache, OrdersModel model, UserSettingsHub settings_hub) : base(owner, "SparkOrderBookWindow") {
var raw_config = Configuration.get<bts.config.OrderBookOptions>();
_config = new OrderBookConfig(raw_config.can_edit_specific_algo_params);
_bus = bus;
_notification_service = notification_service;
_notification_service_data_cache = notification_service_data_cache;
@@ -29,7 +33,8 @@ namespace bts.shell {
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
(us, s) => us.with(order_book_grid: s));
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state,
SparkCommands.ShowOrders.icon, settings_manager.as_non_shared(), _config.can_edit_specific_algo_params);
return Task.FromResult<IShellWindow>(w);
}
@@ -37,5 +42,12 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
readonly struct OrderBookConfig {
public readonly bool can_edit_specific_algo_params;
public OrderBookConfig(bool can_edit_specific_algo_params) =>
this.can_edit_specific_algo_params = can_edit_specific_algo_params;
}
}
}
@@ -0,0 +1,40 @@
<Window
x:Class="bts.shell.orderbook.OrderBookAlgoParamsDialog"
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:ob="clr-namespace:bts.shell.orderbook"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
u:WindowStyle.SizeToContent="WidthAndHeight"
Margin="0"
Padding="0"
Style="{StaticResource BTSWindowTitleTemplate}"
>
<u:WindowStyle.TitleContentTemplate>
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<usp:OrderTypeItem Margin="4,0" DisplayName="{Binding name, StringFormat='{}{0} Params'}" BadgeFill="{Binding badge_fill}"/>
</StackPanel>
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
<Window.ContentTemplate>
<DataTemplate DataType="{x:Type ob:OrderBookAlgoParamsVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<usp:AlgoEditorGridView
Width="390"
Margin="5"
AlgoParamRows="{Binding param_rows}"
CanUserChangeAlgoRoot="False"
/>
<c:DialogSkirt Grid.Row="1" RightItems="{Binding dialog_skirt}"/>
</Grid>
</DataTemplate>
</Window.ContentTemplate>
</Window>
@@ -0,0 +1,49 @@
using bts.api.orderman;
using bts.collections;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.Windows;
namespace bts.shell.orderbook {
/// <summary>
/// Interaction logic for OrderBookAlgoParamsDialog.xaml
/// </summary>
partial class OrderBookAlgoParamsDialog : Window {
public OrderBookAlgoParamsDialog() =>
InitializeComponent();
}
class OrderBookAlgoParamsVM : DisposableViewModelBase, IRequestViewClose {
readonly ObservableAggregateCollection<AlgoParamRowVM> _param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly DialogSkirtItem _commit, _cancel;
public OrderBookAlgoParamsVM(AlgoDefinitionVM algo_definition, bool can_user_edit) {
this.algo_definition = algo_definition;
_dispose = AlgoParamRowsHelper.connect(algo_definition, _param_rows, false);
_commit = new DialogSkirtItem("OK", new DelegateCommand(commit, () => can_user_edit)) { is_default = true };
_cancel = new DialogSkirtItem("Cancel", new DelegateCommand(close)) { is_cancel = true };
// local methods
void commit() {
RequestCommit?.Invoke(new CustomOrderDefinitionImmutable(algo_definition.algo_root.created_assigned()));
close();
}
void close() => RequestClose?.Invoke(this, EventArgs.Empty);
}
public event EventHandler RequestClose;
public event Action<CustomOrderDefinitionImmutable> RequestCommit;
public AlgoDefinitionVM algo_definition { get; }
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows => _param_rows;
public IEnumerable<DialogSkirtItem> dialog_skirt {
get {
yield return _commit;
yield return _cancel;
}
}
}
}
@@ -62,7 +62,7 @@
</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="Command" Value="{Binding DataContext.size_calculator.set_actual_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
@@ -82,7 +82,7 @@
</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="Command" Value="{Binding DataContext.size_calculator.set_actual_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
@@ -101,7 +101,7 @@
<TextBlock.Inlines>
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
<Run Text="{Binding side, Mode=OneWay}" Foreground="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
<Run Text="{Binding quantity, Mode=OneWay}"/>
<Run Text="{Binding display_quantity, Mode=OneWay}"/>
<Run>@</Run>
<Run>
<Run.Text>
@@ -484,6 +484,7 @@
<Border Padding="12,12,12,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
@@ -495,7 +496,45 @@
AlgoParamRows="{Binding param_rows}"
IsReadOnly="{Binding DataContext.can_user_modify_selected_orders, ElementName=Root, Converter={StaticResource NotConverter}}"
/>
<StackPanel Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
<c:ExpanderToggleButton
x:Name="algo_state_toggle"
Grid.Row="1"
BorderThickness="0"
Content="Algo State"
HorizontalContentAlignment="Left"
Margin="0,4,0,0"
VerticalAlignment="Top"
Visibility="{Binding current_algo_custom_state.Count, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:DIYGrid Grid.Row="1" Rows="{Binding current_algo_custom_state}" Margin="0,28,0,4">
<c:DIYGrid.Visibility>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:BooleanAndConverter/>
<cv:BooleanToVisibilityConverter/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="current_algo_custom_state.Count" Mode="OneWay" Converter="{StaticResource IntNotZeroConverter}"/>
<Binding Path="IsChecked" ElementName="algo_state_toggle"/>
</MultiBinding>
</c:DIYGrid.Visibility>
<c:DIYGrid.Columns>
<c:DIYGridColumnCollection>
<c:DIYGridColumn id="Name" Header="Param" Width="176"/>
<c:DIYGridColumn id="Value" Header="Value" Width="176"/>
</c:DIYGridColumnCollection>
</c:DIYGrid.Columns>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
<c:TextCell c:DIYGrid.ColumnKey="Name" Text="{Binding name}"/>
<c:TextCell c:DIYGrid.ColumnKey="Value" Text="{Binding value}"/>
</Grid>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<StackPanel Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
<Button x:Name="modify_orders_button" Command="{Binding modify_selected_orders_cmd}">
<Button.Margin>
<Thickness Right="{StaticResource DialogSkirtButtonPadding.Right}"/>
@@ -5,6 +5,7 @@ using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.utils;
using System;
@@ -140,15 +141,18 @@ namespace bts.shell.pnl {
public PositionPriceSize summary =>
new PositionPriceSize(long_data.working_total, long_data.avg_price, short_data.working_total, short_data.avg_price);
public static WorkingOrderData Create(IEnumerable<OrderRecordImmutable> orders) =>
orders.Aggregate(Empty, (seed, order) => seed.Add(order));
public static WorkingOrderData Create(IEnumerable<OrderRecordImmutable> orders, QuantityConversionData qty_conversion) =>
Create(orders.Select(o => (o, qty_conversion)));
public WorkingOrderData Add(OrderRecordImmutable order) {
public static WorkingOrderData Create(IEnumerable<(OrderRecordImmutable order, QuantityConversionData qty_conversion)> orders) =>
orders.Aggregate(Empty, (seed, tup) => seed.Add(tup.order, tup.qty_conversion));
public WorkingOrderData Add(OrderRecordImmutable order, QuantityConversionData qty_conversion) {
if (order.working_quantity() <= 0)
return new WorkingOrderData(long_data, short_data);
switch (order.side()) {
case Side.Buy: return new WorkingOrderData(long_data.Add(order), short_data);
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order));
case Side.Buy: return new WorkingOrderData(long_data.Add(order, qty_conversion), short_data);
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order, qty_conversion));
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
}
}
@@ -165,18 +169,21 @@ namespace bts.shell.pnl {
}
readonly struct WorkingOrderSideData {
public readonly double avg_price;
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data;
public readonly double avg_price, display_avg_price;
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable order, QuantityConversionData qty_conversion)>> data;
public readonly int working_total;
public readonly int display_working_total;
WorkingOrderSideData(double avg_price, int working_total, ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordImmutable>> data) {
WorkingOrderSideData(double avg_price, int working_total, double display_avg_price, int display_working_total, ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>> data) {
this.avg_price = avg_price;
this.working_total = working_total;
this.display_avg_price = display_avg_price;
this.data = data;
this.display_working_total = display_working_total;
}
public static WorkingOrderSideData EmptyLong => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(new ReverseComparer<double>(OrderDataHelper.PriceComparer)));
public static WorkingOrderSideData EmptyShort => new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(OrderDataHelper.PriceComparer));
public static WorkingOrderSideData EmptyLong => new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(new ReverseComparer<double>(OrderDataHelper.PriceComparer)));
public static WorkingOrderSideData EmptyShort => new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(OrderDataHelper.PriceComparer));
public double top_price =>
data.Count > 0 ?
@@ -185,23 +192,35 @@ namespace bts.shell.pnl {
public int top_volume =>
data.Count > 0 ?
data.Values.First().Values.Sum(o => o.working_quantity()) :
data.Values.First().Values.Sum(o => o.order.working_quantity()) :
0;
public WorkingOrderSideData Add(OrderRecordImmutable order_record) {
uint order = order_record.Key;
public int display_top_volume =>
data.Count > 0 ?
data.Values.First().Values.Sum(o => o.qty_conversion.to_display(o.order.working_quantity())) :
0;
public WorkingOrderSideData Add(OrderRecordImmutable order_record) =>
Add(order_record, new QuantityConversionData());
public WorkingOrderSideData Add(OrderRecordImmutable order_record, QuantityConversionData qty_conversion) {
int working_qty = order_record.working_quantity();
double price = order_record.price();
if (working_qty <= 0)
throw new ArgumentException(nameof(working_qty));
uint order = order_record.Key;
int dworking_qty = qty_conversion.to_display(working_qty);
double price = order_record.price();
if (working_total == 0) {
return new WorkingOrderSideData(price, working_qty, data.Add(price, ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
Log.assert(display_working_total == 0);
return new WorkingOrderSideData(price, working_qty, display_avg_price, dworking_qty, data.Add(price, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>.Empty.Add(order, (order_record, qty_conversion))));
} else return new WorkingOrderSideData(
avg_price + ((price - avg_price) * ((double)working_qty / (working_total + working_qty))),
working_total + working_qty,
display_avg_price + ((price - display_avg_price) * ((double)dworking_qty / (display_working_total + dworking_qty))),
display_working_total + dworking_qty,
data.SetItem(price, data.TryGetValue(price, out var curr) ?
curr.Add(order, order_record) :
ImmutableDictionary<uint, OrderRecordImmutable>.Empty.Add(order, order_record)));
curr.Add(order, (order_record, qty_conversion)) :
ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>.Empty.Add(order, (order_record, qty_conversion))));
}
public WorkingOrderSideData Remove(uint order, double price) {
@@ -210,22 +229,30 @@ namespace bts.shell.pnl {
throw new KeyNotFoundException(nameof(price));
if (!wdata.TryGetValue(order, out var worder))
throw new KeyNotFoundException(nameof(order));
var wqty = worder.working_quantity();
var wqty = worder.order.working_quantity();
var dwqty = worder.qty_conversion.to_display(wqty);
wdata = wdata.Remove(order);
if (wdata.Count == 0)
tmp_data = tmp_data.Remove(price);
if (tmp_data.Count == 0)
return new WorkingOrderSideData(double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordImmutable>>(data.KeyComparer));
return new WorkingOrderSideData(double.NaN, 0, double.NaN, 0, ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData)>>(data.KeyComparer));
return new WorkingOrderSideData(
avg_price - ((price - avg_price) * (wqty / (working_total - wqty))),
working_total - wqty,
display_avg_price - ((price - display_avg_price) * (dwqty / (display_working_total - dwqty))),
display_working_total - dwqty,
tmp_data);
}
public override string ToString() {
if (data.Count > 0) {
var best_order = data.Values.First().Values.First();
return OrderUtils.order_collection_summary(best_order.price(), avg_price, best_order.working_quantity(), working_total, SecurityDataHelper.Defaults.ContractSpecificationImmutable);
return OrderUtils.order_collection_summary(
best_order.order.price(),
display_avg_price,
best_order.qty_conversion.to_display(best_order.order.working_quantity()),
display_working_total,
SecurityDataHelper.Defaults.ContractSpecificationImmutable);
}
return string.Empty;
}
@@ -248,21 +275,25 @@ namespace bts.shell.pnl {
public readonly PNLKey key;
public readonly SecurityImmutable? security;
public readonly QuantityConversionData qty_conversion;
public readonly string account_name;
public readonly PNLCalculationType calc_type;
public readonly PositionPriceSize day, raw_day, sod;
public readonly WorkingOrderData working;
public readonly double
cpp,
default_cpp,
bid_price,
ask_price,
last_price,
settlement_price;
public readonly double? override_cpp;
public PNLRowData(
PNLKey key,
double cpp = 0,
double default_cpp = 0,
double? override_cpp = null,
double bid_price = 0,
double ask_price = 0,
double last_price = 0,
@@ -273,10 +304,12 @@ namespace bts.shell.pnl {
WorkingOrderData? working = null,
SecurityImmutable? security = null,
string account_name = null,
PNLCalculationType calc_type = PNLCalculationType.Mid) {
PNLCalculationType calc_type = PNLCalculationType.Mid,
QuantityConversionData? qty_conversion = null) {
//
this.key = key;
this.cpp = cpp;
this.default_cpp = default_cpp;
this.override_cpp = override_cpp;
this.bid_price = bid_price;
this.ask_price = ask_price;
this.last_price = last_price;
@@ -288,6 +321,7 @@ namespace bts.shell.pnl {
this.security = security;
this.account_name = account_name;
this.calc_type = calc_type;
this.qty_conversion = qty_conversion ?? new QuantityConversionData();
}
[Flags]
@@ -305,6 +339,9 @@ namespace bts.shell.pnl {
AskLTESettle = Ask | Settle | 1 << 7
}
public string symbol => security?.pnl_symbol() ?? string.Empty;
public double effective_cpp => override_cpp ?? default_cpp;
/// Market/Calc
public double mid_price => (bid_price + ask_price) / 2;
public double pnl_price {
@@ -344,117 +381,155 @@ namespace bts.shell.pnl {
}
}
/// Sod
public double sod_pnl => cpp *
public double sod_pnl => effective_cpp *
DoubleUtil.nan_to_zero_sum(
sod_long_avg_price_delta_raw * sod_long_pos,
sod_short_avg_price_delta_raw * sod_short_pos
);
// long
public double sod_long_pos => sod.long_pos;
public double sod_long_avg_price => cpp * sod.long_avg;
public double sod_long_avg_price => effective_cpp * sod.long_avg;
public double sod_long_avg_price_raw => sod.long_avg;
public double sod_long_avg_price_delta => cpp * sod_long_avg_price_delta_raw;
public double sod_long_avg_price_delta => effective_cpp * sod_long_avg_price_delta_raw;
public double sod_long_avg_price_delta_raw => sod.long_pos == 0 ? double.NaN : long_pnl_price - sod.long_avg;
// short
public double sod_short_pos => sod.short_pos;
public double sod_short_avg_price => cpp * sod.short_avg;
public double sod_short_avg_price => effective_cpp * sod.short_avg;
public double sod_short_avg_price_raw => sod.short_avg;
public double sod_short_avg_price_delta => cpp * sod_short_avg_price_delta_raw;
public double sod_short_avg_price_delta => effective_cpp * sod_short_avg_price_delta_raw;
public double sod_short_avg_price_delta_raw => sod.short_pos == 0 ? double.NaN : sod.short_avg - short_pnl_price;
// net
public double sod_net_pos => sod.net_pos;
public double sod_net_avg_price_delta => cpp * sod_net_avg_price_delta_raw;
public double sod_net_avg_price_delta => effective_cpp * sod_net_avg_price_delta_raw;
public double sod_net_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_long_avg_price_delta_raw, sod.long_pos,
sod_short_avg_price_delta_raw, sod.short_pos);
// display
public double display_sod_net_pos => qty_conversion.to_display(sod_net_pos);
public double display_sod_long_pos => qty_conversion.to_display(sod_long_pos);
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
/// Day
public double day_pnl => cpp *
public double day_pnl => effective_cpp *
DoubleUtil.nan_to_zero_sum(
day_long_avg_price_delta_raw * day_long_pos,
day_short_avg_price_delta_raw * day_short_pos
);
// long
public double day_long_pos => day.long_pos;
public double day_long_avg_price => cpp * day.long_avg;
public double day_long_avg_price => effective_cpp * day.long_avg;
public double day_long_avg_price_raw => day.long_avg;
public double day_long_avg_price_delta => cpp * day_long_avg_price_delta_raw;
public double day_long_avg_price_delta => effective_cpp * day_long_avg_price_delta_raw;
public double day_long_avg_price_delta_raw => day.long_pos == 0 ? double.NaN : long_pnl_price - day.long_avg;
// short
public double day_short_pos => day.short_pos;
public double day_short_avg_price => cpp * day.short_avg;
public double day_short_avg_price => effective_cpp * day.short_avg;
public double day_short_avg_price_raw => day.short_avg;
public double day_short_avg_price_delta => cpp * day_short_avg_price_delta_raw;
public double day_short_avg_price_delta => effective_cpp * day_short_avg_price_delta_raw;
public double day_short_avg_price_delta_raw => day.short_pos == 0 ? double.NaN : day.short_avg - short_pnl_price;
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
// net
public double day_net_pos => day.net_pos;
public double day_net_avg_price_delta => cpp * day_net_avg_price_delta_raw;
public double day_net_avg_price_delta => effective_cpp * day_net_avg_price_delta_raw;
public double day_net_avg_price_delta_raw =>
MathUtils.weighted_average(
day_long_avg_price_delta_raw, day.long_pos,
day_short_avg_price_delta_raw, day.short_pos);
// display
public double display_day_net_pos => qty_conversion.to_display(day_net_pos);
public double display_day_long_pos => qty_conversion.to_display(day_long_pos);
public double display_day_short_pos => qty_conversion.to_display(day_short_pos);
/// Raw Day
public double raw_day_long_pos => raw_day.long_pos;
public double raw_day_short_pos => raw_day.short_pos;
public double raw_day_net_pos => raw_day.net_pos;
// display
public double display_raw_day_net_pos => qty_conversion.to_display(raw_day_net_pos);
public double display_raw_day_long_pos => qty_conversion.to_display(raw_day_long_pos);
public double display_raw_day_short_pos => qty_conversion.to_display(raw_day_short_pos);
/// Total
public double total_pnl => DoubleUtil.nan_to_zero_sum(sod_pnl, day_pnl);
// long
public double total_long_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, day.long_pos);
public double total_long_avg_price => cpp * total_long_avg_price_raw;
public double total_long_avg_price => effective_cpp * total_long_avg_price_raw;
public double total_long_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.long_avg, day.long_avg);
public double total_long_avg_price_delta => cpp * total_long_avg_price_delta_raw;
public double total_long_avg_price_delta => effective_cpp * total_long_avg_price_delta_raw;
public double total_long_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_long_avg_price_delta_raw, sod.long_pos,
day_long_avg_price_delta_raw, day.long_pos);
// short
public double total_short_pos => DoubleUtil.nan_to_zero_sum(sod.short_pos, day.short_pos);
public double total_short_avg_price => cpp * total_short_avg_price_raw;
public double total_short_avg_price => effective_cpp * total_short_avg_price_raw;
public double total_short_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.short_avg, day.short_avg);
public double total_short_avg_price_delta => cpp * total_short_avg_price_delta_raw;
public double total_short_avg_price_delta => effective_cpp * total_short_avg_price_delta_raw;
public double total_short_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_short_avg_price_delta_raw, sod.short_pos,
day_short_avg_price_delta_raw, day.short_pos);
// net
public double total_net_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos);
public double total_net_avg_price_delta => cpp * total_net_avg_price_delta_raw;
public double total_net_avg_price_delta => effective_cpp * total_net_avg_price_delta_raw;
public double total_net_avg_price_delta_raw =>
MathUtils.weighted_average(
total_long_avg_price_delta_raw, total_long_pos,
total_short_avg_price_delta_raw, total_short_pos);
// display
public double display_total_net_pos => qty_conversion.to_display(total_net_pos);
public double display_total_long_pos => qty_conversion.to_display(total_long_pos);
public double display_total_short_pos => qty_conversion.to_display(total_short_pos);
#region Working
// long
public double working_long_pos => working.long_data.working_total;
public double working_long_avg_price => cpp * working_long_avg_price_raw;
public double working_long_top_price => cpp * working_long_top_price_raw;
public double working_long_avg_price => effective_cpp * working_long_avg_price_raw;
public double working_long_top_price => effective_cpp * working_long_top_price_raw;
public double working_long_top_price_raw => working.top_bid;
public double working_long_avg_price_raw => working.long_data.avg_price;
public double working_long_avg_price_delta => cpp * working_long_avg_price_delta_raw;
public double working_long_avg_price_delta => effective_cpp * working_long_avg_price_delta_raw;
public double working_long_avg_price_delta_raw => working_long_pos == 0 ? double.NaN : long_pnl_price - working_long_avg_price_raw;
// short
public double working_short_pos => working.short_data.working_total;
public double working_short_avg_price => cpp * working_short_avg_price_raw;
public double working_short_top_price => cpp * working_short_top_price_raw;
public double working_short_avg_price => effective_cpp * working_short_avg_price_raw;
public double working_short_top_price => effective_cpp * working_short_top_price_raw;
public double working_short_top_price_raw => working.top_ask;
public double working_short_avg_price_raw => working.short_data.avg_price;
public double working_short_avg_price_delta => cpp * working_short_avg_price_delta_raw;
public double working_short_avg_price_delta => effective_cpp * working_short_avg_price_delta_raw;
public double working_short_avg_price_delta_raw => working_short_pos == 0 ? double.NaN : working_short_avg_price_raw - short_pnl_price;
// net
public double working_net_pos => working_long_pos - working_short_pos;
public double working_net_avg_price_delta => cpp * working_net_avg_price_delta_raw;
public double working_net_avg_price_delta => effective_cpp * working_net_avg_price_delta_raw;
public double working_net_avg_price_delta_raw =>
MathUtils.weighted_average(
working_long_avg_price_delta_raw, working_long_pos,
working_short_avg_price_delta_raw, working_short_pos);
// display
public double display_working_net_pos => qty_conversion.to_display(working_net_pos);
public double display_working_long_pos => qty_conversion.to_display(working_long_pos);
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
#endregion
public PNLRowData with(SecurityImmutable? security = null, string account_name = null, WorkingOrderData? working = null, PNLCalculationType? calc_type = null) =>
new PNLRowData(
key,
default_cpp,
override_cpp,
bid_price,
ask_price,
last_price,
settlement_price,
sod,
day,
raw_day,
working ?? this.working,
security ?? this.security,
account_name ?? this.account_name,
calc_type ?? this.calc_type,
qty_conversion: qty_conversion);
WaterfallPriceFlags _waterfall_flags() {
var flags = WaterfallPriceFlags.None;
if (!double.IsNaN(bid_price)) flags |= WaterfallPriceFlags.Bid;
@@ -496,7 +571,8 @@ namespace bts.shell.pnl {
public bool Equals(PNLRowData other) =>
key == other.key &&
cpp == other.cpp &&
default_cpp == other.default_cpp &&
override_cpp == other.override_cpp &&
security.HasValue == other.security.HasValue &&
account_name == other.account_name &&
sod == other.sod &&
@@ -508,7 +584,8 @@ namespace bts.shell.pnl {
settlement_price.Equals(other.settlement_price) &&
calc_type.Equals(other.calc_type) &&
raw_day == other.raw_day &&
working.summary == other.working.summary;
working.summary == other.working.summary &&
qty_conversion == other.qty_conversion;
public override bool Equals(object obj) =>
obj is PNLRowData other && Equals(other);
@@ -523,9 +600,12 @@ namespace bts.shell.pnl {
}
static class PNLRowDataFactory {
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value) t) =>
new PNLRowData(new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
t.value.CashValue,
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value, QuantityConversionData qty_conversion) t) {
var (default_cpp, override_cpp) = t.value.pnl_cpp_tuple(t.sec);
return new PNLRowData(
new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
default_cpp,
override_cpp,
t.value.BidPrice,
t.value.AskPrice,
t.value.LastPrice,
@@ -535,25 +615,27 @@ namespace bts.shell.pnl {
t.value.Pos.raw_day_pps(),
WorkingOrderData.Empty,
t.sec,
t.value.Pos.Account);
t.value.Pos.Account,
qty_conversion: t.qty_conversion);
}
}
static class PNLDataExtensions {
// Note: PNLRowData is passed by ref because it is fairly large struct, and since it is a readonly struct, we know it cannot be modified (so might as well avoid excessive copying).
public static PNLRowData with(this PNLRowData pnl_data, SecurityImmutable? security = null, string account_name = null, WorkingOrderData? working = null, PNLCalculationType? calc_type = null) =>
new PNLRowData(
pnl_data.key,
pnl_data.cpp,
pnl_data.bid_price,
pnl_data.ask_price,
pnl_data.last_price,
pnl_data.settlement_price,
pnl_data.sod,
pnl_data.day,
pnl_data.raw_day,
working ?? pnl_data.working,
security ?? pnl_data.security,
account_name ?? pnl_data.account_name,
calc_type ?? pnl_data.calc_type);
public static string pnl_symbol(this SecurityImmutable security) {
return bts.api.ExchangeSpecificUitlities.get_pnl_symbol(security);
}
// while override_cpp could use double.NaN to indicate the lack of an override,
// the nullable double makes it clear that it is an optional value.
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable? security) =>
security.HasValue ?
pnl_cpp_tuple(pnl_unit, security.Value) :
(pnl_unit.CashValue, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable security) {
// TODO: actual impl
double calced_pnl = bts.api.ExchangeSpecificUitlities.get_cpp(pnl_unit,security);
return (calced_pnl, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
}
}
}
}
@@ -16,7 +16,15 @@ namespace bts.shell.pnl {
decimal sod_long_pos,
decimal sod_short_pos,
decimal working_long_pos = 0,
decimal working_short_pos = 0) {
decimal working_short_pos = 0,
decimal? display_day_long_pos = null,
decimal? display_day_short_pos = null,
decimal? display_raw_day_long_pos = null,
decimal? display_raw_day_short_pos = null,
decimal? display_sod_long_pos = null,
decimal? display_sod_short_pos = null,
decimal? display_working_long_pos = null,
decimal? display_working_short_pos = null) {
//
this.day_pnl = day_pnl;
this.day_long_pos = day_long_pos;
@@ -28,6 +36,15 @@ namespace bts.shell.pnl {
this.sod_short_pos = sod_short_pos;
this.working_long_pos = working_long_pos;
this.working_short_pos = working_short_pos;
// display
this.display_day_long_pos = display_day_long_pos ?? day_long_pos;
this.display_day_short_pos = display_day_short_pos ?? day_short_pos;
this.display_raw_day_long_pos = display_raw_day_long_pos ?? raw_day_long_pos;
this.display_raw_day_short_pos = display_raw_day_short_pos ?? raw_day_short_pos;
this.display_sod_long_pos = display_sod_long_pos ?? sod_long_pos;
this.display_sod_short_pos = display_sod_short_pos ?? sod_short_pos;
this.display_working_long_pos = display_working_long_pos ?? working_long_pos;
this.display_working_short_pos = display_working_short_pos ?? working_short_pos;
}
// sod
@@ -35,24 +52,48 @@ namespace bts.shell.pnl {
public decimal sod_long_pos { get; }
public decimal sod_short_pos { get; }
public decimal sod_net_pos => sod_long_pos - sod_short_pos;
// sod display
public decimal display_sod_long_pos { get; }
public decimal display_sod_short_pos { get; }
public decimal display_sod_net_pos => display_sod_long_pos - display_sod_short_pos;
// day
public decimal day_pnl { get; }
public decimal day_long_pos { get; }
public decimal day_short_pos { get; }
public decimal day_net_pos => day_long_pos - day_short_pos;
// display
public decimal display_day_long_pos { get; }
public decimal display_day_short_pos { get; }
public decimal display_day_net_pos => display_day_long_pos - display_day_short_pos;
// raw day
public decimal raw_day_long_pos { get; }
public decimal raw_day_short_pos { get; }
public decimal raw_day_net_pos => raw_day_long_pos - raw_day_short_pos;
// display
public decimal display_raw_day_long_pos { get; }
public decimal display_raw_day_short_pos { get; }
public decimal display_raw_day_net_pos => display_raw_day_long_pos - display_raw_day_short_pos;
// total
public decimal total_pnl => sod_pnl + day_pnl;
public decimal total_net_pos => total_long_pos - total_short_pos;
public decimal total_long_pos => sod_long_pos + day_long_pos;
public decimal total_short_pos => sod_short_pos + day_short_pos;
// display
public decimal display_total_long_pos => display_sod_long_pos + display_day_long_pos;
public decimal display_total_short_pos => display_sod_short_pos + display_day_short_pos;
public decimal display_total_net_pos => display_total_long_pos - display_total_short_pos;
// working
public decimal working_long_pos { get; }
public decimal working_net_pos => working_long_pos - working_short_pos;
public decimal working_short_pos { get; }
public decimal working_net_pos => working_long_pos - working_short_pos;
// display
public decimal display_working_long_pos { get; }
public decimal display_working_short_pos { get; }
public decimal display_working_net_pos => display_working_long_pos - display_working_short_pos;
public bool Equals(PNLNormalizedData other) =>
day_pnl == other.day_pnl &&
@@ -64,6 +105,17 @@ namespace bts.shell.pnl {
sod_long_pos == other.sod_long_pos &&
sod_short_pos == other.sod_short_pos &&
working_long_pos == other.working_long_pos &&
working_short_pos == other.working_short_pos &&
// display
sod_long_pos == other.sod_long_pos &&
sod_short_pos == other.sod_short_pos &&
day_long_pos == other.day_long_pos &&
day_short_pos == other.day_short_pos &&
raw_day_long_pos == other.raw_day_long_pos &&
raw_day_short_pos == other.raw_day_short_pos &&
total_long_pos == other.total_long_pos &&
total_short_pos == other.total_short_pos &&
working_long_pos == other.working_long_pos &&
working_short_pos == other.working_short_pos;
public override bool Equals(object obj) =>
@@ -102,7 +154,16 @@ namespace bts.shell.pnl {
a.sod_long_pos + b.sod_long_pos,
a.sod_short_pos + b.sod_short_pos,
a.working_long_pos + b.working_long_pos,
a.working_short_pos + b.working_short_pos);
a.working_short_pos + b.working_short_pos,
// display
a.display_day_long_pos + b.display_day_long_pos,
a.display_day_short_pos + b.display_day_short_pos,
a.display_raw_day_long_pos + b.display_raw_day_long_pos,
a.display_raw_day_short_pos + b.display_raw_day_short_pos,
a.display_sod_long_pos + b.display_sod_long_pos,
a.display_sod_short_pos + b.display_sod_short_pos,
a.display_working_long_pos + b.display_working_long_pos,
a.display_working_short_pos + b.display_working_short_pos);
public static PNLNormalizedData operator -(PNLNormalizedData a, PNLNormalizedData b) =>
new PNLNormalizedData(
@@ -115,6 +176,15 @@ namespace bts.shell.pnl {
a.sod_long_pos - b.sod_long_pos,
a.sod_short_pos - b.sod_short_pos,
a.working_long_pos - b.working_long_pos,
a.working_short_pos - b.working_short_pos);
a.working_short_pos - b.working_short_pos,
// display
a.display_day_long_pos - b.display_day_long_pos,
a.display_day_short_pos - b.display_day_short_pos,
a.display_raw_day_long_pos - b.display_raw_day_long_pos,
a.display_raw_day_short_pos - b.display_raw_day_short_pos,
a.display_sod_long_pos - b.display_sod_long_pos,
a.display_sod_short_pos - b.display_sod_short_pos,
a.display_working_long_pos - b.display_working_long_pos,
a.display_working_short_pos - b.display_working_short_pos);
}
}
@@ -1,4 +1,5 @@
using bts.api;
using bts.api.position;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
@@ -11,6 +12,7 @@ using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.property_editor;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -22,6 +24,7 @@ using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
@@ -34,11 +37,13 @@ namespace bts.shell {
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
readonly IPositionModel _position_model;
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model, IPositionModel position_model) : base(owner, _window_type) {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_position_model = position_model;
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
@@ -105,6 +110,8 @@ namespace bts.shell {
menu_display: menu_display_vm.create());
return new PNLSaveState { use_defaults = false, settings = state };
};
IDisposable cpp_override_dialog = null;
IEnumerable<SparkCommand> commands = new[] {
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
@@ -116,6 +123,26 @@ namespace bts.shell {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Override CPP...", Icons.CurrencyExchange, new DelegateCommand(() => {
Disposable.dispose(ref cpp_override_dialog);
var security_keys = vm.selected_rows.Select(r => r.security_key.value).Distinct().ToList();
double initial_cpp = vm.selected_rows.Select(r => r.cpp.value).Distinct().SafeSingleOrDefault();
override_cpp_async().ignore();
async Task override_cpp_async() {
try {
var cts = new CancellationTokenSource();
cpp_override_dialog = Disposable.Create(new Action(cts.Cancel));
var (cpp, committed) = await PropertyEditor.get_double_async("Override CPP", "value", cts.Token, initial_cpp);
if (committed && cpp.is_real_positive())
await _position_model.override_cpp_async(security_keys, cpp);
} catch (Exception ex) {
Log.Ui.warn($"Error overriding cpp: {ex}.");
}
}
})) {
show_in_context_menu = true,
show_in_toolbar = false
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
@@ -190,6 +217,7 @@ namespace bts.shell {
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
Disposable.Create(() => Disposable.dispose(ref cpp_override_dialog)),
menu_display_vm,
vm);
return (content, commands, get_save_state, dispose);
@@ -384,5 +412,17 @@ namespace bts.shell {
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class InitializeDataFormattingMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && settings.data_formatting == null) {
new_settings = settings.with(data_formatting: PNLSettings.Default.data_formatting);
return true;
}
new_settings = null;
return false;
}
}
}
}
@@ -204,11 +204,11 @@ namespace bts.shell.pnl {
// impl (add all)
bd.add_discrete_values(fs.account, d => d.key.account_id);
bd.add_discrete_values(fs.exchange, d => d.security.exchange_or_default());
bd.add_discrete_values(fs.symbol, d => d.security.symbol_or_default());
bd.add_discrete_values(fs.symbol, d => d.symbol);
bd.add_discrete_values(fs.security_type, d => d.security.HasValue ? d.security.Value.ContractCase : Security.ContractOneofCase.None);
bd.add_regex(fs.security, d => d.security.ToString());
bd.add_regex(fs.security_key, d => d.key.security_id.ToString());
bd.add_numeric(fs.cpp, d => d.cpp);
bd.add_numeric(fs.cpp, d => d.effective_cpp);
/// sod
bd.add_numeric(fs.sod_pnl, d => d.sod_pnl);
bd.add_numeric(fs.sod_long_pos, d => d.sod_long_pos);
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,52 @@
using bts.utils;
using mbts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
namespace bts.shell.pnl {
class PNLDataFormattingVM : ViewModelBase, IValueFactory<PNLDataFormatting> {
const uint _min_decimal_places = 1, _max_decimal_places = 6;
uint _market_price_decimal_places, _fill_price_decimal_places;
public uint market_price_decimal_places {
get => _market_price_decimal_places;
set => Set(ref _market_price_decimal_places, value);
}
public uint fill_price_decimal_places {
get => _fill_price_decimal_places;
set => Set(ref _fill_price_decimal_places, value);
}
public IEnumerable<uint> decimal_place_options {
get {
for (uint n = _min_decimal_places; n <= _max_decimal_places; n++)
yield return n;
}
}
public PNLDataFormatting create() =>
new PNLDataFormatting {
fill_price_display = new DoubleFormatting {
tick_factors = new mbts.user.settings.TickFactors(
Math.Pow(10, MathUtils.within_range(_min_decimal_places, _max_decimal_places, fill_price_decimal_places)),
1)
},
market_price_display = new DoubleFormatting {
tick_factors = new mbts.user.settings.TickFactors(
Math.Pow(10, MathUtils.within_range(_min_decimal_places, _max_decimal_places, market_price_decimal_places)),
1)
}
};
public void load_from(PNLDataFormatting value) {
if (value != null) {
if (value.market_price_display?.tick_factors != null) {
market_price_decimal_places = (uint)Math.Log10(value.market_price_display.tick_factors.tick_base);
}
if (value.fill_price_display?.tick_factors != null) {
fill_price_decimal_places = (uint)Math.Log10(value.fill_price_display.tick_factors.tick_base);
}
}
}
}
}
@@ -14,7 +14,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
@@ -52,6 +51,7 @@ namespace bts.shell.pnl {
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLDataFormattingVM data_formatting_vm { get; } = new PNLDataFormattingVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
@@ -63,6 +63,7 @@ namespace bts.shell.pnl {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
s.data_formatting = data_formatting_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
List<GridColumnSettings> new_cols = null;
IEnumerable<GridColumnSettings> cols = s.grid.columns;
@@ -136,6 +137,7 @@ namespace bts.shell.pnl {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
data_formatting_vm.load_from(value.data_formatting);
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
@@ -154,7 +156,7 @@ namespace bts.shell.pnl {
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
Loaded 100 of 679 files, more files were not shown because too many files have changed in this diff. Show more