Compare commits
318
Commits
2.10.0
..
2.13.0_rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89fb79c556 | ||
|
|
c92e9df77b | ||
|
|
81aaa853d5 | ||
|
|
4f308faba2 | ||
|
|
0b924033c2 | ||
|
|
3606c707e4 | ||
|
|
5998107e6a | ||
|
|
297982433b | ||
|
|
15ac9d945b | ||
|
|
a0f1d11692 | ||
|
|
e7890a459c | ||
|
|
f7fb5ed400 | ||
|
|
75e98d353f | ||
|
|
5fc6ff4c00 | ||
|
|
6951272633 | ||
|
|
548611b44f | ||
|
|
322da04723 | ||
|
|
3544eac815 | ||
|
|
7fef7d88ce | ||
|
|
7531cf5eb4 | ||
|
|
5826a083c0 | ||
|
|
ec0d5d22f1 | ||
|
|
d433368795 | ||
|
|
dc4453e510 | ||
|
|
1b1583abda | ||
|
|
9d2f2888bb | ||
|
|
26816f0048 | ||
|
|
e97e6b93c7 | ||
|
|
0d8252e131 | ||
|
|
801b9622f8 | ||
|
|
a5df3f2da2 | ||
|
|
f03a413855 | ||
|
|
f3a692bda2 | ||
|
|
6c3bdbdc34 | ||
|
|
d4749dd381 | ||
|
|
a008f4d0fd | ||
|
|
0ee9521454 | ||
|
|
62cef7c2f7 | ||
|
|
1b39f4e47c | ||
|
|
3212b2fcee | ||
|
|
1576541bd1 | ||
|
|
62e97364fb | ||
|
|
93b954ce46 | ||
|
|
c9da022c58 | ||
|
|
0772a13b09 | ||
|
|
c53f6ad906 | ||
|
|
1058c8a80e | ||
|
|
7ecb106880 | ||
|
|
b0c1d93c50 | ||
|
|
0c5a039226 | ||
|
|
2677144bfa | ||
|
|
6b30c42c0d | ||
|
|
2f62d29f66 | ||
|
|
828f867a80 | ||
|
|
c9663cf7ab | ||
|
|
b85de51d0c | ||
|
|
dc8e65923d | ||
|
|
1469373b9c | ||
|
|
820e937336 | ||
|
|
8b73878782 | ||
|
|
b15106dac4 | ||
|
|
100316b791 | ||
|
|
3c98425226 | ||
|
|
1aa9c46367 | ||
|
|
5211cab351 | ||
|
|
e89a4cce53 | ||
|
|
cb56b90737 | ||
|
|
761a89f052 | ||
|
|
cab6b56606 | ||
|
|
8a743a91a5 | ||
|
|
5dec5dc392 | ||
|
|
27aa4fca26 | ||
|
|
84a2e50bf7 | ||
|
|
297eb5b25b | ||
|
|
d9d0e1f4ef | ||
|
|
6ec0cc8bbe | ||
|
|
795b77ea3f | ||
|
|
91c97d32bb | ||
|
|
e0c50703d7 | ||
|
|
dab1e76766 | ||
|
|
b8c927df27 | ||
|
|
2684d25f09 | ||
|
|
7c95a77ae2 | ||
|
|
31b115d13b | ||
|
|
d681a6924b | ||
|
|
d0cce84d2e | ||
|
|
532591c34f | ||
|
|
562b9419bd | ||
|
|
7f187224c1 | ||
|
|
79b2121f5e | ||
|
|
0bd477594c | ||
|
|
149cd90fb4 | ||
|
|
f74e719a9f | ||
|
|
8b68beae96 | ||
|
|
9cc0b471f2 | ||
|
|
3e035197d0 | ||
|
|
c9d8a640b6 | ||
|
|
1d97e620fd | ||
|
|
f6299ab5c3 | ||
|
|
eaff4774e0 | ||
|
|
bed52db98a | ||
|
|
bd3ef33607 | ||
|
|
25a594895e | ||
|
|
4d468c8026 | ||
|
|
68ed90487b | ||
|
|
ddc2c67fea | ||
|
|
77459e9c64 | ||
|
|
db64f2f45b | ||
|
|
3f7ea71de7 | ||
|
|
d334418655 | ||
|
|
d1844afeae | ||
|
|
b6dc2bd4d4 | ||
|
|
58906eb3b6 | ||
|
|
50e61604ba | ||
|
|
6bd23f106f | ||
|
|
dfd8348724 | ||
|
|
963a4207e2 | ||
|
|
d2e9352e48 | ||
|
|
a39d91ceb3 | ||
|
|
4e58342677 | ||
|
|
5eeb5a534c | ||
|
|
032836dea4 | ||
|
|
f20c53bced | ||
|
|
a06461eb5e | ||
|
|
34e7d6a617 | ||
|
|
31e44d8c10 | ||
|
|
8fdab4a510 | ||
|
|
385adaa1b0 | ||
|
|
f9506bc9ad | ||
|
|
6b5b517d70 | ||
|
|
80850146c1 | ||
|
|
0680dddab4 | ||
|
|
70aa62c0cf | ||
|
|
06165b3842 | ||
|
|
f21902d60d | ||
|
|
a27f383b62 | ||
|
|
35d4fcc513 | ||
|
|
8a96b00f64 | ||
|
|
2d06fd3d92 | ||
|
|
76050e0722 | ||
|
|
a3f0c2fbf6 | ||
|
|
1ccc4c3746 | ||
|
|
e5d85db888 | ||
|
|
e47b1c3048 | ||
|
|
039791a296 | ||
|
|
e6884b6654 | ||
|
|
8cbae28dd8 | ||
|
|
010a1fecb1 | ||
|
|
96bec55ee7 | ||
|
|
424bb36fc7 | ||
|
|
c24700195f | ||
|
|
f7184b0949 | ||
|
|
999973c349 | ||
|
|
4663ae9e4d | ||
|
|
34fc6af137 | ||
|
|
6cc25a7337 | ||
|
|
48b62f1cab | ||
|
|
5a0ceafa66 | ||
|
|
d31969606e | ||
|
|
d7d170f228 | ||
|
|
cf977befd1 | ||
|
|
1d538b4cb0 | ||
|
|
b42febdf60 | ||
|
|
71915de4b0 | ||
|
|
facb3249d0 | ||
|
|
f323efc61a | ||
|
|
be5f54326e | ||
|
|
65cf042639 | ||
|
|
57526a5df5 | ||
|
|
7d26ff641e | ||
|
|
a8ffe3a6ec | ||
|
|
bda135c435 | ||
|
|
c9b2ac290c | ||
|
|
0fa97aeb56 | ||
|
|
d1422ddc58 | ||
|
|
340d65a47c | ||
|
|
51ffa7e7f7 | ||
|
|
2a89c3721b | ||
|
|
0156bbfc46 | ||
|
|
f89fb76cb2 | ||
|
|
16adb9ee18 | ||
|
|
154a637ee1 | ||
|
|
8b583fcbec | ||
|
|
48dfded132 | ||
|
|
baaa3c4f98 | ||
|
|
838550d78a | ||
|
|
bf8323e79b | ||
|
|
399e4bf654 | ||
|
|
5908b4478e | ||
|
|
e6ab7d825a | ||
|
|
c78e6e8315 | ||
|
|
44571a5af5 | ||
|
|
62274f5129 | ||
|
|
b5b5101faf | ||
|
|
bb232d8f1e | ||
|
|
1b3f5bc8a6 | ||
|
|
1b767f6e5e | ||
|
|
fe647044f8 | ||
|
|
aaca502500 | ||
|
|
7d2695e602 | ||
|
|
69dcfdb2bd | ||
|
|
e179f7e5a4 | ||
|
|
dba3747d98 | ||
|
|
3e7627ac24 | ||
|
|
1b043f77cb | ||
|
|
14cbd4a174 | ||
|
|
cc3353e243 | ||
|
|
683b85c66b | ||
|
|
789710d841 | ||
|
|
1a9ff09f43 | ||
|
|
13b09ffc12 | ||
|
|
83a2a01789 | ||
|
|
e1137c4e1d | ||
|
|
0a6cc0cdf1 | ||
|
|
e96d9112ef | ||
|
|
72aa028bd6 | ||
|
|
f8d5b9bace | ||
|
|
e861e2916f | ||
|
|
23738b109d | ||
|
|
713e11d146 | ||
|
|
444e3154b3 | ||
|
|
270ee60df9 | ||
|
|
1ce041c5e5 | ||
|
|
50940bfd13 | ||
|
|
8cb4151f63 | ||
|
|
7a47b7a85a | ||
|
|
a60a792dfa | ||
|
|
a73a1cc4f5 | ||
|
|
32fbdf8a21 | ||
|
|
75c54bbe3e | ||
|
|
38575039b9 | ||
|
|
43a49b9399 | ||
|
|
618c41ce97 | ||
|
|
bd8c9c354b | ||
|
|
8f5d062a3b | ||
|
|
e1a3950051 | ||
|
|
7e816c1503 | ||
|
|
15d9afb2de | ||
|
|
b5468e5565 | ||
|
|
a4c3a61f13 | ||
|
|
7ce101f7f7 | ||
|
|
b61cab397d | ||
|
|
222e1a51dc | ||
|
|
9fa3464188 | ||
|
|
fee695d3d9 | ||
|
|
140f1d88ea | ||
|
|
c4eeaf2ec8 | ||
|
|
54dd30245f | ||
|
|
263ff63a37 | ||
|
|
f3d16502de | ||
|
|
4b7db298c4 | ||
|
|
65f41452fb | ||
|
|
d7a2ae4b09 | ||
|
|
61ac626542 | ||
|
|
8d7a2f342e | ||
|
|
0db037175f | ||
|
|
98aa463b4e | ||
|
|
0b40d3971d | ||
|
|
a21a769909 | ||
|
|
ca8e540453 | ||
|
|
134950747f | ||
|
|
5abc17adf3 | ||
|
|
f3cd3a402e | ||
|
|
c7217b8475 | ||
|
|
99c5129ea7 | ||
|
|
d025ee0c89 | ||
|
|
c24ddaa190 | ||
|
|
442aa156ac | ||
|
|
e906d41cc2 | ||
|
|
6e09d9e949 | ||
|
|
aea02e212a | ||
|
|
a94a66234a | ||
|
|
4cbbf6972a | ||
|
|
caa9671c96 | ||
|
|
20eb3ed39d | ||
|
|
772a898433 | ||
|
|
b5997d326e | ||
|
|
f8626f81cd | ||
|
|
bc208d2421 | ||
|
|
625ebcc762 | ||
|
|
0a163badf0 | ||
|
|
dea95455a0 | ||
|
|
f0f5988abf | ||
|
|
db5f16985c | ||
|
|
f0105867be | ||
|
|
2c0b314428 | ||
|
|
3cd74f33f1 | ||
|
|
a6ee36f4bc | ||
|
|
9cadf635fb | ||
|
|
43e043b044 | ||
|
|
6bac75d7ab | ||
|
|
7ceb170c5f | ||
|
|
47c7ee71b4 | ||
|
|
4f58452771 | ||
|
|
d5bf15cc36 | ||
|
|
9736541385 | ||
|
|
5591c8316d | ||
|
|
a44d3a2c26 | ||
|
|
dceb300324 | ||
|
|
84b9ca3096 | ||
|
|
17e9f1ea96 | ||
|
|
946ec36840 | ||
|
|
11425a898f | ||
|
|
d0029e7f88 | ||
|
|
bdbdda947f | ||
|
|
f88ab57fa0 | ||
|
|
36442a037e | ||
|
|
5969568dee | ||
|
|
96265ea187 | ||
|
|
9676b1fdca | ||
|
|
556132c2dd | ||
|
|
a22b7b7189 | ||
|
|
8e41a42ced | ||
|
|
e0f7bdfc13 | ||
|
|
ec0b96215f | ||
|
|
e27a8be526 | ||
|
|
70cd9f185e | ||
|
|
33eaf6f86d |
No files matched your search
@@ -4,17 +4,36 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.12.0
|
||||
### User Visible Changes
|
||||
- Added support for ICE market data and order execution
|
||||
### Internal Changes
|
||||
- Lots of permission / admin changes to facilitate ICE
|
||||
|
||||
## 2.11.0
|
||||
### User Visible Changes
|
||||
- Fixed fills form time filter to also apply to SOD fills
|
||||
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
|
||||
- Removed the option to enter manual spread fills
|
||||
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
|
||||
### Internal Changes
|
||||
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
|
||||
- To facilitate support, allowed the bts user to view and edit algos from all other users
|
||||
- Improved system tests
|
||||
- Improved log messages in UI to include dispatcher thread name
|
||||
- Added support for Windows core dumps in the UI
|
||||
|
||||
## 2.10.0
|
||||
### User Visible Changes
|
||||
- Fills now include information about the algo that originated the fill
|
||||
- A new, more powerful PnL form is now available
|
||||
### API Changes
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
### Bug Fixes
|
||||
- Brought back the "select all" check box in the mini order book
|
||||
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
|
||||
### Internal Changes
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
## 2.9.3
|
||||
### Internal Changes
|
||||
- Fixed system test scripts
|
||||
@@ -30,7 +49,7 @@ insert_anchor_links = "right"
|
||||
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
|
||||
### Api
|
||||
- C# API sample now uses synchronous calls.
|
||||
|
||||
|
||||
## 2.9.0
|
||||
### User Visible Changes
|
||||
- Improved Workspace management User Interface
|
||||
@@ -38,11 +57,11 @@ insert_anchor_links = "right"
|
||||
- The Spark UI Window group now has a title in the Windows task bar
|
||||
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
|
||||
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
|
||||
- Added import facilities for GMI position files
|
||||
- Added import facilities for GMI position files
|
||||
- Added the ability for risk managers to enter manual fills
|
||||
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
|
||||
### Bug Fixes
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the method used to determine if a security is expired to take into account the start of day time
|
||||
### API Changes
|
||||
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
|
||||
@@ -79,10 +98,10 @@ insert_anchor_links = "right"
|
||||
- Made it easier to generate simple UI's for new order types
|
||||
### API Changes
|
||||
- Dynamic orders can now use QuantityMeasure parameter types
|
||||
|
||||
|
||||
## 2.7.2
|
||||
### User Visible Changes
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Added new Splitter algo. From the manual: The splitter is useful
|
||||
for pro-rata markets where you want to vary your exposure
|
||||
independent of the order quantity that you want to fill. The
|
||||
@@ -90,13 +109,13 @@ insert_anchor_links = "right"
|
||||
the order quantity but it will put exposed_qty total size on the
|
||||
market. Splitter favors order size over queue position.
|
||||
### Internal Changes
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
## 2.7.1
|
||||
### User Visible Changes
|
||||
- Fixed some issues with the Cover algo, where in some circumstances
|
||||
it would erroneously cancel launched hedges on completion.
|
||||
|
||||
|
||||
## 2.7.0
|
||||
### User Visible Changes
|
||||
- Allowed algos to process "in-flight" fills while shutting down
|
||||
@@ -115,25 +134,25 @@ insert_anchor_links = "right"
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
|
||||
- Fixed a rare race condition that could lead to erroneous limit enforcement
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
|
||||
## 2.6.1
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
|
||||
|
||||
|
||||
## 2.6.0
|
||||
### User Visible Changes
|
||||
- In the ladder made last trade more distinctive when overlayed on the volumn column
|
||||
- In the ladder, corrected display of volume tool tip
|
||||
- In the ladder, corrected display of volume tool tip
|
||||
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
|
||||
- Added an option for the Top Algo to delete itself if within N ticks from top of book
|
||||
- Address a rare issue that could lead to an error report on UI exit
|
||||
- Added margin risk limits
|
||||
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
|
||||
- Avoided switching the current account and order type on open ladders when limits are changed
|
||||
- Avoided switching the current account and order type on open ladders when limits are changed
|
||||
### Internal Changes
|
||||
- Ensure that admin logins are always allowed to login, regardless of keycode status.
|
||||
|
||||
|
||||
## 2.5.0
|
||||
### User Visible Changes
|
||||
- Added loss limit risk check
|
||||
@@ -163,13 +182,13 @@ insert_anchor_links = "right"
|
||||
- Made Cover order more independent from its hedging chidren:
|
||||
- Cover continues to operate even if an hedging child is canceled
|
||||
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed manual fills to appear in day PnL rather than in the SOD records
|
||||
- Add buy/sell coloring to fill rows in fills window
|
||||
- Streamlined the mini order book:
|
||||
- Display of the mini order book popup is quicker
|
||||
- Only exchange orders are displayed
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
|
||||
- A checkbox column has been added to make clearer which rows are selected
|
||||
### API Changes
|
||||
@@ -179,7 +198,7 @@ insert_anchor_links = "right"
|
||||
- Added system requirements to Spark documentation
|
||||
- Implemented keycodes
|
||||
- Temporarily rejected stop and stop limit orders, pending bug fixes
|
||||
|
||||
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
@@ -190,30 +209,30 @@ insert_anchor_links = "right"
|
||||
- Improved handling of orders with undiscovered securities
|
||||
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
|
||||
- Fixed display of algo studio algo types in order book
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
- Added security symbol column to fills window
|
||||
- Re-organized ladder settings dialog to make it clearer and easier to use
|
||||
|
||||
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
|
||||
|
||||
## 2.1.3
|
||||
### User Visible Changes
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
|
||||
## 2.2.1
|
||||
### User Visible Changes
|
||||
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
|
||||
|
||||
|
||||
## 2.1.1
|
||||
### User Visible Changes
|
||||
- Added PIQ to GLBX Iceberg orders
|
||||
- Corrected handling of stop limit orders
|
||||
|
||||
|
||||
## 2.1.0
|
||||
### User Visible Changes
|
||||
- Added support for the GLBX Iceberg order type
|
||||
@@ -224,7 +243,7 @@ insert_anchor_links = "right"
|
||||
- Added PIQ to the mini order book
|
||||
- Fixed a rare bug that could lead to server failure when requesting market data
|
||||
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
|
||||
|
||||
|
||||
## 2.0.5
|
||||
### User Visible Changes
|
||||
- Fixed a bug in the handling of rejected QHO orders
|
||||
@@ -234,19 +253,19 @@ insert_anchor_links = "right"
|
||||
## 2.0.4
|
||||
### User Visible Changes
|
||||
- Fixed performance regression in order manager
|
||||
|
||||
|
||||
### Internal changes
|
||||
- Improved market data diagnostics for bad configurations
|
||||
- Changed market data default buffer size
|
||||
|
||||
|
||||
## 2.0.3
|
||||
### Internal changes
|
||||
- Improved backwards compatibility in UI settings
|
||||
|
||||
|
||||
|
||||
## 2.0.2
|
||||
### User Visible Changes
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the bid or ask column in the ladder no longer sends an order
|
||||
|
||||
### Internal changes
|
||||
@@ -274,8 +293,8 @@ insert_anchor_links = "right"
|
||||
- Improved settings manager tool output
|
||||
- Added diagnostics in the event MD memory corruption is detected
|
||||
- Added systems tests for join,lean, iceberg and spreader orders
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
## 1.6.2
|
||||
### User Visible Changes
|
||||
### Exchange simulator
|
||||
@@ -314,21 +333,21 @@ insert_anchor_links = "right"
|
||||
- These columns can be restored by toggling their visibility within the PnL's Settings form
|
||||
- A new Traded Today column has been added to the PnL
|
||||
- Filtering on this column will allow a user to restrict the report to only show today's position
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
- Added logging to help troubleshoot shell exit hang
|
||||
|
||||
## 1.5.6
|
||||
### User Visible Changes
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
- Improved algo documentation
|
||||
- Changed how the spreader uses net change
|
||||
- Simplified trigger algo user interface
|
||||
### Internal changes
|
||||
### Internal changes
|
||||
- Defaulted to not recording sim market data
|
||||
- Improved documentation build
|
||||
- Improved documentation build
|
||||
- Added additional performance tracing
|
||||
|
||||
|
||||
## 1.5.5
|
||||
- Fixed Market View 'stale working orders' bug
|
||||
|
||||
@@ -336,11 +355,11 @@ insert_anchor_links = "right"
|
||||
- Avoided logging when warming up cache
|
||||
|
||||
## 1.5.3
|
||||
- Fixed sounds startup issue
|
||||
- Fixed sounds startup issue
|
||||
|
||||
## 1.5.2
|
||||
- Added better logging to AlgoDefinitionService
|
||||
|
||||
|
||||
## 1.5.1
|
||||
- Fixed missing securities in the Fills and P&L windows.
|
||||
|
||||
@@ -361,7 +380,7 @@ insert_anchor_links = "right"
|
||||
#### Bug Fixes
|
||||
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
|
||||
- Fixed a bug in the order book where sometimes parent orders would not update their last message column.
|
||||
|
||||
|
||||
#### Algos
|
||||
- Added OCO Algo
|
||||
- An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill. Composing this order with Cover simulates scalping functionality
|
||||
@@ -369,7 +388,7 @@ insert_anchor_links = "right"
|
||||
- Improved SDK documentation and usability
|
||||
- Dynamic orders are now properly identified in the order book
|
||||
#### Documentation
|
||||
- Information on the Spark API has been added to the Spark manual
|
||||
- Information on the Spark API has been added to the Spark manual
|
||||
- The documentation has been updated and reorganized to make information easier to find
|
||||
### Internal Changes
|
||||
- Improved robustness of limit system test
|
||||
|
||||
@@ -1,83 +1,74 @@
|
||||
using bts;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using bts.api.core;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
public readonly UserTuple user;
|
||||
|
||||
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
|
||||
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
|
||||
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
|
||||
IReadOnlyDictionary<uint, string>
|
||||
_account_map = new Dictionary<uint, string>(),
|
||||
_user_map = new Dictionary<uint, string>();
|
||||
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(new AdminModelWrapper());
|
||||
IDisposable _dispose;
|
||||
|
||||
IDisposable _dispose;
|
||||
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, [Import]UserTuple user) {
|
||||
AdminModel(AdminService service, UserTuple user) {
|
||||
this.user = user;
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
|
||||
IEnumerable<BTSUserImmutable> active_users =
|
||||
from admin_data in update.all
|
||||
from bts_user in admin_data.Users
|
||||
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
|
||||
select bts_user;
|
||||
|
||||
IEnumerable<BTSAccountImmutable> active_accounts =
|
||||
from admin_data in update.all
|
||||
from bts_account in admin_data.Accounts
|
||||
where bts_account.IsPresent && _wrapper_subject.Value.can_view_account(bts_account.Name)
|
||||
join account_id in (
|
||||
from bts_user in active_users
|
||||
from account_id in bts_user.UserData.AccountPermissions.AccountPermissions.Keys
|
||||
select account_id
|
||||
) on bts_account.Id equals account_id
|
||||
select bts_account;
|
||||
|
||||
var users = active_users.ToDictionary(u => u.Id, u => u.Name);
|
||||
var accounts = active_accounts.ToDictionary(a => a.Id, a => a.Name);
|
||||
return (users, accounts);
|
||||
}).Subscribe(t => {
|
||||
_account_map = t.accounts;
|
||||
_user_map = t.users;
|
||||
_users_subject.OnNext(_user_map = t.users);
|
||||
_accounts_subject.OnNext(_account_map = t.accounts);
|
||||
});
|
||||
// accounts
|
||||
on_accounts = service.subscribei_admin_data()
|
||||
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
|
||||
usr.UserData.AccountPermissions.AccountPermissions
|
||||
.Where(kv => (kv.Value.TradingPermission != null && kv.Value.TradingPermission.HasPermissions) || kv.Value.AdminPermission.HasPermissions)
|
||||
.Select(kv => ad.Accounts.Where(a => a.Id == kv.Key).TryGetSingle(out var acc) ? acc : (BTSAccountImmutable?)null)
|
||||
.Where(acc => acc.HasValue && acc.Value.IsPresent)
|
||||
.Select(acc => acc.Value)
|
||||
.ToDictionary(acc => acc.Id, acc => acc.Name) :
|
||||
new Dictionary<uint, string>(0))
|
||||
.Replay(1)
|
||||
.RefCount();
|
||||
// users
|
||||
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
|
||||
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
|
||||
service.subscribei_admin_data()
|
||||
.Select(u => u.all.TryFirst())
|
||||
.Where(ad => ad.HasValue)
|
||||
.Select(ad => ad.Value.Users
|
||||
.Where(u => user.can_access_user_data(u.Id))
|
||||
.Select(u => new UserTuple(u.Id, u.Name))
|
||||
.ToDictionary(UserTuple.key_fn))
|
||||
.Table(UserTuple.key_fn)
|
||||
.Cache() :
|
||||
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
|
||||
_dispose = on_accounts.Subscribe(map => _account_map = map);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
|
||||
public IObservable<IReadOnlyList<UserTuple>> on_user_tuples => on_users.Select(d => d.Select(kv => new UserTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
|
||||
|
||||
public AccountTuple? get_default_account() {
|
||||
foreach (var kv in _account_map) {
|
||||
return new AccountTuple(kv.Key, kv.Value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public uint? get_user_id(string user) => _id_for_name(user, _user_map);
|
||||
public uint? get_account_id(string account) => _id_for_name(account, _account_map);
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
positions :
|
||||
positions.Filter(on_accounts.Select(map => new Func<T, bool>(p => map.ContainsKey(get_account_id(p)))));
|
||||
|
||||
static uint? _id_for_name(string name, IReadOnlyDictionary<uint, string> map) {
|
||||
foreach (var kv in map) {
|
||||
if (kv.Value == name)
|
||||
return kv.Key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, string> get_account_name) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
positions :
|
||||
positions.Filter(on_accounts.Select(map => {
|
||||
var hash = new HashSet<string>(map.Values);
|
||||
return new Func<T, bool>(v => hash.Contains(get_account_name(v)));
|
||||
}));
|
||||
|
||||
public AccountTuple? get_default_account() =>
|
||||
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,10 @@ using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -23,8 +18,8 @@ namespace bts.ui {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
|
||||
_user = username;
|
||||
public AboutDaemon(IBus bus, UserTuple user) {
|
||||
_user = user.name;
|
||||
_bus = bus;
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
}
|
||||
@@ -63,4 +58,4 @@ namespace bts.ui {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,11 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
using System.Threading.Tasks;
|
||||
using mbts.ui.shell;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace bts.spark
|
||||
{
|
||||
namespace bts.spark {
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
public class ErrorReporter : IShellDaemon, IDisposable {
|
||||
readonly ErrorReportService _svc;
|
||||
@@ -21,9 +19,9 @@ namespace bts.spark
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, UserTuple user) {
|
||||
_svc = svc;
|
||||
_username = username;
|
||||
_username = user.name;
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
}
|
||||
|
||||
@@ -48,6 +46,45 @@ namespace bts.spark
|
||||
ErrorMessage = message,
|
||||
});
|
||||
}
|
||||
public async Task report_silently(string contact, string description, string userdata) {
|
||||
var request = append_logs(new ErrorReport {
|
||||
User = _username,
|
||||
ErrorMessage = description,
|
||||
UserDescription = userdata,
|
||||
Contact = contact
|
||||
}.immutable());
|
||||
var reply = await _svc.send_report_async(request);
|
||||
}
|
||||
|
||||
public static ErrorReportImmutable append_logs(ErrorReportImmutable src) {
|
||||
var result = src.CloneToMutable();
|
||||
|
||||
var windows_log = LogUtil.read_windows_event_log();
|
||||
if (!windows_log.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = "Application.evtx.gz",
|
||||
Contents = windows_log
|
||||
});
|
||||
}
|
||||
|
||||
var (data,date) = LogUtil.read_latest_crash_dump();
|
||||
if (!data.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = $"spark-{date.ToString("s")}.dmp.gz",
|
||||
Contents = data
|
||||
});
|
||||
}
|
||||
|
||||
var spark_log = LogUtil.read_logs_compressed();
|
||||
if (!spark_log.IsEmpty) {
|
||||
result.Files.Add(new FileData {
|
||||
Name = "log.gz",
|
||||
Contents = spark_log
|
||||
});
|
||||
}
|
||||
return result.immutable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ErrorReporterExtensions {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
@@ -7,15 +8,10 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using bts.api.user;
|
||||
using bts.md;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IInitializable))]
|
||||
@@ -25,17 +21,21 @@ namespace bts {
|
||||
readonly Lazy<SyntheticSecurityService> _ss;
|
||||
readonly Dictionary<SecurityKey, SecurityMD> _deleted_synthetics = new Dictionary<SecurityKey, SecurityMD>();
|
||||
|
||||
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
|
||||
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, UserTuple user)
|
||||
: base(mbts.config.Configuration.get<md.RelayOptions>().naggle_ms, MDSourceOptions.DeferInit, user) {
|
||||
_bus = bus;
|
||||
_token = token;
|
||||
_ss = ss;
|
||||
_ss = ss;
|
||||
}
|
||||
|
||||
override public void initialize() {
|
||||
public int priority => 55;
|
||||
|
||||
[Export]
|
||||
MDConsumer consumer => _consumer;
|
||||
IBus bus => _bus.Value;
|
||||
|
||||
public override void initialize() {
|
||||
_relay.set_token(_token.Value);
|
||||
_dispose.Add(
|
||||
_ss.Value.subscribei_metadata()
|
||||
@@ -60,16 +60,8 @@ namespace bts {
|
||||
// Post full dictionary of syntheics here
|
||||
bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
}));
|
||||
_dispose.Add( this.on_securities.Subscribe( (md) => {
|
||||
bus.post(SparkTags.SecuritiesTag, md);
|
||||
}));
|
||||
_dispose.Add(on_securities.Subscribe(md => bus.post(SparkTags.SecuritiesTag, md)));
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
[Export]
|
||||
MDConsumer consumer => _consumer;
|
||||
|
||||
public int priority => 55;
|
||||
IBus bus => _bus.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using mbts.ui;
|
||||
using mbts;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.spark {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class OopsDaemon : IShellDaemon, IDisposable {
|
||||
[ImportingConstructor]
|
||||
public OopsDaemon(ErrorReporter e) {
|
||||
var pid = get_pid();
|
||||
if (pid.HasValue) {
|
||||
handle_unclean_shutdown(e, pid.Value);
|
||||
}
|
||||
save_pid();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
clear_pid();
|
||||
}
|
||||
|
||||
void handle_unclean_shutdown(ErrorReporter e, int oldpid) {
|
||||
string extra = "";
|
||||
try {
|
||||
var conf = mbts.config.Configuration.get<bts.config.System>();
|
||||
extra = JsonConvert.SerializeObject(conf, Formatting.Indented);
|
||||
}
|
||||
catch { }
|
||||
Task.Run(() => e.report_silently(
|
||||
$"Host: {Environment.MachineName}",
|
||||
$"pid: {oldpid}\n{extra}",
|
||||
"Automated crash report")
|
||||
);
|
||||
}
|
||||
|
||||
const string _subkey = @"Software\BTS";
|
||||
private void save_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
key.SetValue("pid", System.Diagnostics.Process.GetCurrentProcess().Id, Microsoft.Win32.RegistryValueKind.DWord);
|
||||
}
|
||||
}
|
||||
private void clear_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
key.DeleteValue("pid",false);
|
||||
}
|
||||
}
|
||||
private int? get_pid() {
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(_subkey)) {
|
||||
return key.GetValue("pid") as int?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,11 @@ namespace bts.shell {
|
||||
void _show_order_window(Window window) {
|
||||
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
|
||||
window.Resources[kv.Key] = kv.Value;
|
||||
// the dynamic algo 'SecurityKey' template has a ContractPickerPopup that expects the security map to be available as a DynamicResource.
|
||||
var sub = _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => window.Resources[ResourceKeys.Securities] = sec_map);
|
||||
window.Closed += (s, e) => Disposable.dispose(ref sub);
|
||||
window.Show(-200, -160, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,13 @@ using bts.metrics;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.perf {
|
||||
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class ShellLatencyDaemon : IShellDaemon, IDisposable {
|
||||
readonly ShellLatencyService _service;
|
||||
@@ -24,10 +20,10 @@ namespace bts.perf {
|
||||
Ping _pinger = new Ping();
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
|
||||
public ShellLatencyDaemon(ShellLatencyService service, UserTuple user) {
|
||||
_service = service;
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_username = user;
|
||||
_username = user.name;
|
||||
_hostname = Environment.MachineName;
|
||||
|
||||
_forever_ping_host(_pinger);
|
||||
@@ -105,4 +101,4 @@ namespace bts.perf {
|
||||
Disposable.dispose(ref _subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
@@ -13,13 +12,11 @@ using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using static mbts.ui.shell.ProtoBufSerializationHelper;
|
||||
|
||||
namespace bts.shell
|
||||
{
|
||||
namespace bts.shell {
|
||||
[Export(typeof(IShellModule))]
|
||||
class MarketShellModule : IShellModule
|
||||
{
|
||||
class MarketShellModule : IShellModule {
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace mbts.spark {
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
@@ -54,6 +55,7 @@ namespace mbts.spark {
|
||||
_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);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
@@ -109,6 +111,7 @@ namespace mbts.spark {
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
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 double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
@@ -167,15 +170,17 @@ namespace mbts.spark {
|
||||
_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);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v)) {
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
_settlement.set(md.md.settlement_price);
|
||||
_session_low.set(md.md.session_low);
|
||||
_session_high.set(md.md.session_high);
|
||||
_day_change.set(last_price - settlement);
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace mbts.spark {
|
||||
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.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
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);
|
||||
@@ -138,6 +139,7 @@ namespace mbts.spark {
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
@@ -155,7 +157,7 @@ namespace mbts.spark {
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
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),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
@@ -218,6 +220,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
@@ -250,6 +253,7 @@ namespace mbts.spark {
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace mbts.ui.spark {
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
LastSpd,
|
||||
LastSize,
|
||||
Position,
|
||||
RawDayPosition,
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace bts.shell {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings volume => new GridColumnSettings(col_id(Columns.Volume), DefaultColumnHeaders.Volume, width: DefaultColumnWidths.Volume);
|
||||
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
|
||||
public static GridColumnSettings last_from_spread => new GridColumnSettings(col_id(Columns.LastSpd), DefaultColumnHeaders.S, width: DefaultColumnWidths.Price, is_visible: false);
|
||||
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings raw_day_position => new GridColumnSettings(col_id(Columns.RawDayPosition), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
|
||||
@@ -138,6 +139,7 @@ namespace bts.shell {
|
||||
yield return security;
|
||||
yield return volume;
|
||||
yield return last;
|
||||
yield return last_from_spread;
|
||||
yield return last_size;
|
||||
yield return position;
|
||||
yield return raw_day_position;
|
||||
@@ -224,6 +226,8 @@ namespace bts.shell {
|
||||
[ProtoMember(25)]
|
||||
public string strip_name;
|
||||
[ProtoMember(26)]
|
||||
public string last_price_from_spread;
|
||||
[ProtoMember(28)]
|
||||
public string mic_code;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.synthetic;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -18,11 +20,36 @@ namespace mbts.spark {
|
||||
readonly uint _user_id;
|
||||
|
||||
[ImportingConstructor]
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, AdminModel admin_model, ErrorReporter error, UserTuple user) {
|
||||
_bus = bus;
|
||||
_service = service;
|
||||
_error = error;
|
||||
_user_id = user_id;
|
||||
_user_id = user.id;
|
||||
// synthetic tuples
|
||||
var strategy_source = _bus.subscribe(SparkTags.SyntheticstrategiesTag)
|
||||
.Select(map => MappedDictionary.create<uint, ExecutionStrategyImmutable, (uint security_id, ExecutionStrategyImmutable strategy)>(map, (k, v) => (k, v)))
|
||||
.Table(tup => tup.security_id);
|
||||
|
||||
var synthetic_source = _bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Select(map => new FilteredDictionary<uint, SecurityMD>(map, smd => smd.security.ContractCase == Security.ContractOneofCase.Synthetic))
|
||||
.Table(smd => smd.security.Id);
|
||||
|
||||
var synthetic_user_join = synthetic_source.LeftJoin(
|
||||
admin_model.all_user_tuples,
|
||||
SecurityMD.key_fn,
|
||||
UserTuple.key_fn,
|
||||
smd => smd.security.Synthetic.UserId,
|
||||
(smd, sid, ut, uid) => new SyntheticTuple(smd.security, ut.name, null),
|
||||
(smd, sid, uid) => new SyntheticTuple(smd.security, null, null),
|
||||
SyntheticTuple.key_fn);
|
||||
|
||||
synthetic_tuples = synthetic_user_join.LeftJoin(
|
||||
strategy_source,
|
||||
SyntheticTuple.key_fn,
|
||||
tup => tup.security_id,
|
||||
(synth_tup, id, strat_tup) => new SyntheticTuple(synth_tup.security, synth_tup.user_name, strat_tup.strategy),
|
||||
(synth_tup, id) => synth_tup,
|
||||
SyntheticTuple.key_fn);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
|
||||
@@ -36,6 +63,8 @@ namespace mbts.spark {
|
||||
.LeftJoin(strategies, (security, strategy) => (new ExecutionStrategyImmutable?(strategy), security), security => (new ExecutionStrategyImmutable?(), security))
|
||||
);
|
||||
|
||||
public IObservable<TableUpdate<SyntheticTuple, uint>> synthetic_tuples { get; }
|
||||
|
||||
public IObservable<ExecutionStrategyImmutable> security_strategy(uint security_key) =>
|
||||
strategy.TrySelect(s => {
|
||||
bool success = s.TryGetValue(security_key, out ExecutionStrategyImmutable result);
|
||||
|
||||
@@ -81,9 +81,9 @@ namespace bts.shell {
|
||||
#region QuoteParams
|
||||
public static QuoteParams QuoteParams => new QuoteParams {
|
||||
LeanQuantityRatio = 1,
|
||||
QuoteInsideMarketDistance = QuoteInsideMarketDistance,
|
||||
//QuoteInsideMarketDistance = QuoteInsideMarketDistance,
|
||||
QuoteMaxOrderMove = QuoteMaxOrderMove,
|
||||
QuoteMuteZone = QuoteMuteZone,
|
||||
//QuoteMuteZone = QuoteMuteZone,
|
||||
QuoteRepriceThrottle = QuoteRepriceThrottle,
|
||||
QuoteInsideMarketDistanceWithTravel = QuoteInsideMarketDistanceWithTravel,
|
||||
};
|
||||
@@ -113,6 +113,7 @@ namespace bts.shell {
|
||||
};
|
||||
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
|
||||
#endregion
|
||||
|
||||
#region PreHedge
|
||||
|
||||
@@ -7,8 +7,15 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for SyntheticManagementView.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticManagementView : UserControl {
|
||||
public enum Columns {
|
||||
Name,
|
||||
Description,
|
||||
n_legs,
|
||||
n_working_orders,
|
||||
SecurityKey,
|
||||
User
|
||||
}
|
||||
|
||||
public enum Columns { Name, Description, n_legs, n_working_orders, SecurityKey}
|
||||
public SyntheticManagementView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
@@ -27,7 +28,7 @@ namespace mbts.spark {
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
@@ -66,8 +67,8 @@ namespace mbts.spark {
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
current_row_connectable_builder.create(observe_row_strategy),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
|
||||
), SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_observable.Select(s => s.HasValue ? ImmutableHashSet<uint>.Empty.Add(s.Value.Id) : null)), id => id != null
|
||||
), SparkCommands.ShowOrders.complete(() => {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.settings;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -7,6 +8,7 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
@@ -33,13 +35,8 @@ namespace bts.shell {
|
||||
public async void show(SparkTags.Nothing nothing) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(new StartupData()));
|
||||
|
||||
IShellWindow _create(StartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
get: us => us.synthetic_management_grid,
|
||||
set: (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
|
||||
return new SyntheticManagementWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
IShellWindow _create(StartupData state) =>
|
||||
new SyntheticManagementWindow(this, _bus, _orders_model, state, _settings_hub.create_synth_settings_manager(state.grid));
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
@@ -67,6 +64,7 @@ namespace bts.shell {
|
||||
public static GridColumnSettings n_legs => new GridColumnSettings(col_id(Columns.n_legs), DefaultColumnHeaders.N_Legs, width: DefaultColumnWidths.N_Legs);
|
||||
public static GridColumnSettings n_working_orders => new GridColumnSettings(col_id(Columns.n_working_orders), DefaultColumnHeaders.N_Working_Orders, width: DefaultColumnWidths.N_Working_Orders);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, is_visible: false, width: DefaultColumnWidths.User);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
@@ -81,6 +79,7 @@ namespace bts.shell {
|
||||
yield return n_legs;
|
||||
yield return n_working_orders;
|
||||
yield return security_key;
|
||||
yield return user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +100,15 @@ namespace bts.shell {
|
||||
public UIntMinMaxState n_working_orders;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public uint[] user;
|
||||
}
|
||||
|
||||
// migration rules
|
||||
[Export(typeof(ISyntheticManagementMigrationRule))]
|
||||
class AddMissingColumnsMigrationRule : ISyntheticManagementMigrationRule {
|
||||
public bool apply(GridSettings settings, out GridSettings new_settings) =>
|
||||
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using bts.shell;
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteInsideMarketDistanceEnabledModel : RowModelBase<bool> {
|
||||
readonly QuoteInsideMarketDistanceTowardModel _toward;
|
||||
|
||||
public QuoteInsideMarketDistanceEnabledModel(QuoteInsideMarketDistanceTowardModel toward) =>
|
||||
_toward = toward;
|
||||
|
||||
public override string display_name { get; } = string.Empty;
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle != null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle == null) {
|
||||
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = SyntheticDataHelper.Defaults.QuoteInsideMarketDistanceThrottle;
|
||||
_toward.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle = null;
|
||||
}
|
||||
_toward.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using bts.spark;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteInsideMarketDistanceTowardModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
|
||||
public override string display_name { get; } = "Toward Distance";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.InsideMarketDistance };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_inside_market_distance(leg_tup).OnTowardDistance = _value_from_store(leg_tup);
|
||||
|
||||
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance ?
|
||||
inside_market_distance.OnTowardDistance :
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
if (_get_inside_market_distance(leg_tup) is QuoteInsideMarketDistanceThrottle inside_market_distance)
|
||||
inside_market_distance.OnTowardDistance = value;
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_inside_market_distance(leg_tup) != null;
|
||||
|
||||
QuoteInsideMarketDistanceThrottle _get_inside_market_distance(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.QuoteInsideMarketDistanceThrottle;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,11 @@ namespace mbts.spark {
|
||||
"dictates if this logic applies to the quoting market or to the synthetic spread " +
|
||||
"market");
|
||||
|
||||
public static ParameterSection InsideMarketDistance =>
|
||||
new ParameterSection("Inside Market Distance", true,
|
||||
"Reduces re-quoting by throttling quotes that are moving toward the inside market " +
|
||||
"but are more than 'Toward Distance' away from the inside market");
|
||||
|
||||
public static ParameterSection InsideMarketThrottleThresholds =>
|
||||
new ParameterSection("Inside Market Throttle Thresholds", true,
|
||||
"Reduces re-quoting for quotes that are moving toward the inside market. " +
|
||||
|
||||
@@ -190,6 +190,8 @@ namespace mbts.spark {
|
||||
var quote_inside_market_throttle_ticks = new QuoteInsideMarketThrottleTicksModel();
|
||||
var quote_inside_market_throttle_min_move = new QuoteInsideMarketThrottleMinMoveModel();
|
||||
|
||||
var quote_inside_market_distance_toward = new QuoteInsideMarketDistanceTowardModel();
|
||||
|
||||
var pre_hedge_payless_threshold = new PreHedgePaylessThresholdModel();
|
||||
var pre_hedge_payless_ticks = new PreHedgePaylessTicksModel();
|
||||
|
||||
@@ -227,6 +229,8 @@ namespace mbts.spark {
|
||||
//quote_inside_market_throttle_ticks,
|
||||
//quote_inside_market_throttle_min_move,
|
||||
//new MaxTicksInsideMarketModel(),
|
||||
new QuoteInsideMarketDistanceEnabledModel(quote_inside_market_distance_toward),
|
||||
quote_inside_market_distance_toward,
|
||||
new PreHedgePaylessEnabledModel(pre_hedge_payless_threshold, pre_hedge_payless_ticks),
|
||||
pre_hedge_payless_threshold,
|
||||
pre_hedge_payless_ticks,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
@@ -17,43 +20,45 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.SyntheticManagementView;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementDialogVM : SecurityMDGrid<SyntheticManagementDialogVM.Row, (ExecutionStrategyImmutable? strategy, SecurityMD md)> {
|
||||
class SyntheticManagementDialogVM : SecurityGrid<SyntheticManagementDialogVM.Row, SyntheticTuple> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager) : this(bus, orders_model, settings_manager, null, null) { }
|
||||
public SyntheticManagementDialogVM(IBus bus, OrdersModel orders_model, IWindowSettingsManager<GridSettings> settings_manager, IReadOnlyCollection<SortingCriteria> sorting, FilterStates filters)
|
||||
: base(orders_model.spreader.strategy_tuple.Select(map => map.Values), sorting) {
|
||||
: base(orders_model.spreader.synthetic_tuples.Select(t => t.state.Values), sorting) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_settings_manager = settings_manager;
|
||||
// init filters
|
||||
user_filter = DiscreteValuesFilterVM.CreateTableUpdate<Row, UserTuple, uint>(r => r.user_tuple, UserTuple.key_fn, out var user_filter_items_observer, out var user_filter_items_view, nameof(Row.user_tuple));
|
||||
user_filter_items_view.CustomSort = Comparer<string>.Default.wrap<string, UserTuple>(ut => ut.name).to_non_generic();
|
||||
if (filters != null) {
|
||||
create_time_filter.load_state(filters.create_time);
|
||||
name_filter.load_state(filters.name);
|
||||
description_filter.load_state(filters.description);
|
||||
n_legs_filter.load_state(filters.n_legs);
|
||||
n_working_orders_filter.load_state(filters.n_working_orders);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
user_filter.load_state(filters.user);
|
||||
}
|
||||
_settings_manager = settings_manager;
|
||||
// columns
|
||||
add_col(Columns.Name, DefaultColumnHeaders.Name, r => r.name, nameof(Row.name), name_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Security);
|
||||
add_col(Columns.Description, DefaultColumnHeaders.Description, r => r.description, nameof(Row.description), description_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.Description);
|
||||
add_col(Columns.n_legs, DefaultColumnHeaders.N_Legs, r => r.n_legs.ToString(), nameof(Row.n_legs), n_legs_filter, DataGridFilterKnownTemplates.IntMinMax, DefaultColumnWidths.N_Legs);
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
Columns.n_working_orders,
|
||||
new Binding(nameof(Row.n_working_orders)),
|
||||
new Binding(nameof(Row.show_order_book)),
|
||||
n_working_orders_filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
id = col_id(Columns.n_working_orders),
|
||||
Header = DefaultColumnHeaders.N_Working_Orders,
|
||||
Width = DefaultColumnWidths.N_Working_Orders
|
||||
}
|
||||
);
|
||||
});
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user_name, nameof(Row.user_name), user_filter, DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, IDataGridColumnFilter filter, string filter_template, double width) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
@@ -63,42 +68,39 @@ namespace mbts.spark {
|
||||
Width = width
|
||||
});
|
||||
//
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>(), row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
|
||||
_resources.AddRange(
|
||||
DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter),
|
||||
orders_model.admin_model.all_user_tuples.OnDispatcher().Subscribe(user_filter_items_observer),
|
||||
DataGridFilterHelper.hook_all<SyntheticManagementDialogVM, Row>(this, rows_view),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
}
|
||||
|
||||
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
|
||||
new Row(_bus, row_model.md, row_model.strategy, _orders_model);
|
||||
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
|
||||
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> create_time_filter { get; } = new TimeFilterVM<Row>(r => r.create_time, nameof(Row.create_time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> name_filter { get; } = new RegexFilterVM<Row>(r => r.name, nameof(Row.name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> description_filter { get; } = new RegexFilterVM<Row>(r => r.description, nameof(Row.description));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> n_legs_filter { get; } = new IntMinMax<Row>(r => r.n_legs, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
public IDataGridColumnPersistentFilter<Row, uint[]> user_filter { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() =>
|
||||
_settings_manager.instance_settings == null ? null : grid_settings_manager.create();
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(rows_view.get_sorting_criteria(columns), _get_filter_states(), get_grid_save_state());
|
||||
|
||||
protected override Row row_factory(SyntheticTuple row_model) =>
|
||||
new Row(_bus, row_model, _orders_model);
|
||||
|
||||
protected override void on_row_removed(Row row) =>
|
||||
row.Dispose();
|
||||
|
||||
FilterStates _get_filter_states() =>
|
||||
new FilterStates {
|
||||
@@ -107,19 +109,19 @@ namespace mbts.spark {
|
||||
name = name_filter.get_state(),
|
||||
n_legs = n_legs_filter.get_state(),
|
||||
n_working_orders = n_working_orders_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
security_key = security_key_filter.get_state(),
|
||||
user = user_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
public class Row : SecurityMDRow, IEquatable<Row>, IRowVM, IActionableRowVM {
|
||||
public readonly ExecutionStrategyImmutable? strategy;
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM, IActionableRowVM {
|
||||
public readonly SyntheticTuple synthetic_tuple;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly Property<uint> _n_working_orders;
|
||||
|
||||
public Row(IBus bus, SecurityMD security_md, ExecutionStrategyImmutable? strategy, OrdersModel orders_model) : base(security_md) {
|
||||
public Row(IBus bus, SyntheticTuple synthetic_tuple, OrdersModel orders_model) : base(synthetic_tuple.security) {
|
||||
_bus = bus;
|
||||
this.strategy = strategy;
|
||||
this.synthetic_tuple = synthetic_tuple;
|
||||
_n_working_orders = backing(nameof(n_working_orders),
|
||||
orders_model.on_security_orders(security_key)
|
||||
.Select(t => (uint)(t.state
|
||||
@@ -133,25 +135,23 @@ namespace mbts.spark {
|
||||
show_order_book = new DelegateCommand(_show_order_book);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public DateTime create_time => DateTime.MinValue; //TODO
|
||||
public string name { get; }
|
||||
public string user_name => synthetic_tuple.user_name;
|
||||
public UserTuple user_tuple => new UserTuple(synthetic_tuple.security.Synthetic.UserId, user_name);
|
||||
public int n_legs { get; }
|
||||
public string description { get; }
|
||||
public ExecutionStrategyImmutable? strategy => synthetic_tuple.strategy;
|
||||
public bool strategy_present => strategy.HasValue;
|
||||
public uint n_working_orders => _n_working_orders.Value;
|
||||
public ICommand show_ladder { get; }
|
||||
public ICommand show_order_book { get; }
|
||||
public ICommand default_command => show_ladder;
|
||||
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations => new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<IGridCellStore> cell_stores => new IGridCellStore[0];
|
||||
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
public void raise_invalidated() =>
|
||||
invalidated?.Invoke();
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
@@ -163,10 +163,8 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
|
||||
|
||||
void _show_ladder() =>
|
||||
_bus.post(SparkTags.ShowLadderTag, md);
|
||||
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet<uint>.Empty.Add(security.Id));
|
||||
|
||||
void _show_order_book() {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
@@ -177,4 +175,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,10 @@ namespace mbts.spark {
|
||||
_vm = new AlgoStudioVM(_model);
|
||||
_vm.load_from(_state);
|
||||
_view = new AlgoStudioView { DataContext = _vm };
|
||||
_resources = new CompositeDisposable();
|
||||
_resources = new CompositeDisposable(
|
||||
_bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||
// add definition
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||
// clone definition
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mbts.spark {
|
||||
_error_reporter = error_reporter;
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
|
||||
.SelectKeyValue(_definition_data, d => d.key)
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace mbts.spark {
|
||||
string _template_key;
|
||||
ICommand _set_value_cmd;
|
||||
|
||||
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent)
|
||||
: this(algo, param, parent, (param as ITemplateSelectorKey)?.key) { }
|
||||
|
||||
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
|
||||
this.param = param;
|
||||
this.algo = algo;
|
||||
|
||||
+40
@@ -247,6 +247,31 @@
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<!--Note: Ideally, this 'securities BindingProxy' would be a static resource, but it only seems to detect the dynamic securities when it is in the visual tree.-->
|
||||
<!--<Grid.Resources>
|
||||
<u:BindingProxy x:Key="SecuritiesBindingProxy" x:Shared="False" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
|
||||
</Grid.Resources>-->
|
||||
<u:BindingProxy x:Name="SecuritiesBindingProxy" Value="{DynamicResource {x:Static u:ResourceKeys.Securities}}"/>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe>
|
||||
<cv:DictionaryLookupConverter/>
|
||||
<x:Static MemberType="cv:ValueConverters" Member="SecuritySelectorText"/>
|
||||
</cv:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="Value" ElementName="SecuritiesBindingProxy"/>
|
||||
<Binding Path="param.value"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
@@ -320,6 +345,21 @@
|
||||
</usp:PriceEntryControl>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static u:TemplateKeys.SecurityKey}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource {x:Static u:ResourceKeys.Securities}}"
|
||||
SelectedSecurityKey="{Binding param.value}"
|
||||
ShowDialogSkirt="True"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityKeyHandler}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
|
||||
@@ -118,13 +118,14 @@ namespace mbts.spark {
|
||||
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
|
||||
// all others (easily identified by the enum 'Columns.Foo')
|
||||
add_col(Columns.FillTime, DefaultColumnHeaders.FillTime, r => r.fill_time.ToString("M/d h:mm:ss tt"), new TimeFilterVM<FillTuple>(r => r.fill.Timestamp.ToDateTimeOffset().LocalDateTime), DataGridFilterKnownTemplates.TimeFilter, DefaultColumnWidths.Time);
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.User, DefaultColumnHeaders.User, r => r.user, dv_filter_rx(_model.factory.subscribe_users().Select(d => d.Values.Ordered()), r => r.order?.user() ?? string.Empty), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.User);
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
|
||||
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
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);
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
@@ -146,6 +147,7 @@ namespace mbts.spark {
|
||||
});
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
ObservableAutoWrapper.connect_item_hooks(col_tups, ct => ct.filter as IDisposable),
|
||||
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
last_unfiltered_fills = t;
|
||||
@@ -227,6 +229,7 @@ namespace mbts.spark {
|
||||
case Columns.Exchange: return wrap_default(ft => ft.security.exchange_or_default());
|
||||
case Columns.FillKey: return wrap(ft => ft.fill.FillKey);
|
||||
case Columns.FillKind: return wrap_default(ft => ft.fill.FillKind);
|
||||
case Columns.FillStatus: return wrap_default(ft => ft.fill.FillStatus);
|
||||
case Columns.FillTime: return wrap(ft => ft.fill.Timestamp.to_local_time());
|
||||
case Columns.OrderKey: return wrap(ft => ft.fill.OrderKey);
|
||||
case Columns.OrderType: return wrap_default(ft => ft.order?.order_type() ?? OrderType.None);
|
||||
@@ -282,7 +285,6 @@ namespace mbts.spark {
|
||||
public DateTime fill_time => _fill.Timestamp.to_local_time();
|
||||
public TimeInForce time_in_force => _order.time_in_force();
|
||||
public OrderType order_type => _order.order_type();
|
||||
//public OrderType root_algo_type => _or
|
||||
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
|
||||
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
|
||||
@@ -293,6 +295,7 @@ namespace mbts.spark {
|
||||
public double price => _fill.LastPrice;
|
||||
public double size => _fill.LastShares;
|
||||
public FillKind fill_kind => _fill.FillKind;
|
||||
public FillStatus fill_status => _fill.FillStatus;
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
|
||||
@@ -300,7 +303,7 @@ namespace mbts.spark {
|
||||
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
|
||||
public OrderMetadata root_algo_order_metadata => _model.factory.get_metadata(root_algo_order_type);
|
||||
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
|
||||
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.display_name();
|
||||
public string root_algo_display_name => fill_tuple.root_order_security?.display_name() ?? OrderType.None.display_name();
|
||||
|
||||
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace mbts.ui.spark {
|
||||
Price,
|
||||
Size,
|
||||
FillKind,
|
||||
FillStatus,
|
||||
FillKey,
|
||||
OrderKey,
|
||||
SecurityKey
|
||||
|
||||
@@ -37,7 +37,8 @@ namespace bts.shell {
|
||||
{ nameof(FillsVM.Row.side), Columns.Side },
|
||||
{ nameof(FillsVM.Row.size), Columns.Size },
|
||||
{ nameof(FillsVM.Row.time_in_force), Columns.TimeInForce },
|
||||
{ nameof(FillsVM.Row.user), Columns.User }
|
||||
{ nameof(FillsVM.Row.user), Columns.User },
|
||||
{ nameof(FillsVM.Row.fill_status ), Columns.FillStatus },
|
||||
};
|
||||
|
||||
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
|
||||
|
||||
@@ -74,6 +74,10 @@ namespace bts.shell.fills {
|
||||
[ProtoMember(15)]
|
||||
[DIYGridColumnID(nameof(Columns.Symbol))]
|
||||
public string[] symbol { get; set; }
|
||||
|
||||
[ProtoMember(17)]
|
||||
[DIYGridColumnID(nameof(Columns.FillStatus))]
|
||||
public Fill.Types.FillStatus[] fill_status { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
@@ -96,6 +100,7 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
|
||||
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace mbts.spark {
|
||||
// TODO: somehow allow windows that are using the default settings to still have individual column widths.
|
||||
[ProtoContract]
|
||||
class LadderColumnSettings : IEquatable<LadderColumnSettings> {
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
// so GetHashCode must return the same hash for every instance.
|
||||
static readonly int _type_hash = typeof(LadderColumnSettings).GetHashCode();
|
||||
|
||||
public static LadderColumnSettings Default => new LadderColumnSettings {
|
||||
show = true,
|
||||
width = 50,
|
||||
@@ -74,14 +78,7 @@ namespace mbts.spark {
|
||||
other.show == show;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as LadderColumnSettings);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
width.GetHashCode() +
|
||||
background.GetHashCode() +
|
||||
foreground.GetHashCode() +
|
||||
font_family.GetHashCode() +
|
||||
font_size.GetHashCode() +
|
||||
font_weight.GetHashCode();
|
||||
public override int GetHashCode() => _type_hash;
|
||||
|
||||
public static bool operator ==(LadderColumnSettings x, LadderColumnSettings y) => x.safe_equals(y);
|
||||
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
|
||||
@@ -89,6 +86,9 @@ namespace mbts.spark {
|
||||
|
||||
[ProtoContract]
|
||||
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
// so GetHashCode must return the same hash for every instance.
|
||||
static readonly int _type_hash = typeof(HistogramBarAnimation).GetHashCode();
|
||||
[ProtoMember(1)]
|
||||
public ColorShowProto bars { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -102,14 +102,14 @@ namespace mbts.spark {
|
||||
show_numbers == other.show_numbers;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as HistogramBarAnimation);
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
bars.GetHashCode(),
|
||||
show_numbers.GetHashCode());
|
||||
public override int GetHashCode() => _type_hash;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class VolumeColumnSettings : IEquatable<VolumeColumnSettings> {
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
// so GetHashCode must return the same hash for every instance.
|
||||
static readonly int _type_hash = typeof(VolumeColumnSettings).GetHashCode();
|
||||
public static VolumeColumnSettings Default {
|
||||
get {
|
||||
var gen = LadderColumnSettings.Default;
|
||||
@@ -149,19 +149,14 @@ namespace mbts.spark {
|
||||
show_ltq.Equals(other.show_ltq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
general.GetHashCode(),
|
||||
buy.GetHashCode(),
|
||||
sell.GetHashCode(),
|
||||
passive.GetHashCode(),
|
||||
total.GetHashCode(),
|
||||
show_ltq.GetHashCode());
|
||||
public override int GetHashCode() => _type_hash;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class LadderMarketBreakLine : IEquatable<LadderMarketBreakLine> {
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
// so GetHashCode must return the same hash for every instance.
|
||||
static readonly int _type_hash = typeof(LadderMarketBreakLine).GetHashCode();
|
||||
[ProtoMember(1)]
|
||||
public bool show { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -184,8 +179,7 @@ namespace mbts.spark {
|
||||
Equals(obj as LadderMarketBreakLine);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
show.GetHashCode() +
|
||||
(int)color;
|
||||
_type_hash;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -255,6 +249,11 @@ namespace mbts.spark {
|
||||
const double _default_order_entry_panel_width = 110.0;
|
||||
const double _default_zoom_factor = 1.0;
|
||||
const bool _default_show_warn_on_cross = true;
|
||||
public static readonly IEqualityComparer<LadderSettings> profile_equality_comparer =
|
||||
EqualityComparer.Create<LadderSettings>(profile_equals);
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
// so GetHashCode must return the same hash for every instance.
|
||||
static readonly int _type_hash = typeof(LadderSettings).GetHashCode();
|
||||
|
||||
// Default proto constructor
|
||||
// Added this so that when clients upgrade to a version with new settings,
|
||||
@@ -497,6 +496,9 @@ namespace mbts.spark {
|
||||
[ProtoMember(55)]
|
||||
public LadderMiniOrderBookSettings mini_ob { get; set; }
|
||||
|
||||
[ProtoMember(57)]
|
||||
public double left_panel_scroll_position { get; set; }
|
||||
|
||||
public void sanitize() =>
|
||||
sanitize(this);
|
||||
|
||||
@@ -533,112 +535,81 @@ namespace mbts.spark {
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether or not the specified LadderSettings are 'profile equivalent.'
|
||||
/// This means that 2 ladders, each with a different one of the specified settings, could share the same profile.
|
||||
/// </summary>
|
||||
///
|
||||
/// Note: the impetus for this version of equality is to avoid disconnecting a ladder from its profile due to 'trivial' settings changes.
|
||||
/// Consider the '<see cref="default_algo"/>' property:
|
||||
/// If a ladder is using a settings profile with Limit as the default algo, and the user decides to send a 'Market' order,
|
||||
/// the ladder would suddenly be disconnected from the profile, because it's settings state would no longer be equal to the profile's settings,
|
||||
/// since they changed the selected algo type to 'Market.'
|
||||
/// To avoid such travesties, a separate equals logic is needed: this is called 'profile-equivalent' and is implemented in <see cref="profile_equals)"/>.
|
||||
/// Devs: be sure to account for properties that are excluded in the 'profile-equals' logic in the [regular]'equals' logic.
|
||||
public static bool profile_equals(LadderSettings a, LadderSettings b) =>
|
||||
a == null ? b == null :
|
||||
b != null &&
|
||||
a.row_height == b.row_height &&
|
||||
a.grid_line_color == b.grid_line_color &&
|
||||
a.orders_column.safe_equals(b.orders_column) &&
|
||||
a.bid_column.safe_equals(b.bid_column) &&
|
||||
a.ask_column.safe_equals(b.ask_column) &&
|
||||
a.price_column.safe_equals(b.price_column) &&
|
||||
a.last_column.safe_equals(b.last_column) &&
|
||||
a.piq_column.safe_equals(b.piq_column) &&
|
||||
a.calculator_buttons.safe_equals(b.calculator_buttons, Enumerable.SequenceEqual) &&
|
||||
a.scroll_bar_visible == b.scroll_bar_visible &&
|
||||
a.default_account == b.default_account &&
|
||||
a.default_time_in_force == b.default_time_in_force &&
|
||||
a.size == b.size &&
|
||||
a.animate_recenter == b.animate_recenter &&
|
||||
a.badge_style == b.badge_style &&
|
||||
a.auto_clear == b.auto_clear &&
|
||||
a.bid_break_line.safe_equals(b.bid_break_line) &&
|
||||
a.ask_break_line.safe_equals(b.ask_break_line) &&
|
||||
a.mid_break_line.safe_equals(b.mid_break_line) &&
|
||||
a.alternate_order_size == b.alternate_order_size &&
|
||||
a.session_low_line.safe_equals(b.session_low_line) &&
|
||||
a.session_high_line.safe_equals(b.session_high_line) &&
|
||||
a.session_range_fill.safe_equals(b.session_range_fill) &&
|
||||
a.volume_column.safe_equals(b.volume_column) &&
|
||||
a.size_display.safe_equals(b.size_display) &&
|
||||
a.day_visible == b.day_visible &&
|
||||
a.high_visible == b.high_visible &&
|
||||
a.low_visible == b.low_visible &&
|
||||
a.open_visible == b.open_visible &&
|
||||
a.settle_visible == b.settle_visible &&
|
||||
a.volume_visible == b.volume_visible &&
|
||||
a.account_visible == b.account_visible &&
|
||||
a.tif_visible == b.tif_visible &&
|
||||
a.order_type_visible == b.order_type_visible &&
|
||||
a.account_position_visible == b.account_position_visible &&
|
||||
a.day_position_visible == b.day_position_visible &&
|
||||
a.raw_day_position_visible == b.raw_day_position_visible &&
|
||||
a.size_presets_visible == b.size_presets_visible &&
|
||||
a.cancel_buys_visible == b.cancel_buys_visible &&
|
||||
a.cancel_sells_visible == b.cancel_sells_visible &&
|
||||
a.cancel_all_visible == b.cancel_all_visible &&
|
||||
a.auto_clear_visible == b.auto_clear_visible &&
|
||||
a.clear_visible == b.clear_visible &&
|
||||
a.order_entry_panel_initial_width == b.order_entry_panel_initial_width &&
|
||||
a.zoom_slider_visible == b.zoom_slider_visible &&
|
||||
a.zoom_factor == b.zoom_factor &&
|
||||
a.use_defaults == b.use_defaults &&
|
||||
a.favorite_algos == b.favorite_algos &&
|
||||
a.net_change == b.net_change &&
|
||||
a.show_warn_on_cross == b.show_warn_on_cross &&
|
||||
a.mini_ob == b.mini_ob;
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
other != null &&
|
||||
row_height == other.row_height &&
|
||||
grid_line_color == other.grid_line_color &&
|
||||
orders_column.safe_equals(other.orders_column) &&
|
||||
bid_column.safe_equals(other.bid_column) &&
|
||||
ask_column.safe_equals(other.ask_column) &&
|
||||
price_column.safe_equals(other.price_column) &&
|
||||
last_column.safe_equals(other.last_column) &&
|
||||
piq_column.safe_equals(other.piq_column) &&
|
||||
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
|
||||
scroll_bar_visible == other.scroll_bar_visible &&
|
||||
default_account == other.default_account &&
|
||||
profile_equals(this, other) &&
|
||||
default_algo == other.default_algo &&
|
||||
default_time_in_force == other.default_time_in_force &&
|
||||
size == other.size &&
|
||||
animate_recenter == other.animate_recenter &&
|
||||
badge_style == other.badge_style &&
|
||||
auto_clear == other.auto_clear &&
|
||||
bid_break_line.safe_equals(other.bid_break_line) &&
|
||||
ask_break_line.safe_equals(other.ask_break_line) &&
|
||||
mid_break_line.safe_equals(other.mid_break_line) &&
|
||||
alternate_order_size == other.alternate_order_size &&
|
||||
session_low_line.safe_equals(other.session_low_line) &&
|
||||
session_high_line.safe_equals(other.session_high_line) &&
|
||||
session_range_fill.safe_equals(other.session_range_fill) &&
|
||||
volume_column.safe_equals(other.volume_column) &&
|
||||
size_display.safe_equals(other.size_display) &&
|
||||
day_visible == other.day_visible &&
|
||||
high_visible == other.high_visible &&
|
||||
low_visible == other.low_visible &&
|
||||
open_visible == other.open_visible &&
|
||||
settle_visible == other.settle_visible &&
|
||||
volume_visible == other.volume_visible &&
|
||||
account_visible == other.account_visible &&
|
||||
tif_visible == other.tif_visible &&
|
||||
order_type_visible == other.order_type_visible &&
|
||||
account_position_visible == other.account_position_visible &&
|
||||
day_position_visible == other.day_position_visible &&
|
||||
raw_day_position_visible == other.raw_day_position_visible &&
|
||||
size_presets_visible == other.size_presets_visible &&
|
||||
cancel_buys_visible == other.cancel_buys_visible &&
|
||||
cancel_sells_visible == other.cancel_sells_visible &&
|
||||
cancel_all_visible == other.cancel_all_visible &&
|
||||
auto_clear_visible == other.auto_clear_visible &&
|
||||
clear_visible == other.clear_visible &&
|
||||
order_entry_panel_initial_width == other.order_entry_panel_initial_width &&
|
||||
zoom_slider_visible == other.zoom_slider_visible &&
|
||||
zoom_factor == other.zoom_factor &&
|
||||
use_defaults == other.use_defaults &&
|
||||
favorite_algos == other.favorite_algos &&
|
||||
net_change == other.net_change &&
|
||||
show_warn_on_cross == other.show_warn_on_cross &&
|
||||
mini_ob == other.mini_ob;
|
||||
left_panel_scroll_position == other.left_panel_scroll_position;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as LadderSettings);
|
||||
|
||||
// TODO: remove mutable props from hash logic (or conversely, make them immutable).
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
row_height.GetHashCode(),
|
||||
grid_line_color.GetHashCode(),
|
||||
EqualityComparer.safe_get_hash_code(orders_column),
|
||||
EqualityComparer.safe_get_hash_code(bid_column),
|
||||
EqualityComparer.safe_get_hash_code(ask_column),
|
||||
EqualityComparer.safe_get_hash_code(price_column),
|
||||
EqualityComparer.safe_get_hash_code(last_column),
|
||||
EqualityComparer.safe_get_hash_code(piq_column),
|
||||
EqualityComparer.safe_get_hash_code(calculator_buttons),
|
||||
scroll_bar_visible.GetHashCode(),
|
||||
default_time_in_force.GetHashCode(),
|
||||
size.GetHashCode(),
|
||||
animate_recenter.GetHashCode(),
|
||||
badge_style.GetHashCode(),
|
||||
EqualityComparer.safe_get_hash_code(bid_break_line),
|
||||
EqualityComparer.safe_get_hash_code(ask_break_line),
|
||||
EqualityComparer.safe_get_hash_code(mid_break_line),
|
||||
alternate_order_size.GetHashCode(),
|
||||
EqualityComparer.safe_get_hash_code(session_low_line),
|
||||
EqualityComparer.safe_get_hash_code(session_high_line),
|
||||
EqualityComparer.safe_get_hash_code(session_range_fill),
|
||||
EqualityComparer.safe_get_hash_code(volume_column),
|
||||
EqualityComparer.safe_get_hash_code(size_display),
|
||||
day_visible.GetHashCode(),
|
||||
high_visible.GetHashCode(),
|
||||
low_visible.GetHashCode(),
|
||||
open_visible.GetHashCode(),
|
||||
settle_visible.GetHashCode(),
|
||||
volume_visible.GetHashCode(),
|
||||
account_visible.GetHashCode(),
|
||||
tif_visible.GetHashCode(),
|
||||
order_type_visible.GetHashCode(),
|
||||
account_position_visible.GetHashCode(),
|
||||
size_presets_visible.GetHashCode(),
|
||||
cancel_buys_visible.GetHashCode(),
|
||||
cancel_sells_visible.GetHashCode(),
|
||||
cancel_all_visible.GetHashCode(),
|
||||
auto_clear_visible.GetHashCode(),
|
||||
clear_visible.GetHashCode(),
|
||||
order_entry_panel_initial_width.GetHashCode(),
|
||||
zoom_slider_visible.GetHashCode(),
|
||||
zoom_factor.GetHashCode(),
|
||||
use_defaults.GetHashCode(),
|
||||
favorite_algos.GetHashCode(),
|
||||
net_change.GetHashCode(),
|
||||
show_warn_on_cross.GetHashCode());
|
||||
public override int GetHashCode() => _type_hash;
|
||||
|
||||
public struct WindowSpecifics {
|
||||
public readonly double order_entry_panel_width, zoom_factor;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
@@ -50,6 +51,7 @@ namespace bts.shell {
|
||||
_mbo = mbo;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderAutoLookupTag).Subscribe(_show_windows),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
|
||||
);
|
||||
@@ -79,11 +81,9 @@ namespace bts.shell {
|
||||
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
|
||||
// profile was deleted
|
||||
id = 0;
|
||||
} else {
|
||||
if (!s.Equals(result.value)) {
|
||||
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
|
||||
id = 0;
|
||||
}
|
||||
} else if (!LadderSettings.profile_equals(s, result.value)) {
|
||||
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
|
||||
id = 0;
|
||||
}
|
||||
}
|
||||
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
|
||||
@@ -147,7 +147,7 @@ namespace bts.shell {
|
||||
var curr = vm.get_save_state();
|
||||
if (settings_id == 0) {
|
||||
last_value = curr;
|
||||
} else if (!curr.Equals(last_value)) {
|
||||
} else if (!LadderSettings.profile_equals(curr, last_value)) {
|
||||
settings_id = 0;
|
||||
last_value = curr;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ namespace bts.shell {
|
||||
// not on a profile, don't respond to default settings changes
|
||||
return;
|
||||
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
if (!last_value.Equals(vm.get_save_state())) {
|
||||
if (!LadderSettings.profile_equals(last_value, vm.get_save_state())) {
|
||||
settings_id = 0;
|
||||
} else {
|
||||
settings_subject.OnNext(last_value = profile.value);
|
||||
@@ -209,6 +209,7 @@ namespace bts.shell {
|
||||
_ => _,
|
||||
vm.get_save_state(),
|
||||
UserSettings.FileExtensions.LADDER,
|
||||
LadderSettings.profile_equals,
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
@@ -273,7 +274,21 @@ namespace bts.shell {
|
||||
});
|
||||
}
|
||||
|
||||
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
|
||||
async void _show_windows(IImmutableSet<uint> securities) {
|
||||
if (securities == null)
|
||||
return;
|
||||
var start_time = DateTime.Now;
|
||||
do {
|
||||
var found = securities.KeyJoin(_securities).ToList();
|
||||
securities = securities.RemoveRange(found.Select(SecurityMD.key_fn));
|
||||
show_windows(found);
|
||||
if (securities.Count == 0)
|
||||
break;
|
||||
await Task.Delay(500);
|
||||
} while ((DateTime.Now - start_time) < _ladder_conf.security_timeout);
|
||||
}
|
||||
|
||||
void show_windows(IEnumerable<SecurityMD> securities) {
|
||||
int n_allowed = (int)_ladder_conf.max_open_ladders - _wm.list_windows().Where(w => w.window_type == _window_type).Count();
|
||||
int n_opened = 0;
|
||||
foreach (var s in securities) {
|
||||
|
||||
@@ -35,7 +35,14 @@ namespace mbts.spark {
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom;
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
|
||||
double _position, _day_position, _raw_day_position;
|
||||
double
|
||||
_position,
|
||||
_day_position,
|
||||
_raw_day_position,
|
||||
_left_panel_extent_height,
|
||||
_left_panel_view_port_height,
|
||||
_left_panel_scroll_position,
|
||||
_left_panel_scroll_position_target;
|
||||
bool _show_account_position, _show_day_position, _show_raw_day_position;
|
||||
ulong _misc_time = 0;
|
||||
ulong _last_trade_time = 0;
|
||||
@@ -76,6 +83,7 @@ namespace mbts.spark {
|
||||
_volume_column_width = backing(nameof(volume_column_width), ws.volume_column_width);
|
||||
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
|
||||
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
|
||||
_left_panel_scroll_position_target = init.left_panel_scroll_position;
|
||||
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
|
||||
_volume = backing(nameof(volume), 0ul);
|
||||
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
|
||||
@@ -333,6 +341,33 @@ namespace mbts.spark {
|
||||
public bool show_cancel_sells => _show_cancel_sells.view;
|
||||
public bool show_cancel_all => _show_cancel_all.view;
|
||||
public bool show_zoom => _show_zoom.view;
|
||||
// left panel scroll props
|
||||
public double left_panel_view_port_height {
|
||||
get => _left_panel_view_port_height;
|
||||
set {
|
||||
using (detect_property_changes(nameof(left_panel_scroll_position)))
|
||||
_left_panel_view_port_height = value;
|
||||
}
|
||||
}
|
||||
public double left_panel_scroll_position {
|
||||
get {
|
||||
if (!_left_panel_scroll_position_target.is_nan()) {
|
||||
if (_left_panel_scroll_position_target < _max_left_panel_scroll_position) {
|
||||
_left_panel_scroll_position = _left_panel_scroll_position_target;
|
||||
_left_panel_scroll_position_target = double.NaN;
|
||||
}
|
||||
}
|
||||
return Math.Min(_left_panel_scroll_position, _left_panel_extent_height);
|
||||
}
|
||||
set => Set(ref _left_panel_scroll_position, value);
|
||||
}
|
||||
public double left_panel_extent_height {
|
||||
get => _left_panel_extent_height;
|
||||
set {
|
||||
using (detect_property_changes(nameof(left_panel_scroll_position)))
|
||||
Set(ref _left_panel_extent_height, value);
|
||||
}
|
||||
}
|
||||
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
|
||||
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
|
||||
public string position_display_text {
|
||||
@@ -364,6 +399,10 @@ namespace mbts.spark {
|
||||
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings { get; }
|
||||
|
||||
double _max_left_panel_scroll_position =>
|
||||
Math.Max(0, _left_panel_extent_height - _left_panel_view_port_height);
|
||||
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
var order = op.order;
|
||||
double p = order.price();
|
||||
@@ -394,6 +433,7 @@ namespace mbts.spark {
|
||||
zoom_factor,
|
||||
piq_column_width));
|
||||
rv.auto_clear = auto_clear;
|
||||
rv.left_panel_scroll_position = left_panel_scroll_position;
|
||||
if (order_properties_vm != null) {
|
||||
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
|
||||
if (order_properties_vm.selected_account.HasValue)
|
||||
|
||||
@@ -301,7 +301,14 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Grid.Row="0" Grid.RowSpan="2" VerticalScrollBarVisibility="Hidden">
|
||||
<ScrollViewer
|
||||
x:Name="_left_panel_scroll"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
mbts:ScrollViewerBehavior.VerticalOffset="{Binding left_panel_scroll_position, Mode=TwoWay}"
|
||||
mbts:ScrollViewerBehavior.EnableTwoWayVerticalOffsetBinding="True"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
>
|
||||
<Grid RowSpan="2" Background="{mbts:BTSColor UI.spark_menu_background}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -80,6 +86,8 @@ namespace mbts.spark {
|
||||
_ladder.visible_columns_changed += () => _column_headers_presenter.Columns.SetItems(
|
||||
_ladder.visible_column_types.Select(t => ColumnFactory.create(_ladder, t)));
|
||||
this.vm = vm;
|
||||
this.ConnectDataContext<LadderVM>(lvm => lvm.bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(sec_map => Resources[ResourceKeys.Securities] = sec_map));
|
||||
vm.SubscribeDisposed(_connect_left_panel_scroll());
|
||||
DataContext = vm;
|
||||
DataContextChanged += datacontext_changed;
|
||||
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
|
||||
@@ -87,7 +95,39 @@ namespace mbts.spark {
|
||||
this.PreviewMouseDown += LadderView_PreviewMouseDown;
|
||||
}
|
||||
|
||||
|
||||
IDisposable _connect_left_panel_scroll() {
|
||||
// extent height
|
||||
bool busy = false;
|
||||
var extent_height_hook = _left_panel_scroll.ObserveProperty(ScrollViewer.ExtentHeightProperty, (s, e) => {
|
||||
if (!busy) {
|
||||
double scroll_pos = vm.left_panel_scroll_position;
|
||||
busy = true;
|
||||
Dispatcher.InvokeAsync(() => {
|
||||
vm.left_panel_extent_height = _left_panel_scroll.ExtentHeight;
|
||||
if (_left_panel_scroll.VerticalOffset != scroll_pos)
|
||||
_left_panel_scroll.TryScrollToVerticalOffset(scroll_pos);
|
||||
busy = false;
|
||||
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
});
|
||||
// view port height
|
||||
_left_panel_scroll.Loaded += on_loaded;
|
||||
var dispose = Disposable.CreateDisposeOnce(dispose_impl);
|
||||
return Disposable.Create(dispose, vm.SubscribeDisposed(dispose));
|
||||
// local methods
|
||||
void on_loaded(object sender, RoutedEventArgs e) {
|
||||
if (_left_panel_scroll.descendants_of_type<ScrollBar>().TryGetFirst(sb => sb.Orientation == Orientation.Vertical, out var vscroll)) {
|
||||
vscroll.SetBinding(ScrollBar.ViewportSizeProperty, new Binding(nameof(LadderVM.left_panel_view_port_height)) {
|
||||
Converter = ValueConverter.create<double, Size>(d => new Size(0, d), sz => sz.Height),
|
||||
Mode = BindingMode.OneWayToSource
|
||||
});
|
||||
}
|
||||
}
|
||||
void dispose_impl() {
|
||||
Disposable.dispose(ref extent_height_hook);
|
||||
_left_panel_scroll.Loaded -= on_loaded;
|
||||
}
|
||||
}
|
||||
|
||||
// (Left|Right) CTRL + Wheel to zoom
|
||||
// This is neat b/c the user doesn't even need to have the slider visible to zoom
|
||||
|
||||
@@ -62,15 +62,15 @@
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Securities}}"
|
||||
SelectedSecurity="{Binding security, UpdateSourceTrigger=Explicit}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
|
||||
/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Accounts}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Users}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Sides}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
|
||||
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -6,12 +6,6 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for ManualFillRowEditor.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillRowEditor : UserControl {
|
||||
public const string
|
||||
SecuritiesKey = nameof(SecuritiesKey),
|
||||
AccountsKey = nameof(AccountsKey),
|
||||
UsersKey = nameof(UsersKey),
|
||||
SidesKey = nameof(SidesKey);
|
||||
|
||||
public static readonly RoutedCommand
|
||||
CommitCommand = new RoutedCommand(),
|
||||
CancelCommand = new RoutedCommand();
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace bts.shell.manual_fills {
|
||||
mfd => new ManualFillRow(mfd),
|
||||
mfr => mfr.data),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
|
||||
security_map = m;
|
||||
security_map = new FilteredDictionary<uint, SecurityMD>(m, smd => smd.security.ContractCase == Security.ContractOneofCase.Future);
|
||||
RaisePropertyChanged(nameof(security_map));
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
|
||||
|
||||
@@ -50,11 +50,11 @@ namespace mbts.ui.spark {
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
|
||||
// dynamic resources
|
||||
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
|
||||
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
|
||||
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
|
||||
row_editor.Resources[ResourceKeys.Accounts] = vm.accounts;
|
||||
row_editor.Resources[ResourceKeys.Users] = vm.users;
|
||||
row_editor.Resources[ResourceKeys.Sides] = vm.sides;
|
||||
FocusManager.SetIsFocusScope(row_editor, true);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ResourceKeys.Securities] = v);
|
||||
// popup adorner
|
||||
var popup_adorner = new PopupAdorner(
|
||||
data_grid, data_grid,
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace mbts.spark {
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
model.factory.subscribe_users().Select(d => d.Values.Ordered()),
|
||||
ort => ort.order.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
@@ -133,7 +133,7 @@ namespace mbts.spark {
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
model.factory.subscribe_accounts().Select(d => d.Values.Ordered()),
|
||||
ort => ort.order.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
vals => Observable.Return(vals),
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace mbts.spark {
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
|
||||
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
|
||||
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
|
||||
public string root_algo_display_name => order_tuple.root.algo.name;
|
||||
public string root_algo_display_name => order_tuple.root.display_name();
|
||||
|
||||
// Tree
|
||||
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace mbts.spark {
|
||||
grid_settings_manager,
|
||||
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
|
||||
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
|
||||
order_mod_vm.algo_vm,
|
||||
order_mod_vm,
|
||||
attach_vm,
|
||||
_show_fills.observe_current(true)
|
||||
.Subscribe(is_visible => {
|
||||
@@ -735,17 +735,13 @@ namespace mbts.spark {
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
|
||||
: this(owner,
|
||||
AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions)) { }
|
||||
|
||||
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
_owner = owner;
|
||||
this.algo_vm = algo_vm;
|
||||
algo_vm = AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions);
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
_model.factory.subscribe_account_tuples(),
|
||||
@@ -784,7 +780,8 @@ namespace mbts.spark {
|
||||
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
|
||||
}
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
|
||||
algo_vm);
|
||||
// payup methods
|
||||
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
|
||||
void payup_ask(int ticks) => payup(bbo.ask, ticks);
|
||||
|
||||
@@ -66,6 +66,10 @@ namespace mbts.spark {
|
||||
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
_vm.SubscribeDisposed(
|
||||
_bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -6,8 +7,9 @@ using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged {
|
||||
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged, ITemplateSelectorKey {
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
readonly string _template_key;
|
||||
AlgoParamPermissions _permissions;
|
||||
bool _is_enabled, _has_value;
|
||||
T _committed_value, _value;
|
||||
@@ -15,15 +17,16 @@ namespace mbts.spark {
|
||||
public AlgoParamVM(string id, string display_name)
|
||||
: this(id, display_name, default, EqualityComparer<T>.Default) { }
|
||||
|
||||
public AlgoParamVM(string id, string display_name, T value)
|
||||
: this(id, display_name, value, EqualityComparer<T>.Default) { }
|
||||
public AlgoParamVM(string id, string display_name, T value, string template_key = null)
|
||||
: this(id, display_name, value, EqualityComparer<T>.Default, template_key) { }
|
||||
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer) {
|
||||
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer, string template_key = null) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
_value = value;
|
||||
_committed_value = value;
|
||||
_comparer = comparer;
|
||||
_template_key = template_key;
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
@@ -50,7 +53,9 @@ namespace mbts.spark {
|
||||
public bool is_enabled { get => _is_enabled; set => _set(ref _is_enabled, value); }
|
||||
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
|
||||
public bool is_dirty { get; private set; }
|
||||
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
|
||||
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
|
||||
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
public void revert() {
|
||||
if (is_dirty) {
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace mbts.spark {
|
||||
public AlgoDefinitionMetadata? root_algo_meta =>
|
||||
_order_tuple.HasValue ? _order_tuple.Value.root.meta : null;
|
||||
|
||||
public OrderSecurityAlgo? root_order_security => _order_tuple?.root;
|
||||
|
||||
public string account {
|
||||
get {
|
||||
var account_string = string.Empty;
|
||||
@@ -85,4 +87,4 @@ namespace mbts.spark {
|
||||
public static string user(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,6 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_users() => _admin_model.on_users;
|
||||
public IObservable<IReadOnlyList<UserTuple>> subscribe_user_tuples() => _admin_model.on_user_tuples;
|
||||
public IObservable<IReadOnlyList<AccountTuple>> subscribe_account_tuples() => _admin_model.on_account_tuples;
|
||||
|
||||
#region Defaults
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey:
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: param.AsQuantity = new bts.spark.QuantityMeasure(); break;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Reactive.Linq;
|
||||
namespace mbts.spark {
|
||||
interface IDynamicOrderParamVM {
|
||||
DynamicOrderParamDescriptor descriptor { get; }
|
||||
DynamicOrderParam param { get; set; }
|
||||
DynamicOrderParam param { get; }
|
||||
}
|
||||
|
||||
interface IDynamicOrderParamVM<T> : IDynamicOrderParamVM {
|
||||
@@ -21,26 +21,16 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
abstract class DynamicOrderParamVM : DisposableViewModelBase, IDynamicOrderParamVM {
|
||||
DynamicOrderParam _param;
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
|
||||
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
|
||||
|
||||
private DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
|
||||
this.descriptor = descriptor;
|
||||
_param = param;
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
public DynamicOrderParamDescriptor descriptor { get; set; }
|
||||
public DynamicOrderParam param {
|
||||
get => _param;
|
||||
set {
|
||||
_param = value;
|
||||
on_param_changed();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void on_param_changed();
|
||||
public DynamicOrderParam param { get; }
|
||||
}
|
||||
|
||||
class DynamicOrderParamVM<TView, TModel> : DynamicOrderParamVM, IDynamicOrderParamVM<TView> {
|
||||
@@ -53,14 +43,12 @@ namespace mbts.spark {
|
||||
_field_desc = field_descriptor;
|
||||
_convert = convert;
|
||||
_convert_back = convert_back;
|
||||
value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
|
||||
}
|
||||
|
||||
public TView value {
|
||||
get => _value;
|
||||
set {
|
||||
_field_desc.Accessor.SetValue(param, _convert_back(_value = value));
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
set => Set(ref _value, value);
|
||||
}
|
||||
|
||||
public IObservable<TView> subscribe_value() =>
|
||||
@@ -70,11 +58,6 @@ namespace mbts.spark {
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
o.OnNext(value);
|
||||
});
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicOrderEnumeratedVM : DynamicOrderParamVM, IDynamicOrderParamVM<string> {
|
||||
@@ -105,11 +88,6 @@ namespace mbts.spark {
|
||||
o.OnNext(selected_element);
|
||||
});
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_sync_elements();
|
||||
RaisePropertyChanged(nameof(selected_element));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
|
||||
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
|
||||
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
|
||||
@@ -148,10 +126,5 @@ namespace mbts.spark {
|
||||
o.OnNext(quantity);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
RaisePropertyChanged(nameof(quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ namespace mbts.ui.spark
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
public DataTemplate OrderDefTemplate { get; set; }
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
public DataTemplate SecurityKeyTemplate { get; set; }
|
||||
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
|
||||
@@ -38,6 +39,7 @@ namespace mbts.ui.spark
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DoubleTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return IntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return UIntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return SecurityKeyTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return StringTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: return OrderDefTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: return QuantityMeasureTemplate;
|
||||
|
||||
@@ -4,6 +4,7 @@ using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -73,9 +74,9 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return mk<double, double>(DynamicOrderParam.AsDoubleFieldNumber, v => v, v => v, double.NaN);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<int?, int>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveSecurityKey: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault(), template_key: TemplateKeys.SecurityKey);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return mk<string, string>(DynamicOrderParam.AsStringFieldNumber, v => v, v => v);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity:
|
||||
{
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: {
|
||||
var qm = new DynamicOrderQuantityVM(descriptor);
|
||||
return attached(qm, DynamicOrderHelper.get_quantity_value, DynamicOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
|
||||
}
|
||||
@@ -88,16 +89,16 @@ namespace mbts.spark {
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
|
||||
}
|
||||
|
||||
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default) {
|
||||
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default, string template_key = null) {
|
||||
var fdesc = DynamicOrderParam.Descriptor.FindFieldByNumber(field_number);
|
||||
var pvm = new DynamicOrderParamVM<TView, TModel>(descriptor, fdesc, convert, convert_back);
|
||||
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
|
||||
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value, template_key);
|
||||
|
||||
TView get_from_param(DynamicOrderParam param) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
|
||||
void assign_to_param(DynamicOrderParam param, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
|
||||
}
|
||||
|
||||
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
|
||||
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default, string template_key = null) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
|
||||
add_property<T>(
|
||||
pvm.descriptor.ParamName,
|
||||
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
|
||||
@@ -105,7 +106,8 @@ namespace mbts.spark {
|
||||
() => pvm.value,
|
||||
v => pvm.value = v,
|
||||
pvm.subscribe_value(),
|
||||
default_value);
|
||||
default_value,
|
||||
template_key: template_key);
|
||||
return pvm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace mbts.spark {
|
||||
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
|
||||
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
|
||||
case AlgoType.Composite: vm = composite(key, name, editing_permissions); break;
|
||||
case AlgoType.None: vm = new DefaultAlgoVM(); break;
|
||||
default: vm = generic(key, name, algo_type, editing_permissions); break;
|
||||
//default:
|
||||
// Log.Ui.debug_fatal($"Failed to create {typeof(IAlgoVM)}: unknown algo type '{algo_type}.'");
|
||||
@@ -166,7 +167,8 @@ namespace mbts.spark {
|
||||
};
|
||||
void subscribe_new_algo(IAlgoVM old_algo, IAlgoVM new_algo) {
|
||||
RaisePropertyChanged(sd.prop_name);
|
||||
Disposable.replace(ref def_sub, new CompositeDisposable());
|
||||
Disposable.dispose(ref def_sub);
|
||||
def_sub = new CompositeDisposable();
|
||||
if (assert(new_algo != null, sd.prop_name, "No algo specified.")) {
|
||||
def_sub.Add(new_algo.subscribe_definition().Subscribe(_ => _def_change_notifier.execute()));
|
||||
if (new_algo is INotifyDataErrorInfo ndei)
|
||||
@@ -255,14 +257,14 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.replace(ref _pending_load, Disposable.Create(
|
||||
Disposable.dispose(ref _pending_load);
|
||||
_pending_load = Disposable.Create(
|
||||
// load children
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.SubscribeEach(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
// load regular props
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
|
||||
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom)));
|
||||
}
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
@@ -298,11 +300,12 @@ namespace mbts.spark {
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.replace(ref _pending_load, Disposable.Create(
|
||||
Disposable.dispose(ref _pending_load);
|
||||
_pending_load = Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos))));
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load_many(algos)));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
// **assume that algos has more than 1 element
|
||||
@@ -542,8 +545,19 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
protected void add_property<TValue>(string prop_name, Func<TMutable, TValue> get_from_def, Action<TMutable, TValue> assign_to_def, Func<TValue> get_local, Action<TValue> set_local, IObservable<TValue> subscribe_local, TValue non_uniform_value = default, AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite) {
|
||||
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local()) { permissions = permissions };
|
||||
protected void add_property<TValue>(
|
||||
string prop_name,
|
||||
Func<TMutable, TValue> get_from_def,
|
||||
Action<TMutable, TValue> assign_to_def,
|
||||
Func<TValue> get_local,
|
||||
Action<TValue> set_local,
|
||||
IObservable<TValue> subscribe_local,
|
||||
TValue non_uniform_value = default,
|
||||
AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite,
|
||||
string template_key = null) {
|
||||
var param = new AlgoParamVM<TValue>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), get_local(), template_key) {
|
||||
permissions = permissions
|
||||
};
|
||||
_algo_params.Add(param);
|
||||
_auto_props[prop_name] = new AutoProp(prop_name,
|
||||
def => {
|
||||
@@ -816,12 +830,11 @@ namespace mbts.spark {
|
||||
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.Value.ContractCase)));
|
||||
foreach (var sd in _child_algo_selectors)
|
||||
sd.load_algo_data_recursive(algo_data, default_algo);
|
||||
Disposable.replace(
|
||||
ref _selector_data_hooks,
|
||||
ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
|
||||
s.load_algo_data_recursive(algo_data, default_algo);
|
||||
return Disposable.empty;
|
||||
}));
|
||||
Disposable.dispose(ref _selector_data_hooks);
|
||||
_selector_data_hooks = ObservableAutoWrapper.connect_item_hooks(child_algo_selectors, s => {
|
||||
s.load_algo_data_recursive(algo_data, default_algo);
|
||||
return Disposable.empty;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,9 @@ namespace mbts.spark {
|
||||
public static string display_name(this OrderSecurity order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
|
||||
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
|
||||
public static string display_name(OrderRecordImmutable order, AlgoData? algo, SecurityImmutable? security = null) {
|
||||
if (order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader && security.HasValue)
|
||||
return security.Value.ToString();
|
||||
if (algo.HasValue) {
|
||||
return algo.Value.name;
|
||||
} else if (order.HasDef && order.Def.HasCustomDef) {
|
||||
@@ -250,7 +252,7 @@ namespace mbts.spark {
|
||||
order_type.ToString().ToUpper();
|
||||
|
||||
public static string display_name(this OrderSecurityAlgo order_sec_algo) =>
|
||||
display_name(order_sec_algo.order, order_sec_algo.algo);
|
||||
display_name(order_sec_algo.order, order_sec_algo.algo, order_sec_algo.security);
|
||||
|
||||
public static string display_name(this CustomOrderDefinitionImmutable algo) =>
|
||||
algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic ? algo.Dynamic.DynamicOrderType : algo.CustomCase.display_name();
|
||||
|
||||
@@ -59,18 +59,26 @@ namespace mbts.spark {
|
||||
this.md_auxiliary_model = md_auxiliary_model;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders =
|
||||
_service
|
||||
.subscribei_orders()
|
||||
.Naggle(interval, scheduler)
|
||||
.PermissionFilter(admin_model.on_admin_data)
|
||||
.join(sstore, order => order.security_key())
|
||||
on_orders = admin_model
|
||||
.permission_filter(
|
||||
_service.subscribei_orders()
|
||||
.Naggle(interval, scheduler),
|
||||
o => o.account_id()
|
||||
).join(sstore, order => order.security_key())
|
||||
.Table()
|
||||
.Cache();
|
||||
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model.on_admin_data).join(sstore, pos => pos.SecurityId).Table().Cache();
|
||||
on_pnl = _position_service.subscribei_pnl_data()
|
||||
.Naggle(interval, scheduler)
|
||||
on_positions = admin_model
|
||||
.permission_filter(
|
||||
_position_service.subscribei_position_data().Naggle(interval, scheduler),
|
||||
p => p.AccountId)
|
||||
.join(sstore, pos => pos.SecurityId)
|
||||
.Table()
|
||||
.Cache();
|
||||
on_pnl = admin_model
|
||||
.permission_filter(
|
||||
_position_service.subscribei_pnl_data().Naggle(interval, scheduler),
|
||||
p => p.Pos.AccountId)
|
||||
.join(sstore, p => p.Pos.SecurityId)
|
||||
.TableCheckedAutoCorrect(EqualityComparer<(SecurityImmutable? sec, PNLUnitImmutable data)>.Default)
|
||||
.Where(t => t.has_changes())
|
||||
@@ -173,10 +181,11 @@ namespace mbts.spark {
|
||||
.Where(t => t.has_changes())
|
||||
.Cache();
|
||||
// fill tuples
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
.PermissionFilter(admin_model.on_admin_data)
|
||||
var on_fills = admin_model
|
||||
.permission_filter(
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler),
|
||||
f => f.Account)
|
||||
.join(sstore, f => f.SecurityKey)
|
||||
.Table().Cache();
|
||||
var fill_order_tuples_join = on_fills.LeftJoin(
|
||||
@@ -201,6 +210,7 @@ namespace mbts.spark {
|
||||
// manual fills
|
||||
on_manual_fills_securities = _position_service.subscribei_manual_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
.Filter(f => f.FillType == ManualFill.Types.FillType.Local)
|
||||
.join(sstore, mf => mf.SecurityKey)
|
||||
.Table().Cache();
|
||||
var manual_fill_tuples_account_join = on_manual_fills_securities.LeftJoin(
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace mbts {
|
||||
static class OrderSentEventPredicateVMFactory {
|
||||
public static Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> get_oneofs(IBus bus) =>
|
||||
new Dictionary<OrderSentEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderSentEventPredicate>> {
|
||||
{ OrderSentEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderSentEventPredicate>(p => p.security = null) },
|
||||
{ OrderSentEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderSentEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
|
||||
(p, s) => {
|
||||
if (p.security == null) {
|
||||
p.security = new SecurityPredicate();
|
||||
}
|
||||
p.security.is_value = s;
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderSentEventPredicate : IEquatable<OrderSentEventPredicate> {
|
||||
static readonly int _type_hash = typeof(OrderSentEventPredicate).GetHashCode();
|
||||
// arbitrary short time period (can override in config)
|
||||
static readonly TimeSpan _stale_order_threshold = TimeSpan.FromSeconds(config.Configuration.get<bts.config.OrderAlerts>().stale_order_threshold_seconds);
|
||||
|
||||
public enum PredicateTypeOneofCase {
|
||||
[Description("(any order)")]
|
||||
AnyOrder,
|
||||
[Description("choose securities")]
|
||||
SecurityOrder
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public SecurityPredicate security { get; set; }
|
||||
|
||||
public PredicateTypeOneofCase predicate_type_case {
|
||||
get {
|
||||
if (security != null)
|
||||
return PredicateTypeOneofCase.SecurityOrder;
|
||||
return PredicateTypeOneofCase.AnyOrder;
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<OrderSecurity> create(object obj) =>
|
||||
create_predicate((INotificationServiceDataCache)obj);
|
||||
|
||||
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) {
|
||||
var sec_pred = security?.create();
|
||||
return PredicateBuilder.create<OrderSecurity>(os =>
|
||||
// verify security
|
||||
(sec_pred == null || (os.security.HasValue && sec_pred.trigger(os.security.Value))) &&
|
||||
// verify not stale
|
||||
((DateTime.Now - os.order.created()) < _stale_order_threshold) &&
|
||||
// verify order hasn't been added yet
|
||||
// Note: the notification service should offer a way to connect predicates so that they only receive items in TableUpdate.added,
|
||||
// however, the notification service predated TableUpdate.Changes, so for now, just check that the history.Count == 1)
|
||||
(data_cache.order_history.TryGetValue(os.order.Key, out var hist) && hist.Count == 1));
|
||||
}
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public bool Equals(OrderSentEventPredicate other) =>
|
||||
other != null && other.security.safe_equals(other.security);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as OrderSentEventPredicate);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(_type_hash, security?.GetHashCode() ?? 0);
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,9 @@ using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
using PTOC = mbts.OrderRecordTuplePredicate.PredicateTypeOneofCase;
|
||||
|
||||
namespace mbts {
|
||||
@@ -20,6 +15,7 @@ namespace mbts {
|
||||
// TODO: uncomment the remaining cases when ready
|
||||
public static Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> get_oneofs(IBus bus, OrdersModel orders_model) {
|
||||
return new Dictionary<PTOC, ITemplatedPropertyWrapperVM<OrderRecordTuplePredicate>> {
|
||||
{ PTOC.SentEvent, wrap(rp => rp.sent_event, (rp, p) => rp.sent_event = p, p => p.predicate_type_case, OrderSentEventPredicateVMFactory.get_oneofs(bus)) },
|
||||
{ PTOC.FilledEvent, wrap(rp => rp.filled_event, (rp, p) => rp.filled_event = p, p => p.predicate_type_case, OrderFilledEventPredicateVMFactory.get_oneofs(bus)) },
|
||||
{ PTOC.CompletedEvent, wrap(rp => rp.completed_event, (rp, p) => rp.completed_event = p, p => p.predicate_type_case, OrderCompletedEventPredicateVMFactory.get_oneofs(bus)) },
|
||||
{ PTOC.CanceledEvent, wrap(rp => rp.canceled_event, (rp, p) => rp.canceled_event = p, p => p.predicate_type_case, OrderCanceledEventPredicateVMFactory.get_oneofs(bus)) },
|
||||
@@ -77,6 +73,7 @@ namespace mbts {
|
||||
OrderFilledEventPredicate _filled_event;
|
||||
OrderCompletedEventPredicate _completed_event;
|
||||
OrderCanceledEventPredicate _canceled_event;
|
||||
OrderSentEventPredicate _sent_event;
|
||||
|
||||
public enum PredicateTypeOneofCase {
|
||||
None,
|
||||
@@ -120,6 +117,8 @@ namespace mbts {
|
||||
OrderKey,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
ParentKey,
|
||||
[PredicateCase("When order is sent", PredicateCategory.Event)]
|
||||
SentEvent,
|
||||
[PredicateCase("When order gets a fill", PredicateCategory.Event)]
|
||||
FilledEvent,
|
||||
[PredicateCase("When order is completed", PredicateCategory.Event)]
|
||||
@@ -176,6 +175,8 @@ namespace mbts {
|
||||
public OrderCompletedEventPredicate completed_event { get => _completed_event; set => _set(ref _completed_event, value); }
|
||||
[ProtoMember(23)]
|
||||
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
|
||||
[ProtoMember(26)]
|
||||
public OrderSentEventPredicate sent_event { get => _sent_event; set => _set(ref _sent_event, value); }
|
||||
[ProtoMember(24)]
|
||||
public bool is_toast_enabled { get; set; }
|
||||
[ProtoMember(25)]
|
||||
@@ -206,6 +207,7 @@ namespace mbts {
|
||||
if (_filled_event != null) return PTOC.FilledEvent;
|
||||
if (_completed_event != null) return PTOC.CompletedEvent;
|
||||
if (_canceled_event != null) return PTOC.CanceledEvent;
|
||||
if (_sent_event != null) return PTOC.SentEvent;
|
||||
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
|
||||
return PTOC.None;
|
||||
}
|
||||
@@ -236,6 +238,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
|
||||
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
|
||||
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
|
||||
case PTOC.SentEvent: return _sent_event.create(data_cache);
|
||||
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key.create(), r => r.order.attached_target_order_key());
|
||||
default: return null;
|
||||
}
|
||||
@@ -285,6 +288,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
|
||||
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
|
||||
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
|
||||
case PTOC.SentEvent: return _sent_event.Equals(other._sent_event);
|
||||
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
|
||||
case PTOC.None: return true;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
@@ -320,6 +324,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
|
||||
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
|
||||
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
|
||||
case PTOC.SentEvent: raw_hash = _sent_event.GetHashCode(); break;
|
||||
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
|
||||
case PTOC.None: raw_hash = 0; break;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
@@ -355,6 +360,7 @@ namespace mbts {
|
||||
_filled_event = null;
|
||||
_completed_event = null;
|
||||
_canceled_event = null;
|
||||
_sent_event = null;
|
||||
_attached_target_order_key = null;
|
||||
}
|
||||
field = value;
|
||||
|
||||
@@ -8,6 +8,7 @@ using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.spark {
|
||||
@@ -41,6 +42,9 @@ namespace bts.spark {
|
||||
SparkCommand.create("P&L (2.0)", Icons.Accounting, SparkTags.ShowPNLTag);
|
||||
|
||||
// Ladder
|
||||
public static PartialCommand<IImmutableSet<uint>> ShowLadderAutoLookup(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderAutoLookupTag, key_gesture);
|
||||
|
||||
public static PartialCommand<SecurityMD> ShowLadder(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Ladder", Icons.Ladder, SparkTags.ShowLadderTag, key_gesture);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -55,6 +56,8 @@ namespace mbts.spark {
|
||||
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
|
||||
// For now, both tags go through the same logic in the LadderWindowFactory anyways.
|
||||
public static ITag<SecurityMD> ShowLadderTag { get; } = new SparkTag<SecurityMD>("Show Ladder");
|
||||
// TODO: get rid of the SecurityMD ShowLadderTag. This has created burdensome dependency issues.
|
||||
public static ITag<IImmutableSet<uint>> ShowLadderAutoLookupTag { get; } = new SparkTag<IImmutableSet<uint>>("Show Ladder Auto Lookup");
|
||||
public static ITag<IReadOnlyList<SecurityMD>> ShowLaddersTag { get; } = new SparkTag<IReadOnlyList<SecurityMD>>(nameof(ShowLaddersTag));
|
||||
public static ITag<IObservable<SecurityMD>> ShowLinkedLadder { get; } = new SparkTag<IObservable<SecurityMD>>("Linked Ladder");
|
||||
public static ITag<SecurityCriteria> ShowMarketViewTag { get; } = new SparkTag<SecurityCriteria>("Show MarketView");
|
||||
|
||||
@@ -1,7 +1,36 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts {
|
||||
public static class DoubleHelper {
|
||||
public static bool any_nan_or_infinity(params double[] values) {
|
||||
for (int i = 0; i < values.Length; i++) {
|
||||
if (double.IsNaN(values[i]) || double.IsInfinity(values[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool are_close(double v1, double v2, double threshold) =>
|
||||
!any_nan_or_infinity(v1, v2, threshold) && Math.Abs(threshold) > Math.Abs(v1 - v2);
|
||||
|
||||
// bc double.IsNaN gets annoying write, and a lazy programmer is a happy programmer ;)
|
||||
public static bool is_nan(this double value) =>
|
||||
double.IsNaN(value);
|
||||
|
||||
/// <summary>
|
||||
/// If the specified value is a <see cref="double"/>, <see cref="int"/>, or <see cref="uint"/>, returns the equivalent <see cref="double"/>; otherwise, returns <see cref="double.NaN"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert to a <see cref="double"/>.</param>
|
||||
/// <returns>The <see cref="double"/> equivalent value if the cast succeeds, otherwise <see cref="double.NaN"/>.</returns>
|
||||
public static double nan_cast(object value) {
|
||||
// devs: add numeric type checks as needed
|
||||
if (value is double d) return d;
|
||||
if (value is int i) return i;
|
||||
if (value is uint u) return u;
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
public static bool IsNaNOrInfinity(double d) => double.IsNaN(d) || double.IsInfinity(d);
|
||||
public static bool IsNaNOrInfinityOr(double d, params double[] values) => IsNaNOrInfinity(d) || values.Contains(d);
|
||||
public static bool IsOneOf(double d, params double[] values) => values.Contains(d);
|
||||
|
||||
@@ -11,6 +11,7 @@ using bts.api.persistence;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reactive.Linq;
|
||||
using bts.spark;
|
||||
using bts;
|
||||
|
||||
namespace mbts.persist
|
||||
{
|
||||
@@ -146,16 +147,18 @@ namespace mbts.persist
|
||||
}
|
||||
|
||||
[Export(typeof(IUserPersist))]
|
||||
public class UserUISettings : UISettingsBase
|
||||
{
|
||||
public class UserUISettings : UISettingsBase {
|
||||
string _user;
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
public UserUISettings(uint user_id, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: base(persist,err) {
|
||||
_user = $"User {user}";
|
||||
_user = $"User {user_id}";
|
||||
}
|
||||
|
||||
[ImportingConstructor]
|
||||
UserUISettings(UserTuple user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: this(user.id, persist, err) { }
|
||||
|
||||
protected override string user() { return _user; }
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ namespace bts.settings {
|
||||
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
|
||||
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
|
||||
static class SettingsMigrationHelper {
|
||||
public static bool apply<T>(T settings, IEnumerable<ISettingsMigrationRule<T>> migration_rules, out T new_settings) {
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace mbts.persist {
|
||||
static readonly IImmutableProperty<UserSettings, GridSettings>
|
||||
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
|
||||
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
|
||||
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs));
|
||||
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
|
||||
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
|
||||
|
||||
static readonly IImmutableProperty<UserSettings, PNLSettings>
|
||||
_pnl_propi = ImmutableProperty.create<UserSettings, PNLSettings>(us => us.pnl, (us, pnl) => us.with(pnl: pnl));
|
||||
@@ -36,6 +37,7 @@ namespace mbts.persist {
|
||||
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
|
||||
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
|
||||
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
|
||||
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
|
||||
readonly IUserPersist _persist;
|
||||
|
||||
UserSettings _default_settings;
|
||||
@@ -46,13 +48,15 @@ namespace mbts.persist {
|
||||
[ImportMany]IEnumerable<IFillsGridSettingsMigrationRule> fills_migration_rules,
|
||||
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
|
||||
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
|
||||
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules) {
|
||||
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
|
||||
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
|
||||
//
|
||||
_persist = persist;
|
||||
_fills_migration_rules = fills_migration_rules ?? Enumerable.Empty<IFillsGridSettingsMigrationRule>();
|
||||
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
|
||||
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
|
||||
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
|
||||
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
|
||||
init();
|
||||
async void init() {
|
||||
UserSettings s;
|
||||
@@ -62,7 +66,9 @@ namespace mbts.persist {
|
||||
var wrapped_rules = new[] {
|
||||
_fills_migration_rules.merge().wrap(_fills_propi),
|
||||
_mview_migration_rules.merge().wrap(_mview_propi),
|
||||
_pandl_migration_rules.merge().wrap(_pandl_propi)
|
||||
_pandl_migration_rules.merge().wrap(_pandl_propi),
|
||||
_pnl_migration_rules.merge().wrap(_pnl_propi),
|
||||
_synth_migration_rules.merge().wrap(_synth_propi)
|
||||
};
|
||||
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
|
||||
// TODO: move ladder sanitization logic into ISettingsMigrationRule
|
||||
@@ -136,6 +142,12 @@ namespace mbts.persist {
|
||||
_pnl_migration_rules,
|
||||
_pnl_propi);
|
||||
|
||||
public IWindowSettingsManager<GridSettings> create_synth_settings_manager(GridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
_synth_migration_rules,
|
||||
_synth_propi);
|
||||
|
||||
public async Task save_default(UserSettings value) {
|
||||
// notify observers before persisting so the changes are reflected immediately in the ui
|
||||
lock (_default_settings_lock) {
|
||||
|
||||
@@ -15,12 +15,8 @@ namespace bts.utils {
|
||||
}
|
||||
|
||||
static class ObjectSnapshot {
|
||||
public static IEnumerable<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() where TSource : class where TValue : class {
|
||||
var tval = typeof(TValue);
|
||||
return typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => tval.IsAssignableFrom(p.PropertyType))
|
||||
.Select(_prop_getter<TSource, TValue>);
|
||||
}
|
||||
public static IReadOnlyList<Func<TSource, TValue>> property_delegates_of_type<TSource, TValue>() =>
|
||||
PropertyGetterStore<TSource, TValue>.value;
|
||||
|
||||
public static IObservable<TableUpdate<(string name, object current, object original), string>> property_table<T>(T instance, params string[] prop_names) where T : INotifyPropertyChanged =>
|
||||
_property_table(instance, Array.ConvertAll(prop_names, PropertyInfoStore<T>.type.GetProperty));
|
||||
@@ -162,6 +158,18 @@ namespace bts.utils {
|
||||
}
|
||||
}
|
||||
|
||||
static class PropertyGetterStore<TSource, TValue> {
|
||||
public static readonly IReadOnlyList<Func<TSource, TValue>> value;
|
||||
|
||||
static PropertyGetterStore() {
|
||||
var tval = typeof(TValue);
|
||||
value = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => tval.IsAssignableFrom(p.PropertyType))
|
||||
.Select(_prop_getter<TSource, TValue>)
|
||||
.ToReadOnlyList();
|
||||
}
|
||||
}
|
||||
|
||||
class PropNode {
|
||||
public readonly PropertyInfo prop;
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
struct SyntheticTuple : IEquatable<SyntheticTuple>, IComparable<SyntheticTuple>, IComparable {
|
||||
public static readonly Func<SyntheticTuple, uint> key_fn = a => a.security.Id;
|
||||
public static readonly Func<SyntheticTuple?, uint> nullable_key_fn = a => a.HasValue ? a.Value.security.Id : 0u;
|
||||
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly string user_name;
|
||||
public readonly ExecutionStrategyImmutable? strategy;
|
||||
public readonly int? num_working_orders;
|
||||
|
||||
public SyntheticTuple(SecurityImmutable security, string user_name, ExecutionStrategyImmutable? strategy, int? num_working_orders = null) {
|
||||
this.security = security;
|
||||
this.user_name = user_name;
|
||||
this.strategy = strategy;
|
||||
this.num_working_orders = num_working_orders;
|
||||
}
|
||||
|
||||
public int CompareTo(SyntheticTuple other) =>
|
||||
SecurityHelper.Comparer.Compare(security, other.security);
|
||||
|
||||
int IComparable.CompareTo(object obj) =>
|
||||
obj is SyntheticTuple other ? CompareTo(other) : 1;
|
||||
|
||||
public bool Equals(SyntheticTuple other) =>
|
||||
security == other.security &&
|
||||
user_name == other.user_name &&
|
||||
EqualityComparer.nullable_equals(strategy, other.strategy);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security.Id.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
user_name == null ? security.ToString() : $"{user_name}:{security}";
|
||||
|
||||
public static bool operator ==(SyntheticTuple a, SyntheticTuple b) => a.Equals(b);
|
||||
public static bool operator !=(SyntheticTuple a, SyntheticTuple b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,7 @@ namespace mbts.ui.controls {
|
||||
Aggressor = "Aggressor",
|
||||
Volume = "Volume",
|
||||
Last = "Last",
|
||||
S = "S",
|
||||
Pos = "Pos",
|
||||
Working = "Working",
|
||||
Bid = "Bid",
|
||||
@@ -124,6 +125,7 @@ namespace mbts.ui.controls {
|
||||
SessionLow = "Low",
|
||||
SessionHigh = "High",
|
||||
FillKind = "Fill Kind",
|
||||
FillStatus = "Fill Status",
|
||||
Name = "Name",
|
||||
Description = "Description",
|
||||
N_Legs = "# Legs",
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Linq;
|
||||
namespace mbts.ui.controls {
|
||||
static class DoubleUtil {
|
||||
public const int API_DECIMAL_PRECISION = 9;
|
||||
// arbitrary small number that can be used to determine whether or not scroll changes are significant
|
||||
public const double MIN_SCROLL_DELTA = 0.000015;
|
||||
/* smallest such that 1.0+DBL_EPSILON != 1.0 */
|
||||
private const double EPSILON = 2.2204460492503131e-016;
|
||||
private const double BIG_EPSILON = EPSILON * 10;
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
using System;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
static class DataGridFilterHelper {
|
||||
public static IDisposable hook_filters<TRow>(ICollectionView rows, params IDataGridColumnFilter<TRow>[] filters) {
|
||||
public static IDisposable hook_all<TSource, TRow>(TSource source, ICollectionView rows) =>
|
||||
hook_filters(rows,
|
||||
ObjectSnapshot.property_delegates_of_type<TSource, IDataGridColumnFilter<TRow>>()
|
||||
.Select(f => f(source))
|
||||
.ExceptNull()
|
||||
.ToList());
|
||||
|
||||
public static IDisposable hook_filters<TRow>(ICollectionView rows, params IDataGridColumnFilter<TRow>[] filters) =>
|
||||
hook_filters(rows, filters.AsEnumerable());
|
||||
|
||||
public static IDisposable hook_filters<TRow>(ICollectionView rows, IEnumerable<IDataGridColumnFilter<TRow>> filters) {
|
||||
var icvls = (ICollectionViewLiveShaping)rows;
|
||||
icvls.IsLiveFiltering = true;
|
||||
return _hook_filters(set_filter, filters);
|
||||
@@ -22,7 +33,7 @@ namespace mbts.spark.filters {
|
||||
}
|
||||
}
|
||||
|
||||
static IDisposable _hook_filters<TRow>(Action set_filter, params IDataGridColumnFilter<TRow>[] filters) {
|
||||
static IDisposable _hook_filters<TRow>(Action set_filter, IEnumerable<IDataGridColumnFilter<TRow>> filters) {
|
||||
foreach (var f in filters)
|
||||
f.filter_changed += set_filter;
|
||||
set_filter();
|
||||
@@ -34,7 +45,7 @@ namespace mbts.spark.filters {
|
||||
});
|
||||
}
|
||||
|
||||
static List<IDataGridColumnFilter<TRow>> _get_active_filters<TRow>(IDataGridColumnFilter<TRow>[] filters) =>
|
||||
static List<IDataGridColumnFilter<TRow>> _get_active_filters<TRow>(IEnumerable<IDataGridColumnFilter<TRow>> filters) =>
|
||||
filters.Where(f => f.active.HasValue && f.active.Value).ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,10 +68,11 @@ namespace mbts.spark.filters {
|
||||
Func<TRow, TValue> factory,
|
||||
Func<TValue, TKey> key,
|
||||
out IObserver<TableUpdate<TValue, TKey>> items_observer,
|
||||
out ListCollectionView<TValue> items_view)
|
||||
out ListCollectionView<TValue> items_view,
|
||||
string prop_name = null)
|
||||
where TKey : struct, IEquatable<TKey> =>
|
||||
//
|
||||
CreateTableUpdate(factory, key, keys => keys, keys => keys.ToArray(), out items_observer, out items_view);
|
||||
CreateTableUpdate(factory, key, keys => keys, keys => keys.ToArray(), out items_observer, out items_view, prop_name);
|
||||
|
||||
public static IDataGridColumnPersistentFilter<TRow, TState> CreateTableUpdate<TRow, TValue, TKey, TState>(
|
||||
Func<TRow, TValue> factory,
|
||||
@@ -79,10 +80,11 @@ namespace mbts.spark.filters {
|
||||
Func<TState, IEnumerable<TKey>> load_state,
|
||||
Func<IEnumerable<TKey>, TState> get_state,
|
||||
out IObserver<TableUpdate<TValue, TKey>> items_observer,
|
||||
out ListCollectionView<TValue> items_view)
|
||||
out ListCollectionView<TValue> items_view,
|
||||
string prop_name = null)
|
||||
where TKey : struct, IEquatable<TKey> {
|
||||
//
|
||||
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TState>(factory, key, load_state, get_state);
|
||||
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TState>(factory, key, load_state, get_state, prop_name);
|
||||
items_observer = filter;
|
||||
items_view = filter.items_view;
|
||||
return filter;
|
||||
@@ -291,8 +293,8 @@ namespace mbts.spark.filters {
|
||||
readonly HashSet<TKey> _selected_keys = new HashSet<TKey>(), _desired_keys = new HashSet<TKey>();
|
||||
ListCollectionView<TValue> _items_view;
|
||||
|
||||
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state)
|
||||
: base(factory, null) {
|
||||
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state, string prop_name)
|
||||
: base(factory, prop_name) {
|
||||
_factory = factory;
|
||||
_key = key;
|
||||
_load_state = load_state;
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace mbts.ui {
|
||||
public TimeTextTuple end => _end;
|
||||
public bool is_active =>
|
||||
_mode == TimeFilterMode.Today ||
|
||||
_mode == TimeFilterMode.CMEOpen ||
|
||||
(_mode == TimeFilterMode.Period && (_begin.result.HasValue || _end.result.HasValue));
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
using bts.support;
|
||||
using Google.Protobuf;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -18,6 +12,13 @@ using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using bts.support;
|
||||
using Google.Protobuf;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
|
||||
namespace bts.view.controls {
|
||||
partial class SupportRequestWindow : Window {
|
||||
@@ -50,28 +51,40 @@ namespace bts.view.controls {
|
||||
_base = report_base;
|
||||
send_command = new DelegateCommand(send_report, _ => !string.IsNullOrWhiteSpace(_text.Value));
|
||||
_text = backing(nameof(user_text), "");
|
||||
_text.Changed += (_,__) => send_command.TryRaiseCanExecuteChanged();
|
||||
_text.Changed += (_, __) => send_command.TryRaiseCanExecuteChanged();
|
||||
_contact = backing(nameof(user_contact), "");
|
||||
_contact.Changed += (_,__) => send_command.TryRaiseCanExecuteChanged();
|
||||
_contact.Changed += (_, __) => send_command.TryRaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
async void send_report(object _) {
|
||||
ByteString screenshot = null;
|
||||
if (include_screenshot) {
|
||||
var stream = ScreenCapture.capture_encoded("jpeg");
|
||||
screenshot = await ByteString.FromStreamAsync(stream);
|
||||
}
|
||||
var reply = await _service.send_report_async(_base.with(
|
||||
user_description: _text.Value,
|
||||
contact: user_contact,
|
||||
log: include_logs ? LogUtil.read_logs() : "",
|
||||
image: screenshot
|
||||
));
|
||||
if (reply.success) {
|
||||
BTSMessageBox.Show("Your message has been received.", "Thank You", BTSMessageBoxButtons.OK, MessageBoxImage.Information, is_modal: false, top_most:true);
|
||||
}
|
||||
else {
|
||||
BTSMessageBox.Show($"There was an error submitting your message. '{reply.error_message}'", "Error", BTSMessageBoxButtons.OK, MessageBoxImage.Error, is_modal: false, top_most: true);
|
||||
using (var d = new BusyDialog { Text = "Sending your error report...", Topmost = true }) {
|
||||
d.Show();
|
||||
ByteString screenshot = null;
|
||||
if (include_screenshot) {
|
||||
var stream = ScreenCapture.capture_encoded("jpeg");
|
||||
screenshot = await ByteString.FromStreamAsync(stream);
|
||||
}
|
||||
var request = await Task.Run(() => {
|
||||
var result = _base.with(
|
||||
user_description: _text.Value,
|
||||
contact: user_contact,
|
||||
image: screenshot);
|
||||
if (include_logs) {
|
||||
result = spark.ErrorReporter.append_logs(result);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
var reply = await _service.send_report_async(request);
|
||||
if (reply.success) {
|
||||
Log.Ui.info("Support request message has been received.");
|
||||
BTSMessageBox.Show("Your message has been received.", "Thank You", BTSMessageBoxButtons.OK, MessageBoxImage.Information, is_modal: false, top_most: true);
|
||||
}
|
||||
else {
|
||||
var errmsg = $"There was an error submitting your message. '{reply.error_message}'";
|
||||
Log.Ui.error(errmsg);
|
||||
BTSMessageBox.Show(errmsg, "Error", BTSMessageBoxButtons.OK, MessageBoxImage.Error, is_modal: false, top_most: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1145,6 +1145,30 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.UIntTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.SecurityKeyTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<c:ContractPickerPopup
|
||||
Grid.Column="2"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static u:ResourceKeys.Securities}}"
|
||||
SelectedSecurityKey="{Binding value}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.SecurityKeyTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.StringTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
@@ -1181,30 +1205,35 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.EnumTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<uag:QuantityMeasureControl
|
||||
Grid.Column="2" Focusable="False" HorizontalAlignment="Stretch"
|
||||
QuantityMeasure="{Binding quantity, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<uag:QuantityMeasureControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</uag:QuantityMeasureControl.CommandBindings>
|
||||
<uag:QuantityMeasureControl.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</uag:QuantityMeasureControl.InputBindings>
|
||||
</uag:QuantityMeasureControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"
|
||||
/>
|
||||
<uag:QuantityMeasureControl
|
||||
Grid.Column="2"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
QuantityMeasure="{Binding quantity, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl"
|
||||
>
|
||||
<uag:QuantityMeasureControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</uag:QuantityMeasureControl.CommandBindings>
|
||||
<uag:QuantityMeasureControl.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</uag:QuantityMeasureControl.InputBindings>
|
||||
</uag:QuantityMeasureControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.QuantityMeasureTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector>
|
||||
</ItemsControl.ItemTemplateSelector>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -457,15 +457,15 @@
|
||||
Name="PART_TextBox"
|
||||
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
|
||||
BorderThickness="0">
|
||||
<TextBox.Style>
|
||||
<TextBox.Style>
|
||||
<Style TargetType="{x:Type TextBox}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
</c:TickUpDown>
|
||||
</ControlTemplate>
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
|
||||
u:Behavior.DisablePaste="True"
|
||||
Height="20"
|
||||
BorderThickness="0"
|
||||
>
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int 1}"/>
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace mbts.ui.converters {
|
||||
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert) =>
|
||||
new AnonymousValueConverter<TIn, TOut>(convert);
|
||||
|
||||
public static IValueConverter create<TIn, TOut>(Func<TIn, TOut> convert, Func<TOut, TIn> convert_back) =>
|
||||
new AnonymousValueConverter<TIn, TOut>(convert, convert_back);
|
||||
|
||||
public static IMultiValueConverter create<TIn1, TIn2, TOut>(Func<TIn1, TIn2, TOut> convert) =>
|
||||
new AnonymousMultiValueConverter<TIn1, TIn2, TOut>(convert);
|
||||
|
||||
@@ -28,9 +31,17 @@ namespace mbts.ui.converters {
|
||||
IntTextConverter = new IntTextConverter(),
|
||||
UIntTextConverter = new UIntTextConverter(),
|
||||
QuantityMeasureTextConverter = ValueConverter.create<QuantityMeasureImmutable?, string>(fmt),
|
||||
SecuritySelectorText,
|
||||
SimpleString = new AnonymousValueConverter(obj => obj?.ToString()),
|
||||
ThicknessToDouble = new ThicknessToDoubleConverter();
|
||||
|
||||
static ValueConverters() {
|
||||
SecuritySelectorText = new NullSubstitutionConverterWrapper {
|
||||
Converter = SimpleString,
|
||||
ValueForNull = "(None)"
|
||||
};
|
||||
}
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
AreEqualConverter = new AreEqualConverter();
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class DictionaryLookupConverter : IMultiValueConverter {
|
||||
[ThreadStatic]
|
||||
Dictionary<Type, Lookup> _type_impls_backing;
|
||||
Dictionary<Type, Lookup> _type_impls => _type_impls_backing ?? (_type_impls_backing = new Dictionary<Type, Lookup>());
|
||||
|
||||
delegate bool Lookup(object source, object key, out object result);
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (values.Length == 2 && values[0] != null && values[1] != null) {
|
||||
var lookup_type = values[0].GetType();
|
||||
var lookup = _type_impls.GetOrAdd(lookup_type, TypeImplResolver.resolve);
|
||||
if (lookup(values[0], values[1], out var result))
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
static class TypeImplResolver {
|
||||
public static Lookup resolve(Type lookup_type) =>
|
||||
_prioritized_impls(lookup_type)
|
||||
.OrderBy(tup => tup.priority)
|
||||
.Select(tup => tup.impl)
|
||||
.FirstOrDefault() ?? _lookup_fail;
|
||||
|
||||
static bool _lookup_fail(object source, object key, out object result) {
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static IEnumerable<(int priority, Lookup impl)> _prioritized_impls(Type lookup_type) {
|
||||
foreach (var itype in lookup_type.GetInterfaces()) {
|
||||
if (itype.IsGenericType) {
|
||||
var gen_def = itype.GetGenericTypeDefinition();
|
||||
if (gen_def == typeof(IDictionary<,>))
|
||||
yield return (1, _get_generic_impl(nameof(_GenericImpls<int, int>.idictionary), itype));
|
||||
if (gen_def == typeof(IReadOnlyDictionary<,>))
|
||||
yield return (2, _get_generic_impl(nameof(_GenericImpls<int, int>.ireadonly_dictionary), itype));
|
||||
if (gen_def == typeof(ILookup<,>))
|
||||
yield return (3, _get_generic_impl(nameof(_GenericImpls<int, int>.ilookup), itype));
|
||||
}
|
||||
if (itype == typeof(IDictionary))
|
||||
yield return (4, _NonGenericImpls.idictionary);
|
||||
}
|
||||
}
|
||||
|
||||
static Lookup _get_generic_impl(string impl_name, Type impl_type) =>
|
||||
_get_generic_impl(impl_name, impl_type.GetGenericArguments());
|
||||
|
||||
static Lookup _get_generic_impl(string impl_name, Type[] key_value_types) =>
|
||||
(Lookup)typeof(_GenericImpls<,>)
|
||||
.MakeGenericType(key_value_types)
|
||||
.GetField(nameof(_GenericImpls<int, int>.idictionary))
|
||||
.GetValue(null);
|
||||
|
||||
static class _GenericImpls<TKey, TValue> {
|
||||
public static readonly Lookup
|
||||
idictionary = _idictionary,
|
||||
ireadonly_dictionary = _ireadonly_dictionary,
|
||||
ilookup = _ilookup;
|
||||
|
||||
static bool _idictionary(object lookup, object key, out object result) {
|
||||
if (key is TKey k && lookup is IDictionary<TKey, TValue> d && d.TryGetValue(k, out var value)) {
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _ireadonly_dictionary(object lookup, object key, out object result) {
|
||||
if (key is TKey k && lookup is IReadOnlyDictionary<TKey, TValue> rod && rod.TryGetValue(k, out var value)) {
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _ilookup(object lookup, object key, out object result) {
|
||||
if ((key is TKey k) && lookup is ILookup<TKey, TValue> l && l.Contains(k)) {
|
||||
result = l[k];
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static class _NonGenericImpls {
|
||||
public static readonly Lookup
|
||||
idictionary = _idictionary;
|
||||
|
||||
static bool _idictionary(object lookup, object key, out object result) {
|
||||
var dict = (IDictionary)lookup;
|
||||
if (dict.Contains(key)) {
|
||||
result = dict[key];
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,29 @@ using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
|
||||
|
||||
namespace mbts.ui.extensions {
|
||||
static class FrameworkElementHelper {
|
||||
/// <summary>
|
||||
/// Invokes the specified action immediately if the element is already loaded, otherwise hooks up to the <see cref="FrameworkElement.Loaded"/> event,
|
||||
/// and [conditionally]invokes the specified action if <paramref name="should_still_execute"/> returns true.
|
||||
/// </summary>
|
||||
/// <param name="element">The element that must be loaded before the specified action is invoked.</param>
|
||||
/// <param name="action">The action to invoke when the element is loaded.</param>
|
||||
/// <param name="should_still_execute">If the element is not already loaded, this delegate determines whether or not to invoke the action in the Loaded event callback.</param>
|
||||
/// <returns></returns>
|
||||
public static void InvokeOnLoaded(this FrameworkElement element, Action action, Func<bool> should_still_execute) {
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
if (element.IsLoaded) {
|
||||
action();
|
||||
} else {
|
||||
element.Loaded += on_loaded;
|
||||
void on_loaded(object sender, RoutedEventArgs e) {
|
||||
element.Loaded -= on_loaded;
|
||||
if (should_still_execute())
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Point GetCenter(this FrameworkElement element) =>
|
||||
new Point(element.ActualWidth * 0.5, element.ActualHeight * 0.5);
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui {
|
||||
static class ScrollViewerBehavior {
|
||||
public static readonly DependencyProperty VerticalOffsetProperty =
|
||||
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerBehavior), new PropertyMetadata(double.NaN, OnVerticalOffsetChanged));
|
||||
|
||||
static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is ScrollViewer scroll) {
|
||||
double voff = DoubleHelper.nan_cast(e.NewValue);
|
||||
// Ignore small deltas to prevent strange layout rounding issues/loops.
|
||||
// Be mindful that small deltas are not accumulated into a single larger delta.
|
||||
if (!double.IsNaN(voff) && !DoubleHelper.are_close(DoubleHelper.nan_cast(e.OldValue), voff, DoubleUtil.MIN_SCROLL_DELTA)) {
|
||||
scroll.InvokeOnLoaded(() => scroll.ScrollToVerticalOffset(voff),
|
||||
() => scroll.GetValue(VerticalOffsetProperty) is double curr_voff && curr_voff == voff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetVerticalOffset(DependencyObject d) => (double)d.GetValue(VerticalOffsetProperty);
|
||||
public static void SetVerticalOffset(DependencyObject d, double value) => d.SetValue(VerticalOffsetProperty, value);
|
||||
|
||||
public static readonly DependencyProperty EnableTwoWayVerticalOffsetBindingProperty =
|
||||
DependencyProperty.RegisterAttached("EnableTwoWayVerticalOffsetBinding", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, OnEnableTwoWayVerticalOffsetBindingChanged));
|
||||
|
||||
static void OnEnableTwoWayVerticalOffsetBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is ScrollViewer scroll) {
|
||||
if ((bool)e.NewValue) {
|
||||
scroll.ScrollChanged += EnableTwoWayVerticalOffsetBindingUtils.on_scroll_changed;
|
||||
} else {
|
||||
scroll.ScrollChanged -= EnableTwoWayVerticalOffsetBindingUtils.on_scroll_changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetEnableTwoWayVerticalOffsetBinding(DependencyObject d) => (bool)d.GetValue(EnableTwoWayVerticalOffsetBindingProperty);
|
||||
public static void SetEnableTwoWayVerticalOffsetBinding(DependencyObject d, bool value) => d.SetValue(EnableTwoWayVerticalOffsetBindingProperty, value);
|
||||
|
||||
public static readonly DependencyProperty VerticalOffsetMirrorBusyProperty =
|
||||
DependencyProperty.RegisterAttached("VerticalOffsetMirrorBusy", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false));
|
||||
|
||||
static class EnableTwoWayVerticalOffsetBindingUtils {
|
||||
public static void on_scroll_changed(object sender, ScrollChangedEventArgs e) {
|
||||
if (e.VerticalChange != 0 && sender is ScrollViewer scroll) {
|
||||
if (!(bool)scroll.GetValue(VerticalOffsetMirrorBusyProperty)) {
|
||||
scroll.SetValue(VerticalOffsetMirrorBusyProperty, true);
|
||||
// scroll changes could get noisy while layouts are being processed;
|
||||
// let everything settle down before updating the VerticalOffset binding sources/targets.
|
||||
scroll.Dispatcher.InvokeAsync(() => {
|
||||
scroll.SetCurrentValue(VerticalOffsetProperty, scroll.VerticalOffset);
|
||||
scroll.SetValue(VerticalOffsetMirrorBusyProperty, false);
|
||||
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,6 +236,14 @@ namespace mbts.ui.extensions {
|
||||
target.ScrollChanged += callback;
|
||||
return Disposable.Create(() => target.ScrollChanged -= callback);
|
||||
}
|
||||
|
||||
public static bool TryScrollToVerticalOffset(this ScrollViewer scroll_viewer, double vertical_offset) {
|
||||
if (vertical_offset >= 0 && scroll_viewer.IsLoaded && vertical_offset < scroll_viewer.ExtentHeight - scroll_viewer.ViewportHeight) {
|
||||
scroll_viewer.ScrollToVerticalOffset(vertical_offset);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextBoxExtensions {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace mbts.ui {
|
||||
static class ResourceKeys {
|
||||
public const string
|
||||
Securities = nameof(Securities),
|
||||
Accounts = nameof(Accounts),
|
||||
Users = nameof(Users),
|
||||
Sides = nameof(Sides);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mbts.ui {
|
||||
static class TemplateKeys {
|
||||
public const string
|
||||
SecurityKey = nameof(SecurityKey);
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,7 @@ namespace mbts.ui.shell {
|
||||
// The LoginContainer participates in the startup Import/Export dependency system
|
||||
// Since the login form should not depend on *anything* it will be shown before this is created
|
||||
// Once this is created it access the global LoginModel and retrieves the username
|
||||
internal class LoginContainer
|
||||
{
|
||||
class LoginContainer {
|
||||
[ImportingConstructor]
|
||||
public LoginContainer(ServiceContext context) {
|
||||
var model = LoginModel.instance;
|
||||
@@ -28,10 +27,8 @@ namespace mbts.ui.shell {
|
||||
model.Dispose();
|
||||
}
|
||||
|
||||
[Export(Exports.UserName)]
|
||||
public string UserName { get; private set; }
|
||||
|
||||
[Export(Exports.UserID)]
|
||||
public uint UserId { get; private set; }
|
||||
|
||||
[Export]
|
||||
@@ -42,13 +39,6 @@ namespace mbts.ui.shell {
|
||||
|
||||
[Export("workspace")]
|
||||
public string Workspace { get; private set; }
|
||||
|
||||
public static class Exports {
|
||||
public const string
|
||||
UserName = "username",
|
||||
UserID = "userid",
|
||||
UserTuple = "usertuple";
|
||||
}
|
||||
}
|
||||
|
||||
public struct User {
|
||||
|
||||
@@ -130,8 +130,10 @@ namespace mbts.spark {
|
||||
DialogSkirtItem _apply_item, _ok_item, _cancel_item;
|
||||
MenuItemVM _import_item, _export_item, _load_factory_item;
|
||||
|
||||
public ProfiledShellSettingsDialogVM(ISettingsProfileService<T> profile_service, Func<UserSettings, T> get_fallback, Func<T, TVM> vm_factory, Func<T, T> normalize, T init, string file_ext, uint settings_id = 0) {
|
||||
public ProfiledShellSettingsDialogVM(ISettingsProfileService<T> profile_service, Func<UserSettings, T> get_fallback, Func<T, TVM> vm_factory, Func<T, T> normalize, T init, string file_ext, Func<T, T, bool> profile_equals = null, uint settings_id = 0) {
|
||||
_vm_factory = vm_factory;
|
||||
if (profile_equals == null)
|
||||
profile_equals = EqualityComparer<T>.Default.Equals;
|
||||
// profile collections
|
||||
var skip_local = profiles.WrapSkip(1);
|
||||
var skip_local_wrapper = skip_local.WrapSelector(
|
||||
@@ -185,12 +187,19 @@ namespace mbts.spark {
|
||||
continue;
|
||||
i = ~i;
|
||||
var temp = ns.with(value: normalize(ns.value));
|
||||
skip_local_wrapper.Insert(i, temp);
|
||||
if (settings_id == temp.id) {
|
||||
if (temp.value.Equals(init))
|
||||
selected_profile = skip_local[i];
|
||||
settings_id = 0;
|
||||
if (profile_equals(temp.value, init)) {
|
||||
if (!temp.value.Equals(init)) {
|
||||
// they aren't exactly equal (don't overwrite any props in 'init' - use it to create the vm)
|
||||
temp = temp.with(value: init);
|
||||
}
|
||||
skip_local_wrapper.Insert(i, temp);
|
||||
selected_profile = skip_local[i];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
skip_local_wrapper.Insert(i, temp);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -114,10 +114,16 @@ namespace mbts.ui
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
foreach (IShellModule module in _modules.OrderBy(m => m.priority).Reverse()) {
|
||||
foreach (var module in _modules.OrderBy(m => m.priority).Reverse()) {
|
||||
module.Dispose();
|
||||
}
|
||||
_modules.Clear();
|
||||
foreach (var daemon in _services) {
|
||||
if (daemon is IDisposable d) {
|
||||
d.Dispose();
|
||||
}
|
||||
}
|
||||
_services.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,7 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.shell {
|
||||
[Export(typeof(ILauncher))]
|
||||
public class LauncherVM : ViewModelBase, ILauncher
|
||||
{
|
||||
public class LauncherVM : ViewModelBase, ILauncher {
|
||||
Window _window;
|
||||
string _user;
|
||||
readonly IBus _bus;
|
||||
@@ -23,9 +22,9 @@ namespace mbts.ui.shell {
|
||||
[ImportingConstructor]
|
||||
public LauncherVM(
|
||||
ObservableCollection<IShellModule> modules,
|
||||
[Import(LoginContainer.Exports.UserName)]string username,
|
||||
UserTuple user,
|
||||
ErrorReporter errors, IBus bus) {
|
||||
_user = username;
|
||||
_user = user.name;
|
||||
_modules = new ObservableAutoWrapper<ShellModuleVM, IShellModule>(modules, m => new ShellModuleVM(m, bus));
|
||||
_error_service = errors;
|
||||
_bus = bus;
|
||||
@@ -123,7 +122,7 @@ namespace mbts.ui.shell {
|
||||
_shown = true;
|
||||
if (_window != null) {
|
||||
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||
if (shown != null) shown();
|
||||
shown?.Invoke();
|
||||
_window.Show();
|
||||
}
|
||||
}
|
||||
@@ -140,4 +139,4 @@ namespace mbts.ui.shell {
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ namespace mbts.ui.shell
|
||||
try {
|
||||
Dispatcher.Run();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.assert(false, e.ToString());
|
||||
catch (Exception ex) {
|
||||
Log.assert(false, $"Exception on dispatcher thread '{name}': {ex}.");
|
||||
}
|
||||
finally {
|
||||
Log.Ui.info("Disposing slow {0} and cleaning up background thread.".fmt(name));
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace mbts.spark {
|
||||
readonly IRaiseCanExecuteChangedCommand _add_criteria_command, _remove_criteria_command, _ok_button_command;
|
||||
|
||||
bool _allow_empty, _is_edit_mode;
|
||||
SecurityImmutable? _pending_sec;
|
||||
(SecurityCriteria criteria, SecurityImmutable? sec, SecurityMD? md)? _snapshot;
|
||||
uint? _pending_sec_key;
|
||||
(SecurityCriteria criteria, uint? sec, SecurityMD? md)? _snapshot;
|
||||
|
||||
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null)
|
||||
: this(bus.subscribe(SecuritiesTag), edit_mode, existing_criteria, world_view_filter) { }
|
||||
@@ -209,9 +209,9 @@ namespace mbts.spark {
|
||||
_update_security_types();
|
||||
_update_symbols();
|
||||
_update_securities();
|
||||
if (_pending_sec.HasValue && changed_securities.ContainsKey(_pending_sec.Value.Id)) {
|
||||
select_security_request?.Invoke(_pending_sec.Value);
|
||||
_pending_sec = null;
|
||||
if (_pending_sec_key.HasValue && changed_securities.TryGetValue(_pending_sec_key.Value, out var smd)) {
|
||||
select_security_request?.Invoke(smd.security);
|
||||
_pending_sec_key = null;
|
||||
}
|
||||
}
|
||||
internal void criteria_selection_changed(IEnumerable<SecurityCriteriaClause> selected_clauses) {
|
||||
@@ -229,6 +229,8 @@ namespace mbts.spark {
|
||||
security_criteria_clause_changed?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public bool is_editing => _snapshot.HasValue;
|
||||
|
||||
public ObservableCollection<Venue> all_exchanges { get; } = new ObservableCollection<Venue>();
|
||||
|
||||
public List<Venue> selected_exchanges = new List<Venue>();
|
||||
@@ -298,7 +300,22 @@ namespace mbts.spark {
|
||||
if (all_securities.ContainsKey(value.Value.security.Id)) {
|
||||
select_security_request?.Invoke(value.Value.security);
|
||||
} else {
|
||||
_pending_sec = value.Value.security;
|
||||
_pending_sec_key = value.Value.security.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint? selected_security_key {
|
||||
get => selected_securities.Count == 1 && all_securities.TryGetValue(selected_securities[0], out var smd) ? smd.security.Id : (uint?)null;
|
||||
set {
|
||||
if (value != selected_security_key) {
|
||||
if (value.HasValue) {
|
||||
if (all_securities.TryGetValue(value.Value, out var smd)) {
|
||||
select_security_request?.Invoke(smd.security);
|
||||
} else {
|
||||
_pending_sec_key = value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,7 +330,7 @@ namespace mbts.spark {
|
||||
if (all_securities.ContainsKey(value.Value.Id)) {
|
||||
select_security_request?.Invoke(value.Value);
|
||||
} else {
|
||||
_pending_sec = value.Value;
|
||||
_pending_sec_key = value.Value.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,18 +353,19 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
#region IEditableObject
|
||||
public void BeginEdit() => _snapshot = (result_criteria, _pending_sec ?? selected_security, selected_security_md);
|
||||
public void BeginEdit() => _snapshot = (result_criteria, _pending_sec_key ?? selected_security_key, selected_security_md);
|
||||
public void EndEdit() => _snapshot = null;
|
||||
public void CancelEdit() {
|
||||
if (_snapshot.HasValue) {
|
||||
var (criteria, sec, md) = _snapshot.Value;
|
||||
security_criteria_clauses.SetItems(criteria.clauses);
|
||||
selected_security = sec;
|
||||
selected_security_key = sec;
|
||||
/// A <see cref="_pending_sec"/> may have been used to create the snapshot when <see cref="BeginEdit"/> was called (this happens when securities are loaded lazily).
|
||||
/// If this is the case, pending sec was assigned to the <see cref="_snapshot"/> security;
|
||||
/// check if the security is now present in <see cref="all_securities"/>, if so, use that value instead of the null.
|
||||
selected_security_md = sec.HasValue && !md.HasValue && all_securities.TryGetValue(sec.Value.Id, out var md_match) ? md_match : md;
|
||||
selected_security_md = sec.HasValue && !md.HasValue && all_securities.TryGetValue(sec.Value, out var md_match) ? md_match : md;
|
||||
_snapshot = null;
|
||||
RaisePropertyChanged(nameof(selected_security));
|
||||
_add_criteria_command.RaiseCanExecuteChanged();
|
||||
_remove_criteria_command.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
@@ -16,8 +17,11 @@ namespace mbts.ui.controls {
|
||||
partial class ContractPickerPopup : PopupControl {
|
||||
public static readonly EventHandler
|
||||
FlushSelectedCriteriaHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedCriteriaProperty)?.UpdateSource(),
|
||||
FlushSelectedSecurityKeyHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedSecurityKeyProperty)?.UpdateSource(),
|
||||
FlushSelectedSecurityHandler = (s, e) => (s as ContractPickerPopup)?.GetBindingExpression(SelectedSecurityProperty)?.UpdateSource();
|
||||
|
||||
readonly PropertyUpdateGate _selection_sync_gate = new PropertyUpdateGate();
|
||||
|
||||
IReadOnlyDictionary<uint, SecurityMD> _cached_security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
ContractPickerPopupController _controller;
|
||||
bool _allow_empty, _is_lazy;
|
||||
@@ -97,12 +101,55 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedSecurityKey
|
||||
public static readonly DependencyProperty SelectedSecurityKeyProperty =
|
||||
WPFHelper.create_dp<uint?, ContractPickerPopup>(
|
||||
nameof(SelectedSecurityKey),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => {
|
||||
o.Controller.model.selected_security_key = v;
|
||||
o._selection_sync_gate.try_update(() => o._sync_selection_props(o.SelectedSecurityMD, o.SelectedSecurity, v, v));
|
||||
});
|
||||
//(o, base_value) => base_value is uint u && u != 0 ? u : (uint?)null);
|
||||
|
||||
public uint? SelectedSecurityKey {
|
||||
get => GetValue(SelectedSecurityKeyProperty) as uint?;
|
||||
set => SetValue(SelectedSecurityKeyProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
enum SecurityProp {
|
||||
Security,
|
||||
SecurityMD,
|
||||
ID
|
||||
}
|
||||
|
||||
void _sync_selection_props(SecurityMD? curr_sec_md, SecurityImmutable? curr_sec, uint? curr_sec_key, uint? target_sec_key) {
|
||||
if (curr_sec_key != target_sec_key) {
|
||||
SetCurrentValue(SelectedSecurityKeyProperty, target_sec_key);
|
||||
}
|
||||
bool sec_md_dirty = target_sec_key != (curr_sec_md?.security)?.Id;
|
||||
bool sec_dirty = target_sec_key != curr_sec?.Id;
|
||||
if (sec_md_dirty || sec_dirty) {
|
||||
curr_sec_md = target_sec_key.HasValue && _cached_security_map.TryGetValue(target_sec_key.Value, out var smd) ? smd : (SecurityMD?)null;
|
||||
if (sec_md_dirty)
|
||||
SetCurrentValue(SelectedSecurityMDProperty, curr_sec_md);
|
||||
if (sec_dirty)
|
||||
SetCurrentValue(SelectedSecurityProperty, curr_sec_md?.security);
|
||||
}
|
||||
if (!IsEditMode)
|
||||
SetCurrentValue(SelectedCriteriaProperty, target_sec_key.HasValue ? SecurityCriteria.from_keys(new[] { target_sec_key.Value }) : null);
|
||||
}
|
||||
|
||||
#region SelectedSecurity
|
||||
public static readonly DependencyProperty SelectedSecurityProperty =
|
||||
WPFHelper.create_dp<SecurityImmutable?, ContractPickerPopup>(
|
||||
nameof(SelectedSecurity),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Controller.model.selected_security = v);
|
||||
(o, v) => {
|
||||
o.Controller.model.selected_security = v;
|
||||
o._selection_sync_gate.try_update(() => o._sync_selection_props(o.SelectedSecurityMD, v, o.SelectedSecurityKey, v?.Id));
|
||||
});
|
||||
|
||||
public SecurityImmutable? SelectedSecurity {
|
||||
get => GetValue(SelectedCriteriaProperty) as SecurityImmutable?;
|
||||
@@ -115,7 +162,10 @@ namespace mbts.ui.controls {
|
||||
WPFHelper.create_dp<SecurityMD?, ContractPickerPopup>(
|
||||
nameof(SelectedSecurityMD),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => o.Controller.model.selected_security_md = v);
|
||||
(o, v) => {
|
||||
o.Controller.model.selected_security_md = v;
|
||||
o._selection_sync_gate.try_update(() => o._sync_selection_props(v, o.SelectedSecurity, o.SelectedSecurityKey, (v?.security)?.Id));
|
||||
});
|
||||
|
||||
public SecurityMD? SelectedSecurityMD {
|
||||
get => GetValue(SelectedCriteriaProperty) as SecurityMD?;
|
||||
@@ -248,12 +298,10 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
void on_cancel() {
|
||||
model.CancelEdit();
|
||||
propagate_to_owner();
|
||||
owner.CancelClick?.Invoke(owner, EventArgs.Empty);
|
||||
}
|
||||
void propagate_to_owner() {
|
||||
owner.set_current_if_changed(SelectedSecurityMDProperty, owner.Controller.model.selected_security_md);
|
||||
owner.set_current_if_changed(SelectedSecurityProperty, owner.Controller.model.selected_security);
|
||||
owner.set_current_if_changed(SelectedSecurityKeyProperty, owner.Controller.model.selected_security_key);
|
||||
owner.set_current_if_changed(SelectedCriteriaProperty, owner.Controller.model.result_criteria);
|
||||
}
|
||||
}
|
||||
@@ -262,4 +310,4 @@ namespace mbts.ui.controls {
|
||||
public SecurityCriteria selected_criteria => _selected_criteria.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui {
|
||||
class BindingProxy : FrameworkElement {
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(object), typeof(BindingProxy));
|
||||
|
||||
public object Value {
|
||||
get => GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,12 @@ namespace mbts.co2 {
|
||||
total_volume = _cold->total_volume;
|
||||
return (_cold->last_trade.load_if_new(ref time, out ps));
|
||||
}
|
||||
public bool get_last_trade(ref ulong time, out NativePriceSize ps, out DateTime timestamp, out ulong total_volume, out bool from_spread) {
|
||||
timestamp = _cold->last_trade_time;
|
||||
total_volume = _cold->total_volume;
|
||||
from_spread = (_cold->from_spread != 0);
|
||||
return (_cold->last_trade.load_if_new(ref time, out ps));
|
||||
}
|
||||
public ulong misc_timestamp { get { return _cold->misc_timestamp; } }
|
||||
|
||||
public ulong total_volume { get { return _cold->total_volume; } }
|
||||
@@ -259,7 +265,7 @@ namespace mbts.co2 {
|
||||
// Last trade
|
||||
public AtomicPriceSize last_trade;
|
||||
public ulong total_volume;
|
||||
public utils.LLDateTime last_trade_time;
|
||||
public utils.LLDateTime last_trade_time;
|
||||
|
||||
//// Settlement
|
||||
public UInt64 misc_timestamp;
|
||||
@@ -280,6 +286,7 @@ namespace mbts.co2 {
|
||||
public double session_low_offer;
|
||||
public double fixing_price;
|
||||
|
||||
public int from_spread;
|
||||
public int deleted;
|
||||
// FixedSecurity will not be directly addressable. Instead the security is available through the callback
|
||||
public Int64 security_size;
|
||||
|
||||
@@ -745,13 +745,19 @@ namespace bts.api.core {
|
||||
return new CompositeDisposable(source.Subscribe(aggregator.on_left), right.Subscribe(aggregator.on_right));
|
||||
});
|
||||
|
||||
public static IObservable<TResult> AggregateLatest<TLeft, TRight, TAggregate, TResult>(this IObservable<TLeft> source, IObservable<TRight> right,
|
||||
Func<TAggregate, Change<TLeft>, TRight, TAggregate> update_left, Func<TAggregate, TLeft, Change<TRight>, TAggregate> update_right, TAggregate seed,
|
||||
public static IObservable<TResult> AggregateLatest<TLeft, TRight, TAggregate, TResult>(
|
||||
this IObservable<TLeft> source,
|
||||
IObservable<TRight> right,
|
||||
Func<TAggregate, Change<TLeft>, TRight, TAggregate> update_left,
|
||||
Func<TAggregate, TLeft, Change<TRight>, TAggregate> update_right,
|
||||
Func<TAggregate> seed_factory,
|
||||
Func<TAggregate, TResult> result_selector,
|
||||
TLeft init_left = default, TRight init_right = default) =>
|
||||
TLeft init_left = default,
|
||||
TRight init_right = default) =>
|
||||
//
|
||||
Observable.Create<TResult>(o => {
|
||||
Log.DataFlow.diag($"Create AggregateLatest (Left: {typeof(TLeft)}, Right: {typeof(TRight)}).");
|
||||
var aggregator = new ConcurrentAggregator<TLeft, TRight, TAggregate>(update_left, update_right, agg => o.OnNext(result_selector(agg)), seed, init_left, init_right);
|
||||
var aggregator = new ConcurrentAggregator<TLeft, TRight, TAggregate>(update_left, update_right, agg => o.OnNext(result_selector(agg)), seed_factory(), init_left, init_right);
|
||||
return new CompositeDisposable(source.Subscribe(aggregator.on_left), right.Subscribe(aggregator.on_right));
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace bts.api.core {
|
||||
return left_source.AggregateLatest<TableUpdate<TLeft, TKey>, TableUpdate<TRight, TKey>, (TableUpdate<TResult, TKey> result, IReadOnlyDictionary<TKey, TLeft> left_state, IReadOnlyDictionary<TKey, TRight> right_state), TableUpdate<TResult, TKey>>(right_source,
|
||||
process_left,
|
||||
process_right,
|
||||
(TableUpdate<TResult, TKey>.mk_empty(result_key), new Dictionary<TKey, TLeft>(0), new Dictionary<TKey, TRight>(0)),
|
||||
() => (TableUpdate<TResult, TKey>.mk_empty(result_key), new Dictionary<TKey, TLeft>(0), new Dictionary<TKey, TRight>(0)),
|
||||
t => t.result,
|
||||
TableUpdate<TLeft, TKey>.mk_empty(left_key), TableUpdate<TRight, TKey>.mk_empty(right_key));
|
||||
// left
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace bts.api.core {
|
||||
right_source,
|
||||
process_left,
|
||||
process_right,
|
||||
(TableUpdate<TResult, TLeftKey>.mk_empty(result_key), new Dictionary<TRightKey, HashSet<TLeftKey>>()),
|
||||
() => (TableUpdate<TResult, TLeftKey>.mk_empty(result_key), new Dictionary<TRightKey, HashSet<TLeftKey>>()),
|
||||
state => state.result,
|
||||
TableUpdate<TLeft, TLeftKey>.mk_empty(left_key),
|
||||
TableUpdate<TRight, TRightKey>.mk_empty(right_key));
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace bts {
|
||||
struct UserTuple : IEquatable<UserTuple>, IComparable<UserTuple>, IComparable {
|
||||
public readonly struct UserTuple : IEquatable<UserTuple>, IComparable<UserTuple>, IComparable {
|
||||
const string _admin_name = "bts";
|
||||
|
||||
public static readonly Func<UserTuple, uint> key_fn = u => u.id;
|
||||
public static UserTuple admin => new UserTuple(0, _admin_name, Permissions.Admin);
|
||||
|
||||
public readonly uint id;
|
||||
public readonly string name;
|
||||
public readonly Permissions permissions;
|
||||
|
||||
public UserTuple(uint id, string name) {
|
||||
public UserTuple(uint id, string name)
|
||||
: this(id, name, name == _admin_name ? Permissions.Admin : Permissions.Regular) { }
|
||||
|
||||
public UserTuple(uint id, string name, Permissions permissions) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public enum Permissions {
|
||||
Regular,
|
||||
Admin
|
||||
}
|
||||
|
||||
public int CompareTo(UserTuple other) {
|
||||
@@ -28,4 +41,9 @@ namespace bts {
|
||||
public static bool operator ==(UserTuple a, UserTuple b) => a.Equals(b);
|
||||
public static bool operator !=(UserTuple a, UserTuple b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UserTupleExtensions {
|
||||
public static bool can_access_user_data(this UserTuple user, uint user_id) =>
|
||||
user.id == user_id || user.permissions == UserTuple.Permissions.Admin;
|
||||
}
|
||||
}
|
||||
@@ -13,84 +13,78 @@ namespace bts.md {
|
||||
enum MDSourceOptions {
|
||||
DeferInit = 1,
|
||||
}
|
||||
class MDSource : IObserver<List<SecurityAndHandle>>, IDisposable
|
||||
{
|
||||
|
||||
class MDSource : IObserver<List<SecurityAndHandle>>, IDisposable {
|
||||
const uint _no_user_id = 0;
|
||||
|
||||
protected readonly MDConsumer _consumer = new MDConsumer();
|
||||
protected readonly Co2RelayControl _relay;
|
||||
protected DateTime _version = DateTime.MinValue;
|
||||
protected readonly ConcurrentDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new ConcurrentDictionary<SecurityKey, SecurityMD>();
|
||||
protected List<IDisposable> _dispose = new List<IDisposable>();
|
||||
protected readonly MDSourceOptions _options;
|
||||
readonly uint _user_id;
|
||||
protected DateTime _version = DateTime.MinValue;
|
||||
protected List<IDisposable> _dispose = new List<IDisposable>();
|
||||
|
||||
public MDSource(uint naggle_ms, MDSourceOptions options = 0, uint user_id = _no_user_id)
|
||||
{
|
||||
readonly UserTuple _user;
|
||||
readonly BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>> _on_securities =
|
||||
new BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>>(new ConcurrentDictionary<SecurityKey, SecurityMD>());
|
||||
|
||||
public MDSource(uint naggle_ms, MDSourceOptions options = 0)
|
||||
: this(naggle_ms, options, UserTuple.admin) { }
|
||||
|
||||
public MDSource(uint naggle_ms, MDSourceOptions options, UserTuple user) {
|
||||
_options = options;
|
||||
_user_id = user_id;
|
||||
_user = user;
|
||||
_relay = new Co2RelayControl(_consumer, naggle_ms);
|
||||
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
|
||||
_dispose.Add(_consumer.on_security.Subscribe(this));
|
||||
if(!options.HasFlag(MDSourceOptions.DeferInit))
|
||||
if (!options.HasFlag(MDSourceOptions.DeferInit))
|
||||
initialize();
|
||||
}
|
||||
|
||||
readonly BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>> _on_securities =
|
||||
new BehaviorSubject<ConcurrentDictionary<SecurityKey, SecurityMD>>( new ConcurrentDictionary<SecurityKey, SecurityMD>() );
|
||||
public IObservable<ConcurrentDictionary<SecurityKey, SecurityMD>> on_securities => _on_securities;
|
||||
|
||||
virtual public void initialize()
|
||||
{
|
||||
public virtual void initialize() =>
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
public void set_token(AuthTokenImmutable token)
|
||||
{
|
||||
public void set_token(AuthTokenImmutable token) =>
|
||||
_relay.set_token(token);
|
||||
}
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) => Log.Md.error(error.ToString());
|
||||
|
||||
public void OnError(Exception error) =>
|
||||
Log.Md.error(error.ToString());
|
||||
|
||||
public void OnNext(List<SecurityAndHandle> values) {
|
||||
if (values.Count > 0) {
|
||||
foreach (var v in values) {
|
||||
if (_user_id != _no_user_id &&
|
||||
v.security.ContractCase == Security.ContractOneofCase.Synthetic &&
|
||||
v.security.Synthetic.UserId != _user_id &&
|
||||
v.security.Synthetic.UserId != _no_user_id) {
|
||||
continue;
|
||||
if (v.security.ContractCase != Security.ContractOneofCase.Synthetic || _user.can_access_user_data(v.security.Synthetic.UserId)) {
|
||||
var smd = new SecurityMD(new SecurityImmutable(v.security), v.handle);
|
||||
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
|
||||
}
|
||||
var smd = new SecurityMD(new SecurityImmutable(v.security), v.handle);
|
||||
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
|
||||
}
|
||||
_version = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(_dispose);
|
||||
async protected Task run()
|
||||
{
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(_dispose);
|
||||
|
||||
protected async Task run() {
|
||||
DateTime run_version = DateTime.MinValue;
|
||||
while (true)
|
||||
{
|
||||
while (true) {
|
||||
var exch = _exchange_sec_map;
|
||||
var current_version = _version;
|
||||
if (current_version > run_version)
|
||||
{
|
||||
if (current_version > run_version) {
|
||||
run_version = current_version;
|
||||
try
|
||||
{
|
||||
try {
|
||||
Log.Md.info($"MDSource publishing {exch.Count} securities");
|
||||
_on_securities.OnNext(exch);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
} catch (Exception e) {
|
||||
Log.Md.error($"Error in publish_securities: {e}");
|
||||
}
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using bts.api;
|
||||
using mbts.co2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts {
|
||||
struct SecurityMD : IEquatable<SecurityMD> {
|
||||
readonly struct SecurityMD : IEquatable<SecurityMD> {
|
||||
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
|
||||
|
||||
public SecurityImmutable security { get; }
|
||||
public MDConsumerHandle md { get; }
|
||||
|
||||
@@ -36,4 +34,4 @@ namespace mbts {
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,6 +278,9 @@ namespace System.Collections.Generic {
|
||||
public static IComparer<KeyValuePair<TKey, TValue>> key_comparer<TKey, TValue>() =>
|
||||
Impl<TKey, TValue>.key_comparer;
|
||||
|
||||
public static IReadOnlyDictionary<TKey, TValue> wrap_value<TKey, TValue>(TKey key, TValue value) =>
|
||||
new Impl<TKey, TValue>.ValueWrapper(key, value);
|
||||
|
||||
static class Impl<TKey, TValue> {
|
||||
public static readonly IReadOnlyDictionary<TKey, TValue> empty =
|
||||
new ReadOnlyDictionary<TKey, TValue>(new Dictionary<TKey, TValue>(0));
|
||||
@@ -297,6 +300,57 @@ namespace System.Collections.Generic {
|
||||
public int Compare(object x, object y) =>
|
||||
x is KeyValuePair<TKey, TValue> xkv ? (y is KeyValuePair<TKey, TValue> ykv ? Compare(xkv, ykv) : 1) : -1;
|
||||
}
|
||||
|
||||
public class ValueWrapper : IReadOnlyDictionary<TKey, TValue> {
|
||||
readonly TKey _key;
|
||||
readonly TValue _value;
|
||||
readonly IEqualityComparer<TKey> _key_comparer;
|
||||
|
||||
public ValueWrapper(TKey key, TValue value)
|
||||
: this(key, value, EqualityComparer<TKey>.Default) { }
|
||||
|
||||
public ValueWrapper(TKey key, TValue value, IEqualityComparer<TKey> key_comparer) {
|
||||
_key = key;
|
||||
_value = value;
|
||||
_key_comparer = key_comparer;
|
||||
}
|
||||
|
||||
public TValue this[TKey key] =>
|
||||
_key_comparer.Equals(key, _key) ? _value : throw new KeyNotFoundException();
|
||||
|
||||
public int Count => 1;
|
||||
|
||||
public IEnumerable<TKey> Keys {
|
||||
get {
|
||||
yield return _key;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TValue> Values {
|
||||
get {
|
||||
yield return _value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key) => _key_comparer.Equals(key, _key);
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
||||
yield return new KeyValuePair<TKey, TValue>(_key, _value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
yield return new KeyValuePair<TKey, TValue>(_key, _value);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value) {
|
||||
if (_key_comparer.Equals(key, _key)) {
|
||||
value = _value;
|
||||
return true;
|
||||
}
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,6 +343,18 @@ namespace System.Collections.Generic {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<T> ToListSorted<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> key_selector) =>
|
||||
enumerable.ToListSorted(key_selector, Comparer<TKey>.Default);
|
||||
|
||||
public static List<T> ToListSorted<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> key_selector, IComparer<TKey> key_comparer) =>
|
||||
enumerable.ToListSorted(key_comparer.wrap(key_selector));
|
||||
|
||||
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable, IComparer<T> comparer) {
|
||||
var list = new List<T>(enumerable);
|
||||
list.Sort(comparer);
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simply returns enumerable.ToList(), under the guise ofo IReadOnlyList<>
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace bts.collections {
|
||||
public static class ImmutableSetExtensions {
|
||||
public static IImmutableSet<T> RemoveRange<T>(this IImmutableSet<T> source, IEnumerable<T> values) {
|
||||
/// Prefer the <see cref="ImmutableHashSet{T}"/> implementation.
|
||||
/// It uses an <see cref="ImmutableHashSet{T}.Builder"/> to remove values, which is optimized for bulk changes (less memory allocations, etc.).
|
||||
if (source is ImmutableHashSet<T> ihs) {
|
||||
return ihs.RemoveRange(values);
|
||||
}
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (values == null) throw new ArgumentNullException(nameof(values));
|
||||
foreach (var v in values)
|
||||
source = source.Remove(v);
|
||||
return source;
|
||||
}
|
||||
|
||||
public static ImmutableHashSet<T> RemoveRange<T>(this ImmutableHashSet<T> source, IEnumerable<T> values) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (values == null) throw new ArgumentNullException(nameof(values));
|
||||
var bd = source.ToBuilder();
|
||||
foreach (var v in values)
|
||||
bd.Remove(v);
|
||||
return bd.ToImmutable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ using System.Diagnostics;
|
||||
namespace bts.test {
|
||||
public static class InProcessTestRunner {
|
||||
|
||||
public static void run_test_synchronously(string v, TestEnumerator enumerator, Action<TestResult> report) {
|
||||
foreach (var t in enumerator.find(v)) {
|
||||
run_test_synchronously(t, report);
|
||||
public static void run_test_synchronously(string v, TestType type, TestEnumerator enumerator, Action<TestResult> report) {
|
||||
foreach (var t in enumerator.find(v,type)) {
|
||||
run_test_synchronously(t, report);
|
||||
}
|
||||
}
|
||||
public static void run_test_synchronously(TestMethod test, Action<TestResult> report) {
|
||||
|
||||
@@ -36,18 +36,19 @@ namespace bts.test {
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<TestMethod> find(string name) {
|
||||
public IEnumerable<TestMethod> find(string name, TestType type) {
|
||||
foreach (var g in TestGroups) {
|
||||
foreach (var t in g.methods) {
|
||||
if (g.name == name || t.name == name || t.full_name == name) {
|
||||
if (g.test_type == type &&
|
||||
(g.name == name || t.name == name || t.full_name == name)) {
|
||||
yield return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TestMethod> match(IEnumerable<string> names) {
|
||||
return names.SelectMany(find).Distinct();
|
||||
public IEnumerable<TestMethod> match(TestType type, IEnumerable<string> names) {
|
||||
return names.SelectMany(name => find(name,type)).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 256 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user