Compare commits

...
282 Commits
Author SHA1 Message Date
Pete Fisher e974edfc6e Merge branch 'master' of bitbucket.org:bluetradesys/spark 2021-03-18 16:22:27 -04:00
Pete Fisher 3b7740cee0 updating submodule to latest 2021-03-18 16:22:12 -04:00
John Colvin 258d4a3fc1 import compute_synthetic_book method from spartan 2020-12-08 23:00:18 -06:00
John Colvin c53f6425cd import IntPrice arithmetic operators from spartan
The compute_synthetic_book method (also to be imported
from spartan) depends on a couple of them.
2020-12-08 22:52:28 -06:00
Pedro Pinto e5d77593d1 Changed logging 2020-10-15 14:24:02 -04:00
Pedro Pinto 6580f3883b Added logging of cancel/replace 2020-10-15 14:18:57 -04:00
Pete Fisher a66eae66bb fixing for python3 2020-10-14 21:56:49 -04:00
Pedro Pinto 090a9dfb17 Added iLink3 documentation. 2020-09-14 12:02:37 -04:00
Pete Fisher 27aa43f44d fixed time on ilink2 audit 2020-09-09 12:16:57 -04:00
Pedro Pinto 19e087aa69 Fixed time reporting in iLink3. 2020-09-09 10:58:34 -04:00
Pedro Pinto 167c8c23a0 Fixed batching of orders under iLink3. 2020-08-27 08:19:47 -04:00
Pete Fisher d235f4be41 fixed jetfix.conf ports on relay 2020-08-25 10:03:09 -04:00
Pedro Pinto ec43dbd82d Maybe fixed the self match prevention problem. 2020-08-24 17:02:58 -04:00
Pedro Pinto f07998d6a6 Fixed order rounding error in ilink3 2020-08-24 16:17:31 -04:00
Pedro Pinto b3dc2cdcd4 Changed logging subsystem from ilink3 to iLink3 2020-08-24 11:06:44 -04:00
Pete Fisher 0963aee137 added audit log scripts to be run by the docker container 2020-08-19 17:49:47 -04:00
Pedro Pinto c2f8e92b10 Hopefully fixed the ilink3 filled qty reporting problem.
This was crashing the spreader.
2020-08-13 08:42:43 -04:00
Pedro Pinto 7ae0142dff Fixed reset of sequence numbers problem 2020-08-12 11:56:08 -04:00
Pedro Pinto db0ffba8ed Fixed windows build 2020-07-10 13:00:20 -04:00
Pedro Pinto a274a214b4 Merged with master 2020-07-10 11:31:47 -04:00
Pedro Pinto bbcfa3c75a merged with prod 2020-07-10 10:45:10 -04:00
Pedro Pinto 89229d65f1 Merge branch 'master' of bitbucket.org:bluetradesys/spark 2020-07-10 10:43:34 -04:00
Pedro Pinto 0e75cde409 Fixed a bad asser in recovery, a few typos, haderned against bad securities. 2020-07-10 10:36:03 -04:00
Pedro Pinto 42fa6dfaff Fixed life cycle of exe reports and config during replay. 2020-07-09 16:06:39 -04:00
Pedro Pinto 400143c84d Protected against ticker subs for expired securities. 2020-07-09 14:45:51 -04:00
Pedro Pinto e915945880 Trying to replay fix logs. 2020-07-09 14:27:22 -04:00
Taha Afzal ca6673e98f Merge branch 'prod' 2020-07-09 13:09:57 -04:00
Taha Afzal 479f3dccff Adding OnSignal (this is a new algo which is hidden by default) 2020-07-09 13:07:28 -04:00
Pedro Pinto 9601ebab20 Adding replay of stored messages. 2020-07-09 10:16:31 -04:00
Taha Afzal c272d8a8c2 Pyramid Algo: Takes care of the rounding error when Top == sum (child orders) 2020-07-08 13:35:21 -04:00
Pedro Pinto b344ab3377 Fixed an issue with order recovery from Exec reports. 2020-07-08 12:07:48 -04:00
Pedro Pinto ae3080b031 Refactoring. 2020-07-08 10:10:14 -04:00
Pedro Pinto f0464331cb Added better handling of cancel rejects. 2020-07-08 09:52:18 -04:00
Pedro Pinto 0648b618bf Refactored. 2020-07-08 08:42:53 -04:00
Pedro Pinto 59282b8c70 Added one more test to manual order entry list. 2020-07-08 08:42:18 -04:00
Pedro Pinto 34a86009ce Doc and tool minor update. 2020-07-08 07:44:01 -04:00
Pedro Pinto 0ad9ae9001 Small tweak to logging tool. 2020-07-07 20:19:59 -04:00
Pedro Pinto a296e1e772 Fixed parsing of stop px in link3. 2020-07-07 20:19:41 -04:00
Pedro Pinto 7f53d8b161 Improved logging, fix iceberg order, removed duplicate logging. 2020-07-07 17:01:24 -04:00
John Colvin fbd8bbd4a8 algo abbreviation code uses string.Join instead of list_string (the latter method is redundant, should be removed) 2020-07-07 13:16:18 -05:00
John Colvin 3da52b350f fix pure crash (algo abbreviation code choked on names that end with a space) 2020-07-07 13:15:55 -05:00
Pedro Pinto 243d6827d4 Merge remote-tracking branch 'refs/remotes/origin/ilink3_final' into ilink3_final 2020-07-07 12:12:09 -04:00
Pedro Pinto 00221f9bd9 Fixed spread order entry. 2020-07-07 12:11:11 -04:00
Pedro Pinto a6171ae405 Fixed spread order entry. 2020-07-07 11:55:05 -04:00
Pedro Pinto be5d01a797 Fixed fills. 2020-07-07 06:47:58 -04:00
Pedro Pinto e5d9d324bf Order order entry manual test. 2020-07-06 18:22:39 -04:00
Pedro Pinto c34dee9d96 Taked order status from execution report, parse error message in il3 2020-07-06 18:21:44 -04:00
Pedro Pinto e5a98aab41 Merged with master 2020-07-06 12:49:24 -04:00
Pedro Pinto 10cd35372b Can enter, modify, cancel orders. 2020-07-06 11:38:17 -04:00
Pedro Pinto ad4515be39 Renamed NativeOrder to GLBXNativeOrder 2020-07-06 09:47:34 -04:00
Pedro Pinto ff4d87dd6b Fixed IL3 execution report parser to convert exchange prices. 2020-07-06 09:34:26 -04:00
Pedro Pinto 8ddde3f9da Part 2 of MBO service fix. 2020-07-06 09:34:09 -04:00
Pedro Pinto 51093dc3a5 Added simple tool to watch spark logs. 2020-07-06 09:33:35 -04:00
Pedro Pinto 5d5db9ad9f Fixed MBO parser to extract the right exchange key. 2020-07-06 09:33:06 -04:00
Pedro Pinto fca808fe0d Made ilink3 gw wait for start() before its starts. 2020-07-03 14:11:22 -04:00
Pedro Pinto 07d14f877b More nonsense 2020-07-02 20:28:49 -04:00
Pedro Pinto 5ea6dccdd4 Something here breaks 2020-07-02 19:04:49 -04:00
Pedro Pinto ac956a5c02 Renamed ExchangeOrderIDu64 to ExchangeOrderID64 2020-07-02 19:00:32 -04:00
Pedro Pinto 1fba09cf94 Refactor of ids. 2020-07-02 11:05:04 -04:00
Pedro Pinto 0baa6b875e Fixed merge error. 2020-07-02 09:09:20 -04:00
Pedro Pinto cdacc2d4d4 Merged 2020-07-02 07:26:43 -04:00
Pedro Pinto 1433541947 Automatically advanced our own seq number on connection failure.
It is fairly easy for our sequence number to be behind CME if we
crash. Since we can detect this issue (CME tells us about it) we can
try to fix it automatically by incrementing our own sequence
number. This should allow us to recover automatically in some
scenarios.
2020-07-02 06:57:01 -04:00
Pedro Pinto 1070ca7ace Ran clang-format. 2020-07-02 06:28:32 -04:00
Pedro Pinto 56fa27dd7e Handled non text timestamp in native order. 2020-07-02 06:23:07 -04:00
Pedro Pinto d87b522bc6 Added better logging when terminated by exchange. 2020-07-02 06:22:38 -04:00
Eric Harding 166cb03782 Provide account when parsing execution reports. 2020-07-01 14:40:10 -04:00
John Colvin 40bad84c48 algo studio includes algos with undefined children in the dropdown 2020-07-01 12:09:12 -05:00
Eric Harding c3786d9d64 ilink3: Provide SecurityOracle to execution report parser 2020-07-01 11:50:03 -04:00
Pedro Pinto 9080e3124b Filled in cust_order_handing_inst 2020-07-01 11:29:47 -04:00
Pedro Pinto d9c536124a Fixed logging of session seq numbers. 2020-07-01 11:29:30 -04:00
Pedro Pinto 7de6b2907e Filled in sendercompid in routing record.
iLink3 requires this.
2020-07-01 10:48:01 -04:00
Eric Harding 75f017e9f8 ilink3: Get PartyDetails from sender_cmp_id 2020-07-01 10:29:20 -04:00
Pedro Pinto 7757ab3ea0 Fixed a few bugs. 2020-06-30 19:35:53 -04:00
Pedro Pinto 3186dd383c We can connect now. 2020-06-30 19:03:35 -04:00
Pedro Pinto 45f751c9c6 Bad merge. 2020-06-30 19:03:35 -04:00
Pedro Pinto e4d3774980 Better logging. 2020-06-30 19:03:35 -04:00
Eric Harding eedb3ce05d Clear hmac for json logging 2020-06-30 17:11:01 -04:00
Eric Harding 6a7d38c0fd flip session bool 2020-06-30 16:10:26 -04:00
Eric Harding 7f9967834d add readme note 2020-06-30 15:34:03 -04:00
Eric Harding 31ccbfcd19 Switch to ilink3 logging group 2020-06-30 14:43:38 -04:00
Eric Harding c2abf90cc4 IL3MessageFramer owns outgoing messages so it needs to outlive ILinkGW 2020-06-30 14:36:02 -04:00
Eric Harding 8dc4735248 ilink3: set_state(Error) only after disconnect 2020-06-30 14:25:59 -04:00
Pedro Pinto d940d9f11d For now, caught serialization error in some ilink3 msgs. 2020-06-30 14:10:07 -04:00
Pedro Pinto 1e0c75c6c7 Added iLink3 subsystem for logging. 2020-06-30 14:09:55 -04:00
Eric Harding fbaa17bc40 Add some comments to marketdata.proto 2020-06-30 13:16:30 -04:00
Eric Harding 38e3cd6197 Update market data readme 2020-06-30 13:13:48 -04:00
Pedro Pinto 3bb9961af4 Refactor. 2020-06-30 11:35:40 -04:00
Pedro Pinto da8088fd8d Rebased 2020-06-30 11:27:56 -04:00
Eric Harding 748a65f671 Merge branch 'ilink3_gateway' of bitbucket.org:bluetradesys/spark into ilink3_gateway 2020-06-30 10:05:33 -04:00
Eric Harding 66d80136bd ilink3: Keep outgoing messages alive forever. 2020-06-30 10:04:42 -04:00
Eric Harding 10386311d4 Update market data readme 2020-06-29 12:25:30 -04:00
Eric Harding 629a7f8130 Remove unused class in MD 2020-06-29 12:25:14 -04:00
Pedro Pinto 81189bb2cc Forgot to add message logger. 2020-06-29 12:08:22 -04:00
Pedro Pinto e5681a50f2 Made session name come from date. 2020-06-29 10:59:15 -04:00
Pedro Pinto 69fe4569e3 Made FIX session name available as uint64_t as well as strings. 2020-06-29 10:57:40 -04:00
Pedro Pinto c7caf66f9c Changed the ILink3 log format to json. 2020-06-26 19:11:05 -04:00
Pedro Pinto 14504f4a94 Cleaned up comment. 2020-06-26 19:10:49 -04:00
Pedro Pinto 5b601a4016 Fixed encofing of clorid. 2020-06-26 19:10:23 -04:00
John Colvin e3207b921a truncate list of securities in transitive compare test (was timing out due to too many permutations) 2020-06-26 13:32:27 -05:00
Taha Afzal 98db8e61a5 Removing hardcoded ref's to Katallage 2020-06-25 17:05:10 -04:00
Pedro Pinto 45aa75e8bb Brought back the ilink3/ilink2 encoding abstraction. 2020-06-25 16:49:46 -04:00
Pedro Pinto b2471d45b2 Added tool 2020-06-25 16:49:10 -04:00
Pedro Pinto 11e2df8c76 Tweaked build tools. 2020-06-25 16:49:10 -04:00
Pedro Pinto cba6852655 Updated docker 2020-06-25 16:49:10 -04:00
Pedro Pinto 5eeaedde26 Added a few build tools. 2020-06-25 16:49:10 -04:00
Pedro Pinto b63052dbaa Updated ICE Onixs 2020-06-25 16:49:10 -04:00
Pedro Pinto 29f5bc798e Added +x 2020-06-25 16:49:10 -04:00
Pedro Pinto 04603acbb9 Added publishing of new routing info when orders are recorevered from disk 2020-06-25 16:49:10 -04:00
Pedro Pinto bc6890055e temp commit, fixing glbxnative order 2020-06-25 16:49:10 -04:00
Pedro Pinto ea94c09c78 Eric intermediate ilink3 fixes 2020-06-25 16:49:10 -04:00
Eric Harding e3c3bc78fc Note what this address list is for 2020-06-24 20:55:20 -04:00
Eric Harding a11d390dcd ilink3 message logging 2020-06-24 20:52:21 -04:00
Eric Harding 7c74a961d4 Add canned cert sessions for ilink3 nrcert 2020-06-24 20:43:27 -04:00
Eric Harding 5db21ad2ca fix build, session and clordid changes 2020-06-24 19:32:12 -04:00
Eric Harding f703dabdfe Raw msg should be 2 strings 2020-06-24 17:53:30 -04:00
Eric Harding 237d3eea4b encode_clordid needs security key 2020-06-24 17:29:06 -04:00
Taha Afzal e009316bfb Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-06-24 17:13:38 -04:00
Taha Afzal 5528efd2d2 Cleaning up 2020-06-24 17:13:31 -04:00
Eric Harding 818bdec130 Add NrCert keys 2020-06-24 15:07:02 -04:00
John Colvin 09f91735fe Security sort tests run with ice securities 2020-06-24 12:58:29 -05:00
John Colvin d9fc18547b SecurityHelper.Compare fallsback on 'ToString' then 'Id' comparisons to guarentee deterministic sort 2020-06-24 12:56:29 -05:00
Eric Harding d257cf2c77 Disable broken ilink3 tests for now 2020-06-24 13:28:33 -04:00
Eric Harding 079f46404f Add some testing notes 2020-06-24 10:57:15 -04:00
Eric Harding edcc73e934 Remove old ILink3ExecutionGateway. Instead use GlbxIlink3ExecutionGateway 2020-06-23 22:41:33 -04:00
Taha Afzal 5905ca3e92 Merging frmo master 2020-06-23 13:51:14 -04:00
Pedro Pinto bc7ac8523b Added tool 2020-06-23 13:06:15 -04:00
Eric Harding 0c4c2b7231 cme autocert msgw_failover test script 2020-06-23 13:05:32 -04:00
Eric Harding 420351c093 Replay refactor #4 2020-06-23 12:01:52 -04:00
Eric Harding f69e6b89c5 Sequence number fixes 2020-06-22 22:23:13 -04:00
Eric Harding c3e22e4b23 Recovery revamp #3 2020-06-22 20:16:21 -04:00
John Colvin 32b7b7f40d quantity conversion for ladder right click order 2020-06-22 17:02:23 -05:00
Pedro Pinto 54e5a68730 Tweaked build tools. 2020-06-22 08:41:19 -04:00
Pedro Pinto 880c95d92b Tweaked build tools. 2020-06-20 17:17:22 -04:00
John Colvin e7da6dd79e remove unused 'save/remove_template_cmd' props from OrderPropertiesEditor 2020-06-19 16:42:38 -05:00
John Colvin 8bf78aa880 cleanup/normalize whitespace in OrderDataHelper.cs 2020-06-19 16:37:21 -05:00
John Colvin 1c44165181 remove unused methods/props in OrderDataHelper.cs 2020-06-19 16:35:15 -05:00
John Colvin 8165ec0e3d fix AlgoProvider.get_permitted_algos (seed intersection aggregate a full set instead of empty) 2020-06-19 16:14:58 -05:00
John Colvin 7d1ec5b82a Merge remote-tracking branch 'refs/remotes/origin/DJ_ICE' into DJ_ICE 2020-06-19 15:48:55 -05:00
John Colvin 02e7fb1ee0 iceberg 'show quantity' param uses display divisor 2020-06-19 15:45:17 -05:00
John Colvin c0ea6943e1 AlgoProvider ensures synthetic security algo's last child is a Spreader algo 2020-06-19 15:12:42 -05:00
John Colvin 4c914a2d48 Merge branch 'DJ_ICE_algo_config' into DJ_ICE 2020-06-19 10:59:10 -05:00
Taha Afzal 0ee86f2dd4 CPP override fix 2020-06-19 11:58:58 -04:00
Taha Afzal 00bf3c7f47 Fixing the PNL timestamp 2020-06-19 11:10:23 -04:00
Taha Afzal b61e18d10e CPP logic 2020-06-19 00:44:13 -04:00
John Colvin 90b2607f86 pnl window symbol filter items are sorted 2020-06-18 13:06:41 -05:00
John Colvin e452697088 remove struct contraint on TKey in KeyedMultiSelector, and TableUpdateItemsFilter 2020-06-18 12:50:23 -05:00
John Colvin 1988443497 fix pnl symbol filter (was checking the security.Symbol instead of the 'pnl_symbol') 2020-06-18 12:40:07 -05:00
John Colvin 3f33627f00 add pnl_cpp_tuple extension method bones (taha will implement) 2020-06-18 12:14:44 -05:00
John Colvin 0610612ae6 make PNLData.with a member method instead of extension so it isn't
passed by value (it's a large struct)
2020-06-18 11:35:00 -05:00
John Colvin efcf78bb95 pnl window restores fill/market price decimal format when workspace loaded 2020-06-18 11:28:17 -05:00
John Colvin 03678e79a5 PNLSettings.cs line endings (need to figure out why they keep changing) 2020-06-18 11:00:32 -05:00
Taha Afzal c15a364496 Merge branch 'DJ_ICE_pnl_decimals' into DJ_ICE 2020-06-17 23:50:17 -04:00
Taha Afzal d8ec713dc5 Spreads in PNL show under a different symbol 2020-06-17 23:22:33 -04:00
Taha Afzal daaa7311a0 Merge branch 'DJ_ICE_pnl_symbol' into DJ_ICE_cpp_mult 2020-06-17 22:58:10 -04:00
Taha Afzal f893af1930 Strip Legs are not counted toward futures 2020-06-17 22:57:08 -04:00
Taha Afzal 6ec413c516 Dictinction bw spread legs and strip legs 2020-06-17 15:50:06 -04:00
John Colvin a2a0d88a81 pnl window disposes filters of type IDisposable 2020-06-17 12:39:59 -05:00
John Colvin 7a4dca9589 PNLWindow uses special pnl_symbol (taha will implement pnl_symbol method later, returns regular symbol in meantime) 2020-06-17 12:30:01 -05:00
John Colvin b21cd12cea copy AppendTable extension from another branch 2020-06-17 12:24:58 -05:00
John Colvin ec0b726751 ladder mid tick rounds down for negative numbers (still rounds up for positive) 2020-06-16 16:40:28 -05:00
John Colvin a9f2b4ad9a PNL window - setting for market/fill price column decimal places 2020-06-16 15:32:51 -05:00
Taha Afzal 9cb3a21e5a position service exposes both cpp and overridden cpp 2020-06-15 23:05:53 -04:00
Eric Harding 3231ffb343 Meaningless autocert code 2020-06-15 22:48:28 -04:00
Eric Harding 8d7f1738cf update cert secure logon 2020-06-15 22:47:30 -04:00
John Colvin 60d4dd87fd rename ISecurityAlgoProvider.cs -> IAlgoProvider.cs 2020-06-15 17:01:02 -05:00
John Colvin 3a7eeda70e AlgoProvider.subscribe_algos always fires at least once (doesn't depend on studio algos being present) 2020-06-15 17:00:13 -05:00
John Colvin 712146e028 Merge branch 'algo-type-config' into DJ_ICE_algo_config 2020-06-15 16:57:07 -05:00
Eric Harding 9101214e66 Add message logger and revamp replay sequence handling 2020-06-15 14:51:41 -04:00
Taha Afzal f381d536d6 Merge branch 'DJ_ICE' into DJ_ICE_cpp_mult 2020-06-15 09:53:32 -04:00
Pedro Pinto d8d5f3b93c Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-06-15 09:29:08 -04:00
Pedro Pinto 1d4dc47a62 Added missing update to session/high low. 2020-06-15 09:28:23 -04:00
Taha Afzal 482c300b72 Merge branch 'DJ_ICE_space_bar_recenter' into DJ_ICE 2020-06-14 21:08:51 -04:00
Pedro Pinto 59774ccbb5 Updated docker 2020-06-14 15:27:18 -04:00
Pedro Pinto fcb6c9b8d9 Added a few build tools. 2020-06-14 15:27:01 -04:00
Pedro Pinto 69ad995ba9 Updated docker 2020-06-14 15:23:18 -04:00
Pedro Pinto 0b26ec1132 Added a few build tools. 2020-06-14 15:21:55 -04:00
John Colvin 3de494c80f recenter all ladders on spacebar (don't require shift) 2020-06-12 17:05:53 -05:00
Eric Harding c6b2e2f21e OMF setting to true, always 2020-06-12 17:49:37 -04:00
Eric Harding 2eac97c1a9 meaningless cert change 2020-06-12 15:18:20 -04:00
Eric Harding b4b0e10f29 Fix IOC=FAK order type (not FOK) 2020-06-12 15:17:38 -04:00
John Colvin 7deb25dd1c fix ladder recenter on spacebar 2020-06-12 13:16:57 -05:00
Pedro Pinto 7a29ae1a83 Merge branch 'DJ_ICE' of bitbucket.org:bluetradesys/spark into DJ_ICE 2020-06-12 08:44:36 -04:00
Pedro Pinto 6da893128c Cleaned up logging. 2020-06-12 08:44:20 -04:00
Pedro Pinto 2cb458af2d Added OnBehalfofcompid tag to ICE as per cert. 2020-06-12 08:42:40 -04:00
John Colvin a144b03361 ladder recenter on spacebar (across thread barrier) 2020-06-11 18:28:37 -05:00
John Colvin 9c89a9a47e TabDocument forwards key press events to IShellWindows 2020-06-11 18:24:26 -05:00
John Colvin 7cfe8f4214 add override cpp context menu item in pnl window 2020-06-11 16:33:28 -05:00
John Colvin 24f8e66953 add IPositionModel (has function to override cpp) 2020-06-11 16:30:35 -05:00
John Colvin 90fc6d901f add PropertyEditor control/dialog 2020-06-11 16:29:45 -05:00
Eric Harding 38d9db4a90 Session and sequence number fixes 2020-06-11 11:36:37 -04:00
Pedro Pinto f8395c11b5 Fixed application, vendor tags for DJ. 2020-06-11 08:26:26 -04:00
Eric Harding 6a679ff78c Fix for cancel/replace 2020-06-10 14:13:28 -04:00
Eric Harding aba3f21020 stop order does not carry price 2020-06-09 19:18:36 -04:00
Eric Harding fede19fd11 stop-limit success 2020-06-09 16:30:00 -04:00
Eric Harding 3fa8dbf112 Cannot send price with market order 2020-06-08 23:09:10 -04:00
Eric Harding 77ae4703b3 Fix clOrdID and custOrdCapacity 2020-06-08 22:58:53 -04:00
Eric Harding a28bfa1175 execution report handling tweaks 2020-06-04 10:05:44 -04:00
Eric Harding 4e4a48283d bool parse_report(string&, SecurityOracle&, ExecutionReport*); 2020-06-03 13:09:56 -04:00
Pedro Pinto 7a761d00ce Added security oracle to SessionStore so we can resolve exe reports. 2020-06-03 08:51:31 -04:00
Pedro Pinto 968be6a99a Added storage for sessions. Still missing parsing of exec reports. 2020-06-02 17:13:53 -04:00
Pedro Pinto 03404eb3f1 Made some progress in GLBXiLink3ExecutionGateway 2020-06-02 09:16:02 -04:00
Pedro Pinto ff1a970982 Fixed build some more 2020-05-28 14:29:45 -04:00
Pedro Pinto ed890b4d84 Fixed broken tests for subscribe_exe_report change 2020-05-28 13:50:04 -04:00
Pedro Pinto e87003cc41 Merged with master 2020-05-28 12:21:51 -04:00
Eric Harding bc06037843 Fix the build 2020-05-28 12:20:30 -04:00
Pedro Pinto c4c7cebfc5 Updated externals 2020-05-28 12:12:46 -04:00
Pedro Pinto f18bcd2995 Build tweaks 2020-05-28 12:12:46 -04:00
Pedro Pinto a6a1b6ac53 Fixed clang build. 2020-05-28 12:12:46 -04:00
Eric Harding 8c3da4e43c Re-arrange some methods in ILinkGW.h 2020-05-27 16:51:36 -04:00
Eric Harding 06b955dceb Exchange seq number fix 2020-05-27 11:49:08 -04:00
Eric Harding e75d15146c cme cert test update 2020-05-26 23:55:27 -04:00
Eric Harding cb07384c9f Fix problem with party details 2020-05-26 23:55:13 -04:00
Eric Harding fc9e502539 cert refactor 2020-05-26 12:40:16 -04:00
Eric Harding fcbf3388c0 update ilinkbinaryxml templates 2020-05-26 12:40:04 -04:00
Eric Harding 899bea940f improve ilinkgw logging 2020-05-26 12:39:55 -04:00
Eric Harding 14b7509607 ensure local launch timezone 2020-05-26 12:39:41 -04:00
Eric Harding 25668e8df0 dont' build web client on wsl2 2020-05-26 11:54:19 -04:00
Eric Harding 5345ba8ff8 securities for sending orders ilink3 test 2020-05-19 15:58:56 -05:00
Eric Harding 9bf05973bd Add in/out to logs 2020-05-14 20:51:15 -05:00
Eric Harding cea4d05bc4 log every msg 2020-05-13 20:19:48 -05:00
Eric Harding 0b7208784b Can't take the double negatives anymore 2020-05-13 14:01:16 -05:00
Eric Harding 059ccbdab6 Merge branch 'master' of bitbucket.org:bluetradesys/spark into ilink3_gateway 2020-05-13 11:05:31 -05:00
Eric Harding 75eaf8c3d4 IL3ExecutionReportParser 2020-05-12 13:54:28 -05:00
Eric Harding 1e8fcd794f Update ExecutionReport::exchange_order_id 2020-05-06 12:22:33 -05:00
Eric Harding 726329c4bf Add ilink3 execution report parser and order test 2020-05-06 11:56:52 -05:00
Eric Harding 2d01f3f0f4 CME Certification requires very specific UUID generation 2020-05-06 11:56:01 -05:00
Eric Harding 2a595c0fd7 cme cert hard coding 2020-05-06 11:54:32 -05:00
Eric Harding 5f9866c9e8 SecurityCache now hands out raw pointers. SecurityOracle now indexes exchange security key 2020-05-05 11:17:16 -05:00
Eric Harding 78f1cf88e0 cmake needs love too 2020-05-04 11:16:21 -05:00
Eric Harding 4877436877 Ilink3 order unit test 2020-05-04 11:32:30 -04:00
Eric Harding 1b43124756 Add ReentrancyGuard.h 2020-05-02 13:10:49 -04:00
Eric Harding a1feb3fddf PrimaryGatewayConnectionEnforcement 2020-05-01 13:22:35 -04:00
Eric Harding 024328bf8b Refactor empty session 2020-05-01 11:44:03 -04:00
Eric Harding 80d7e34bf1 Fixes for updated sbe codegen 2020-04-28 18:16:45 -04:00
Eric Harding 8ab69c919b Improve cme provided error message 2020-04-27 16:56:32 -04:00
Eric Harding 99531bcfa0 Update ilink3 generated templates 2020-04-27 16:50:54 -04:00
Eric Harding 6bd516d4f2 cleanup 2020-04-27 11:26:42 -04:00
Eric Harding 1a1d771e1b addition ilink3 tests 2020-04-25 13:18:58 -04:00
Eric Harding 050fcf7c6e Passing simple log in test 2020-04-24 12:31:30 -04:00
Eric Harding aadeae3201 update cmake, avoid web build on wsl 2020-04-17 22:38:53 -04:00
Eric Harding b72e8aed02 ilink3 test code 2020-04-17 20:30:11 -05:00
Eric Harding 0820cc3e9a Fix a very subtle bug in serializer 2020-04-17 13:15:12 -05:00
Eric Harding b1ba9a1315 update ilink code gen readme 2020-04-16 14:03:53 -05:00
Eric Harding 39ce4323a1 Add CME cert command line tool 2020-04-13 16:18:59 -05:00
Eric Harding d9fcd290d9 source files don't need to be executable 2020-04-13 09:53:28 -05:00
Eric Harding 05939b02c5 save 2020-04-12 15:11:46 -05:00
Eric Harding aa91638fe4 ilink3 receive buffer handler + tests 2020-04-10 10:55:53 -05:00
Eric Harding 065fc54f73 IL3Reactor 2020-04-07 14:05:31 -05:00
Eric Harding 95fb13f58b Fix some build issues 2020-04-01 15:21:04 -05:00
Eric Harding 4eb2931cb8 Additional ilink3 refactoring 2020-03-28 21:08:47 -05:00
Eric Harding 5326015a48 Testcase for framer 2020-03-27 13:46:29 -05:00
Eric Harding fcbe319470 Split out functional pieces for easier testing 2020-03-27 09:14:30 -05:00
Eric Harding 5ad21c88b2 Add tests and refactor includes for faster build 2020-03-25 21:15:19 -05:00
Eric Harding 3a9c1d557b Split Retransmit of old vs new session logic 2020-03-09 16:29:15 -05:00
Eric Harding 6fb5b83249 Only send Negotiate if the session is new 2020-02-28 15:33:27 -06:00
Eric Harding 95830fafdf send_cancel 2020-02-27 14:48:56 -06:00
Eric Harding d68b03bb91 send_cancel_replace 2020-02-26 17:56:50 -06:00
Eric Harding e7062ce01e send_order call for link3 2020-02-26 17:36:18 -06:00
Pedro Pinto 932738f98e Merge branch 'master' into ilink3_gateway 2020-02-20 08:39:47 -05:00
Pedro Pinto 1426b00e83 Added fake iLink3 gateway 2020-02-19 16:21:06 -05:00
Pedro Pinto e724409b58 Added jetfix support for string_view 2020-02-19 10:55:56 -05:00
Pedro Pinto 9cf828e9a4 Implemented iLink3 order 2020-02-18 15:37:06 -05:00
Eric Harding 41408fb609 ILink3ExecutionGateway placeholder 2020-02-14 16:07:44 -06:00
Eric Harding 4c21e32838 ILink3GW core gateway 2020-02-14 16:06:58 -06:00
Eric Harding 385f3ea402 add unix_nano_now() to datetime.h 2020-02-14 16:05:00 -06:00
Eric Harding 8d6d8cab18 Update SecureLogon for ilink3 2020-02-14 16:05:00 -06:00
Eric Harding 01615f824a Generated ilink3 messages 2020-02-14 16:05:00 -06:00
Pedro Pinto c6779ddc90 Reactore GLBX native order for iLink3. 2020-02-13 15:26:33 -05:00
Pedro Pinto 46eff65435 Changed IExecutionGateway to support non FIX gateways. 2020-02-12 10:11:53 -05:00
John Colvin 17b314ea3a Merge branch 'master' into algo-type-config 2020-02-07 13:35:50 -06:00
John Colvin e713210030 remove obsolete algo types from hardcoded defaults 2019-10-29 18:07:32 -05:00
John Colvin c057bf18f6 Merge remote-tracking branch 'origin/master' into algo-type-config 2019-10-29 18:07:01 -05:00
John Colvin 17a0bef824 Merge remote-tracking branch 'origin/master' into algo-type-config 2019-10-21 12:15:00 -05:00
John Colvin 9180c48b67 CompositeAlgoVM 'group' is AlgoDisplayGroup.Studio 2019-10-14 15:29:02 -05:00
John Colvin 01acbe08f5 remove dead code 2019-10-14 15:26:01 -05:00
John Colvin 8a47cd4821 exchange/security based algo type config 2019-10-14 15:19:36 -05:00
John Colvin 03050d9b07 refactor some Disposable.replace calls so that the prev hook is disposed before the new one is created 2019-10-14 13:25:21 -05:00
John Colvin 9178878880 AlgoStudio model does not really need to import AdminModel (just UserTuple) 2019-10-14 13:25:21 -05:00
John Colvin f38b1c7af9 ContractPickerModel.result getter performs dictionary lookup (was brute force enumeration) 2019-10-14 13:25:21 -05:00
John Colvin 45cd958b1e make security criteria clause immutable 2019-10-14 13:25:21 -05:00
John Colvin 4d49c8f030 add .rej to gitignore 2019-10-14 13:25:21 -05:00
John Colvin b2227ce37b DynamicOrderHelper stores dyn types in map 2019-10-14 13:25:21 -05:00
344 changed files with 155083 additions and 3051 deletions

No files matched your search

+2
View File
@@ -34,5 +34,7 @@ trace_data/
/doc/user/public/*
.ionide/*
/.ignore
*.rej
/build
.clangd/
*CMakeDeps.cmake
+9 -5
View File
@@ -8,13 +8,15 @@
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
"args": [
"run",
// "test",
// "-a",
// "[ilink3]"
"test",
"-a",
"OutrightVanillaOrder"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
"environment": [],
"environment": [
{ "name": "TZ", "value": "America/Chicago" }
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
@@ -31,7 +33,9 @@
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"run"
"test",
"-a",
"[ilink3]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
+1
View File
@@ -41,6 +41,7 @@ else ()
-Wno-missing-braces -Wno-ignored-qualifiers \
--std=c++17 -fdiagnostics-color")
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wno-undef")
add_subdirectory(src/server/sparkd)
add_subdirectory(src/lib)
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/pfisher/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
@@ -0,0 +1,193 @@
using bts;
using bts.api;
using bts.api.core;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class AlgoProviderFactory {
[ImportingConstructor]
AlgoProviderFactory(IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
var from_conf = config.Configuration.get<bts.config.AlgoTypeRuleSet>();
if (from_conf.rules.Count == 0) {
// There seems to be a config race bug, where the returned config values are empty, despite having entries in spark.conf.
// [John]I've only noticed it happen when attached to the debugger,
// but best to have a contingency plan in place anyways, to prevent any chance of regression.
// The fallback sets below mimic the old hardcoded algo type arrays.
var full_algo_set = new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(EnumHelper.get_values_except(
// non-factory algos
AlgoType.None,
AlgoType.Dynamic,
AlgoType.Composite,
// obsolete algos
AlgoType.Oco,
AlgoType.TimeBox,
AlgoType.Pricer,
AlgoType.Tick,
AlgoType.Trigger,
AlgoType.QueueHolder)),
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
var rules = new[] {
// Glbx: Futures/Spreads
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
exchanges: new[] { Venue.Glbx },
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
new AlgoTypeSetImmutable(
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader }),
full_algo_set.dynamic_types)),
// Gen: Futures/Spreads
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
exchanges: EnumHelper.get_values_except(Venue.Glbx, Venue.Unset).ToArray(),
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
new AlgoTypeSetImmutable(
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader, AlgoType.GlbxIceberg }),
full_algo_set.dynamic_types)),
// Gen: Synthetics
new SecurityAlgoTypeSetImmutable(
new SecurityCriteriaClause(
security_types: new[] { SecurityType.Synthetic }),
new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(new[] {
AlgoType.Spreader,
AlgoType.Iceberg,
AlgoType.Lean,
AlgoType.Join,
AlgoType.Cover,
AlgoType.Offset,
AlgoType.Top,
AlgoType.Splitter,
AlgoType.Scratch,
AlgoType.Prorata
}),
full_algo_set.dynamic_types))
};
// attachable
var attachable_algos =
ImmutableHashSet.CreateRange(new[] {
AlgoType.Iceberg,
AlgoType.Lean,
AlgoType.Join,
AlgoType.Cover,
AlgoType.Top,
AlgoType.Offset,
AlgoType.Scratch,
AlgoType.Prorata
});
var attachable_rules = Array.ConvertAll(rules,
satsi => new SecurityAlgoTypeSetImmutable(
satsi.security_criteria,
new AlgoTypeSetImmutable(
satsi.algo_type_set.factory_types.Intersect(attachable_algos),
satsi.algo_type_set.dynamic_types)));
Log.Config.warn($"Failed to load AlgoTypes config, falling back to the following algo set rules:" +
"\n\tStandard\n" +
rules.fmt_diagnostic("\n\t\t") +
"\n\n\tAttachable\n" +
attachable_rules.fmt_diagnostic("\n\t\t"));
security_algo_provider = new AlgoProvider(rules, studio_model, dynamic_helper, security_auxiliary_model);
attachable_algo_provider = new AttachableAlgoProvider(attachable_rules, studio_model, dynamic_helper, security_auxiliary_model);
} else {
var algo_type_data =
EnumHelper.get_values_except(AlgoType.Dynamic, AlgoType.Composite, AlgoType.None)
.Select(t => _get_type_data(t))
.Concat(dynamic_helper.dynamic_types.Select(dt => _get_type_data(AlgoType.Dynamic, dt)))
.ToListLazy();
var conf_sets = from_conf.rules.Select(r => _to_security_algo_set(r, dynamic_helper, algo_type_data)).ToList();
security_algo_provider = new AlgoProvider(conf_sets.Select(tup => tup.standard).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
attachable_algo_provider = new AttachableAlgoProvider(conf_sets.Select(tup => tup.attachable).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
}
}
[Export]
public IAlgoProvider security_algo_provider { get; }
[Export]
public IAttachableAlgoProvider attachable_algo_provider { get; }
static (string name, AlgoType factory, string dynamic) _get_type_data(AlgoType type, string dynamic_type = null) {
string type_name = type.ToString();
return (type_name, type, dynamic_type ?? type_name);
}
static SecurityCriteriaClause _to_clause(bts.config.AlgoTypeContext context) =>
context == null ?
new SecurityCriteriaClause() :
new SecurityCriteriaClause(
exchanges: context.exchanges?.Select(EnumHelper.parse<Venue>)?.ToArray(),
security_types: context.security_types?.Select(EnumHelper.parse<Security.ContractOneofCase>)?.Select(t => t.security_type())?.ToArray(),
symbols: context.symbols?.ToArray());
static (SecurityAlgoTypeSetImmutable standard, SecurityAlgoTypeSetImmutable attachable) _to_security_algo_set(
bts.config.AlgoTypeRule rule,
DynamicOrderHelper dynamic_helper,
IEnumerable<(string name, AlgoType factory, string dynamic)> algo_type_data) {
//
var factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
var dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
var attachable_factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
var attachable_dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
foreach (var a in rule.algos) {
if (Enum.TryParse(a.name, out AlgoType sys_algo) && sys_algo != AlgoType.Composite && sys_algo != AlgoType.Dynamic) {
on_sys_match(sys_algo, a.is_attachable);
}
else if (dynamic_helper.try_parse(a.name, out var dyn_type)) {
on_dyn_match(dyn_type, a.is_attachable);
} else {
foreach (var (name, factory, dynamic) in algo_type_data) {
if (Regex.Match(name, a.name).Success) {
switch (factory) {
case AlgoType.None:
case AlgoType.Composite:
// unexpected, but no need to crash
continue;
case AlgoType.Dynamic:
on_dyn_match(dynamic, a.is_attachable);
break;
default:
on_sys_match(factory, a.is_attachable);
break;
}
}
}
}
}
return (
// standard
new SecurityAlgoTypeSetImmutable(
_to_clause(rule.context),
new AlgoTypeSetImmutable(
factory_types.ToImmutable(),
dynamic_types.ToImmutable())),
// attachable
new SecurityAlgoTypeSetImmutable(
_to_clause(rule.context),
new AlgoTypeSetImmutable(
attachable_factory_types.ToImmutable(),
attachable_dynamic_types.ToImmutable())));
// local methods
void on_sys_match(AlgoType sys_algo, bool is_attachable) {
factory_types.Add(sys_algo);
if (is_attachable)
attachable_factory_types.Add(sys_algo);
}
void on_dyn_match(string dyn_algo, bool is_attachable) {
dynamic_types.Add(dyn_algo);
if (is_attachable)
attachable_dynamic_types.Add(dyn_algo);
}
}
}
}
@@ -0,0 +1,132 @@
using bts.api.core;
using bts.api.orderman;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
readonly struct AlgoTypeSetImmutable {
public static readonly AlgoTypeSetImmutable empty = new AlgoTypeSetImmutable(ImmutableHashSet<AlgoType>.Empty, ImmutableHashSet<string>.Empty);
public readonly ImmutableHashSet<AlgoType> factory_types;
public readonly ImmutableHashSet<string> dynamic_types;
public AlgoTypeSetImmutable(
ImmutableHashSet<AlgoType> factory_types,
ImmutableHashSet<string> dynamic_types) {
this.factory_types = factory_types;
this.dynamic_types = dynamic_types;
}
}
readonly struct SecurityAlgoTypeSetImmutable {
public readonly SecurityCriteriaClause security_criteria;
public readonly AlgoTypeSetImmutable algo_type_set;
public SecurityAlgoTypeSetImmutable(SecurityCriteriaClause security_criteria, AlgoTypeSetImmutable algo_type_set, CustomOrderDefinitionImmutable? default_algo = null) {
this.security_criteria = security_criteria;
this.algo_type_set = algo_type_set;
}
}
static class AlgoTypeSetExtensions {
public static string fmt_diagnostic(this IEnumerable<SecurityAlgoTypeSetImmutable> rules, string line_delim = "\n") {
var sb = new StringBuilder();
int rule_count = 1;
using (var en = rules.GetEnumerator()) {
if (en.MoveNext()) {
string line_delim_tab1 = line_delim + "\t";
string line_delim_tab2 = line_delim + "\t\t";
append_rule(en.Current);
while (en.MoveNext()) {
sb.AppendLine();
append_rule(en.Current);
}
//
void append_rule(SecurityAlgoTypeSetImmutable r) {
sb.Append(line_delim + $"**RULE {rule_count++}**");
sb.Append(line_delim_tab1);
sb.Append("Security Criteria: ");
sb.Append(r.security_criteria.is_defined ? string.Join("; ", r.security_criteria.fmt_diagnostic_msgs()) : "(Undefined)");
sb.Append(line_delim_tab1);
// Factory
sb.Append("Factory Types:");
sb.Append(line_delim_tab2);
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.factory_types));
sb.Append(line_delim_tab1);
// Dynamic
sb.Append("Dynamic Types:");
sb.Append(line_delim_tab2);
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.dynamic_types));
}
}
}
return sb.ToString();
}
public static AlgoTypeSetImmutable intersect_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
new AlgoTypeSetImmutable(a.factory_types.Intersect(b.factory_types), a.dynamic_types.Intersect(b.dynamic_types));
public static AlgoTypeSetImmutable union_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
new AlgoTypeSetImmutable(a.factory_types.Union(b.factory_types), a.dynamic_types.Union(b.dynamic_types));
public static IEnumerable<AlgoData> define_algos(
this AlgoTypeSetImmutable algo_set,
DynamicOrderHelper dynamic_helper,
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
uint display_qty_divisor = 1) =>
//
algo_set.factory_types
.Select(algo_type => {
var def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(algo_type);
if (fit_to_type != null)
def = fit_to_type(def);
// multiply quantity props by display qty divisor to get appropriate actual value
if (display_qty_divisor > 1) {
switch (def.CustomCase) {
case AlgoType.Iceberg:
var mdef = def.CloneToMutable();
mdef.Iceberg.ShowQuantity *= display_qty_divisor;
def = new CustomOrderDefinitionImmutable(mdef);
break;
}
}
return def;
})
.Concat(algo_set.dynamic_types.Select(dynamic_helper.create_custom_defi))
.Select(AlgoData.from_custom_def);
public static ImmutableDictionary<AlgoKey, AlgoData> match_algos(
this IEnumerable<SecurityAlgoTypeSetImmutable> security_algo_sets,
SecurityCriteriaClause clause,
DynamicOrderHelper dynamic_helper,
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
uint display_qty_divisor = 1) {
//
ImmutableDictionary<AlgoKey, AlgoData> rv = null;
foreach (var set in security_algo_sets) {
if (!clause.is_subset_of(set.security_criteria)) {
continue;
} else if (rv == null) {
rv = ImmutableDictionary.CreateRange(
set.algo_type_set.define_algos(dynamic_helper, fit_to_type, display_qty_divisor)
.Select(a => new KeyValuePair<AlgoKey, AlgoData>(a.key, a)));
} else {
foreach (var kv in rv) {
if (kv.Value.value.CustomCase == AlgoType.Dynamic) {
if (!set.algo_type_set.dynamic_types.Contains(kv.Value.value.Dynamic.DynamicOrderType))
rv = rv.Remove(kv.Key);
} else if (!set.algo_type_set.factory_types.Contains(kv.Value.value.CustomCase))
rv = rv.Remove(kv.Key);
}
}
}
return rv ?? ImmutableDictionary<AlgoKey, AlgoData>.Empty;
}
}
}
@@ -0,0 +1,234 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
interface IAlgoProvider {
AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security);
AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause);
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos();
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security);
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause clause);
}
// This interface is used to distinguish the attachable algo provider
// from the standard one wrt MEF export/import.
interface IAttachableAlgoProvider : IAlgoProvider { }
class AlgoProvider : IAlgoProvider {
readonly IEnumerable<SecurityAlgoTypeSetImmutable> _security_algo_types;
readonly IAlgoStudioModel _studio_model;
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
readonly IObservable<TableUpdate<AlgoData, AlgoKey>> _studio_algos_source;
readonly DynamicOrderHelper _dynamic_helper;
readonly AlgoTypeSetImmutable _full_set;
public AlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
_security_algo_types = algo_sets;
_studio_model = studio_model;
_dynamic_helper = dynamic_helper;
_full_set = new AlgoTypeSetImmutable(
ImmutableHashSet.CreateRange(
EnumHelper.get_values_except(CustomOrderDefinition.CustomOneofCase.None, CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite)),
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
_security_auxiliary_model = security_auxiliary_model;
/// initialize <see cref="_studio_algos_source"/> with an empty table, so the
/// <see cref="UpdateExtensions.AppendTable{T, TKey}(IObservable{TableUpdate{T, TKey}}, IEnumerable{T})"/>
/// calls made by the 'subscribe_algos' methods will fire at least once, even if the underlying
/// studio algos stream is empty have been created (this would be the case if the user had not yet created any studio algos).
/// <see cref="subscribe_algos"/>,
/// <see cref="subscribe_algos(SecurityCriteriaClause)"/>,
/// <see cref="subscribe_algos(SecurityImmutable)"/>
_studio_algos_source = studio_model.definitions
.StartWith(TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn))
.Cache();
}
public AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security) =>
_security_algo_types.Where(p => p.security_criteria.includes(security))
.Select(sec_algos => sec_algos.algo_type_set)
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
public AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause) =>
_security_algo_types.Where(p => p.security_criteria.is_subset_of(clause))
.Select(sec_algos => sec_algos.algo_type_set)
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos() =>
_studio_algos_source
.AppendTable(_security_algo_types
.Select(s => s.algo_type_set)
.Aggregate(AlgoTypeSetImmutable.empty, (a, b) => a.union_with(b))
.define_algos(_dynamic_helper));
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause security_clause) {
var system_algos = _security_algo_types.match_algos(
security_clause,
_dynamic_helper,
def => _fit_to_type(def, security_clause, _security_algo_types));
return _studio_algos_source.Select(
a => (algo: a, fitted_def: _fit_to_type(a.value, security_clause, _security_algo_types)),
tup => tup.algo.key)
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), a => a.key)
.AppendTable(system_algos.Values);
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security) {
var clause = new SecurityCriteriaClause(
exchanges: new[] { security.Exchange },
security_types: new[] { security.security_type() });
// i.e., non-studio algos
var system_algos = _security_algo_types.match_algos(
clause,
_dynamic_helper,
def => _fit_to_type(def, security, _security_algo_types),
_security_auxiliary_model.get_display_quantity_divisor(security));
return _studio_algos_source.Select(
a => (algo: a, fitted_def: _fit_to_type(a.value, security, _security_algo_types)),
tup => tup.algo.key)
// filter out algos that use an unsupported root, because this would have the effect of duplicating the default algo
// (e.g., GlbxIceberg orders would be converted to Limit/Spreader orders for non-Glbx securities).
.Filter(tup => tup.fitted_def.actual().CustomCase == tup.algo.value.actual().CustomCase, tup => tup.algo.key)
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), AlgoData.key_fn)
.AppendTable(system_algos.Values);
}
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) {
if (security_clause.security_types.empty_if_null().Contains(Security.ContractOneofCase.Synthetic.security_type())) {
mdef.Spreader = new SpreaderOrderDefinition();
} else {
mdef.Limit = new LimitOrderDefinition();
}
}
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) {
if (security.ContractCase == Security.ContractOneofCase.Synthetic) {
mdef.Spreader = new SpreaderOrderDefinition {
Security = security.CloneToMutable()
};
} else {
mdef.Limit = new LimitOrderDefinition();
}
}
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
var mroot = defi.CloneToMutable();
if (_fit_to_type(mroot, security_clause, algo_sets)) {
defi = new CustomOrderDefinitionImmutable(mroot);
}
return defi;
}
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
var mroot = defi.CloneToMutable();
if (_fit_to_type(mroot, security, algo_sets)) {
defi = new CustomOrderDefinitionImmutable(mroot);
}
return defi;
}
bool _fit_to_type(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
mdef = mdef.Composite.Definition;
var sec_sets = algo_sets.Where(s => security_clause.is_subset_of(s.security_criteria));
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
if (!algo_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
assign_fallback_algo(mdef, security_clause);
return true;
}
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
assign_fallback_algo(mdef, security_clause);
return true;
}
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security_clause, algo_sets)) > 0;
}
bool _fit_to_type(CustomOrderDefinition mdef, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
mdef = mdef.Composite.Definition;
var sec_sets = algo_sets.Where(s => s.security_criteria.includes(security));
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
if (!sec_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
assign_fallback_algo(mdef, security);
return true;
}
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
assign_fallback_algo(mdef, security);
return true;
}
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security, algo_sets)) > 0;
}
}
class AttachableAlgoProvider : AlgoProvider, IAttachableAlgoProvider {
public AttachableAlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model)
: base(algo_sets, studio_model, dynamic_helper, security_auxiliary_model) { }
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) =>
mdef.Limit = new LimitOrderDefinition();
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) =>
mdef.Limit = new LimitOrderDefinition();
}
static class AlgoProviderExtensions {
// This stream filters out composite algos that are not subsets of the non-composite algo types in the table
// (normally, such composites are 'auto-fit,' meaning the algo nodes that are not part of the set are replaced with ones that are).
// For example, consider a composite algo the following structure: Offset -> GlbxIceberg.
// If an update arrives with these noncomposite types: "Iceberg, Limit, Market," that composite algo is not a subset of the non-composite algo types,
// because GlbxIceberg is not in the table, so it would only be added to the table if/when GlbxIceberg arrives.
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(this IAlgoProvider algo_provider, bool allow_undefined_child = false) =>
subscribe_complete_algos(algo_provider.subscribe_algos(), allow_undefined_child);
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(IObservable<TableUpdate<AlgoData, AlgoKey>> source, bool allow_undefined_child = false) =>
Observable.Create<TableUpdate<AlgoData, AlgoKey>>(o => {
var last_out = TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn);
var incomplete_composites = ImmutableHashSet<AlgoData>.Empty;
return source.Subscribe(table => {
var bd = last_out.to_builder();
foreach (var algo in incomplete_composites.Where(a => _validate_algo_structure(a.value, table.state, allow_undefined_child))) {
incomplete_composites = incomplete_composites.Remove(algo);
bd.add_or_update(algo);
}
foreach (var a in table.removed.Concat(table.changes.Old()))
bd.remove(a.key);
foreach (var a in table.added.Concat(table.changed)) {
if (_validate_algo_structure(a.value, table.state, allow_undefined_child)) {
bd.add_or_update(a);
} else {
incomplete_composites = incomplete_composites.Add(a);
}
}
o.OnNext(last_out = bd.to_table());
});
});
static bool _validate_algo_structure(CustomOrderDefinitionImmutable custom_def, IReadOnlyDictionary<AlgoKey, AlgoData> allowed_algo_parts, bool allow_undefined_child) =>
OrderDataHelper.enumerate_algo_structure(custom_def)
.Select((algo_node, index) =>
// None is an acceptable algo type as long as it's not the root algo (i.e., index > 0)
(index > 0 && allow_undefined_child && algo_node.type == CustomOrderDefinition.CustomOneofCase.None) || _is_part_contained(algo_node, allowed_algo_parts))
.All();
static bool _is_part_contained((CustomOrderDefinition.CustomOneofCase type, string dyn, uint studio_id, uint spreader_sec) structure, IReadOnlyDictionary<AlgoKey, AlgoData> state) =>
structure.type == CustomOrderDefinition.CustomOneofCase.Composite ||
structure.type == CustomOrderDefinition.CustomOneofCase.Dynamic ?
state.ContainsKey(AlgoKey.from_dynamic_type(structure.dyn)) :
state.ContainsKey(AlgoKey.from_known_type(structure.type));
}
}
+2
View File
@@ -27,6 +27,7 @@ namespace mbts.ui {
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
CurrencyExchangeGeometryKey = nameof(CurrencyExchangeGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
@@ -161,6 +162,7 @@ namespace mbts.ui {
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
public static ImageSource CopyBoxes => create_icon(CopyBoxesGeometryKey, null, _copy_boxes_pen);
public static ImageSource CurrencyExchange => create_icon(CurrencyExchangeGeometryKey);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
@@ -62,6 +62,10 @@
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.CurrencyExchangeGeometryKey}">
M0,24L15.324005,24C16.042007,24.785004,16.875,25.460999,17.800003,26L2,26 2,30 23.087997,30 23.087006,27.424988C23.771011,27.423004,24.439011,27.352997,25.085999,27.225983L25.085999,30C25.085999,31.10199,24.189011,32,23.085999,32L2,32C0.89700317,32,0,31.10199,0,30z M22.484015,13.026998L23.475018,13.026998 23.475018,13.856997C24.151018,13.885996,24.605021,14.016996,24.93902,14.167996L24.646021,15.233995C24.38302,15.129995 23.919018,14.912995 23.191017,14.912995 22.535015,14.912995 22.323015,15.176995 22.323015,15.440995 22.323015,15.751994 22.676016,15.950994 23.535017,16.251994 24.73702,16.647994 25.222021,17.166993 25.222021,18.015992 25.222021,18.854991 24.58502,19.571991 23.424017,19.759991L23.424017,20.72199 22.434015,20.72199 22.434015,19.825991C21.757013,19.797991,21.101011,19.627991,20.71701,19.420991L21.019012,18.317992C21.444014,18.533992 22.040014,18.731992 22.697016,18.731992 23.272017,18.731992 23.666018,18.524992 23.666018,18.147992 23.666018,17.788992 23.343018,17.562993 22.596016,17.326993 21.515013,16.987993 20.777012,16.516994 20.777012,15.601995 20.777012,14.771996 21.404013,14.119996 22.484015,13.922997z M0.065009117,12.384011L13.628021,12.384011C13.320021,13.018011,13.075021,13.687011,12.89902,14.384011L2.065011,14.384011 2.065011,18.384011 12.68002,18.384011C12.77602,19.075011,12.93702,19.743011,13.16102,20.384011L0.065009117,20.384011z M23.060013,10.460008C19.480015,10.460008 16.568016,13.368006 16.568016,16.943003 16.568016,20.519001 19.480015,23.426999 23.060013,23.427 26.640011,23.426999 29.55201,20.519001 29.55201,16.943003 29.55201,13.368006 26.640011,10.460008 23.060013,10.460008z M23.060013,8.4600086C27.74201,8.4600086 31.552008,12.266006 31.552008,16.943003 31.552008,21.621 27.74201,25.426998 23.060013,25.426998 18.378014,25.426998 14.568016,21.621 14.568016,16.943003 14.568016,12.266006 18.378014,8.4600086 23.060013,8.4600086z M2.0000181,2L2.0000181,6 23.08602,6 23.08602,2z M2.0000181,0L23.08602,0C24.18902,0,25.08602,0.89699936,25.08602,2L25.08602,6.6599998C24.430019,6.5310001 23.753019,6.4599991 23.060019,6.4599991 21.06402,6.4599991 19.20402,7.0300007 17.614019,8L1.8119812E-05,8 1.8119812E-05,2C1.8066406E-05,0.89699936,0.89701813,0,2.0000181,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
</sys:String>
@@ -276,6 +280,7 @@
m464.867188 0c-25.988282 0-47.132813 21.144531-47.132813 47.132812v313.75c0 49.324219 11.660156 98.710938 33.71875 142.824219 2.539063 5.082031 7.734375 8.292969 13.414063 8.292969 5.683593 0 10.875-3.210938 13.417968-8.292969 22.054688-44.113281 33.714844-93.503906 33.714844-142.824219v-313.75c0-25.988281-21.144531-47.132812-47.132812-47.132812zm0 30c9.445312 0 17.132812 7.6875 17.132812 17.132812v33.199219h-34.265625v-33.199219c0-9.445312 7.683594-17.132812 17.132813-17.132812zm17.132812 323.46875h-34.265625v-243.136719h34.265625zm-17.132812 105.4375c-8.71875-24.277344-14.1875-49.734375-16.222657-75.4375h32.445313c-2.035156 25.707031-7.507813 51.160156-16.222656 75.4375zm0 0
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CheckMarkGeometryKey}">
F1M9.97498,1.22334L4.6983,9.09834 4.52164,9.09834 0,5.19331 1.27664,3.52165 4.255,6.08833 8.33331,1.52588E-05 9.97498,1.22334z
</sys:String>
@@ -46,7 +46,7 @@ namespace bts.shell {
async void _show_synthetic_create(SecurityCriteria security_criteria) {
var found_securities = await security_criteria.securities(_bus);
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Count > 0);
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Length > 0);
if (!respect_clause_security_ordering_is_possible) {
_show_synthetic_create(found_securities.Values.ToList());
} else {
@@ -85,9 +85,7 @@ namespace mbts.spark {
icon = Icons.Synthetics;
_has_working_orders = backing(nameof(has_working_orders), false);
contract_picker_model = new ContractPickerModel(bus, false, null, new SecurityCriteria(new[] {
new SecurityCriteriaClause() {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
}
new SecurityCriteriaClause(exchanges: EnumHelper.get_values_except(Venue.Bts).ToArray())
}));
contract_picker_model.cancel_action += _close_new_leg_popup;
contract_picker_model.ok_action += () => {
@@ -9,6 +9,8 @@ namespace mbts.spark {
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
struct AlgoData : IEquatable<AlgoData> {
public static readonly Func<AlgoData, AlgoKey> key_fn = a => a.key;
public readonly CustomOrderDefinitionImmutable value;
public readonly string name;
public readonly AlgoKey key;
@@ -128,6 +128,7 @@ namespace mbts.spark {
}
public bool is_persisted => mode == Mode.Persisted;
public bool is_composite => _composite_key.HasValue || _unpersisted.HasValue;
public uint persisted => _composite_key.Value;
public Guid unpersisted => _unpersisted.Value;
@@ -199,6 +200,15 @@ namespace mbts.spark {
}
return EqualityComparer.phase_hash(hash, phase_mult);
}
public override string ToString() {
switch (mode) {
case Mode.Dynamic: return $"Dynamic: {dynamic_type}";
case Mode.Factory: return $"Factory: {factory}";
case Mode.Persisted: return $"Composite: {persisted}";
case Mode.Unpersisted: return $"(Composite): {unpersisted}";
default: return mode.ToString();
}
}
public static bool operator ==(AlgoKey a, AlgoKey b) => a.Equals(b);
public static bool operator !=(AlgoKey a, AlgoKey b) => !a.Equals(b);
@@ -12,20 +12,20 @@ namespace mbts.spark {
[Export(typeof(IAlgoStudioModel))]
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
readonly AlgoDefinitionService _service;
readonly AdminModel _admin_model;
readonly UserTuple _user;
readonly ErrorReporter _error_reporter;
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
IDisposable _dispose;
[ImportingConstructor]
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, UserTuple user, ErrorReporter error_reporter) {
_service = service;
this.metadata_service = metadata_service;
_admin_model = admin_model;
_user = user;
_error_reporter = error_reporter;
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
.Filter(r => r.Metadata.IsActive && _user.can_access_user_data(r.Metadata.CreatorUserId))
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
@@ -37,7 +37,7 @@ namespace mbts.spark {
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
_with_key(new AlgoDefinitionUpdateRequest { UserId = _user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving definition");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
@@ -93,4 +93,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -11,6 +11,8 @@ namespace mbts.spark {
/// decouple the client-side key type from the server-side one, so both key types may evolve independently moving forward.
/// </summary>
struct AlgoDefinitionMetadata : IEquatable<AlgoDefinitionMetadata> {
public static readonly Func<AlgoDefinitionMetadata, AlgoKey> key_fn = m => m.id;
public readonly AlgoKey id, default_template;
readonly AlgoKey[] _favorite_templates;
public readonly bool is_favorite, is_hidden;
@@ -35,34 +35,21 @@ namespace mbts.spark {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed)
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) =>
update_persisted_definitions(target, update.added, update.changed, update.removed.Select(AlgoData.key_fn), factory);
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, IEnumerable<AlgoData> added, IEnumerable<AlgoData> changed, IEnumerable<AlgoKey> removed, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var k in removed)
target.Remove(k);
foreach (var d in changed)
target[d.key].load(d);
foreach (var d in update.added) {
foreach (var d in added) {
if (target.TryGetValue(d.key, out AlgoDefinitionVM def)) {
def.load(d);
} else target.Add(factory(d));
}
}
public static void update_persisted_definition_wrappers(IDictionary<AlgoKey, IAlgoVM> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed) {
var vm = target[d.key];
if (vm is AlgoVMWrapper wrapper) {
wrapper.value.load(d);
} else vm.load_from(d.value);
}
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out IAlgoVM def)) {
def.load_from(d.value);
} else target.Add(d.key, new AlgoVMWrapper(factory(d)));
}
}
public static void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).commit_all();
@@ -42,21 +42,21 @@ namespace mbts.spark {
readonly Property<string> _name, _abbreviation;
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
readonly DeferrableActionWrapper _refresh_on_local_changes;
readonly Property<AlgoFormats> _selected_format;
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
int? _favorite_index;
Brush _badge_fill;
Snapshot? _committed;
AlgoDefinitionMetadata _metadata;
IDisposable _loaded_data_hook;
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
new AlgoDefinitionVM(model, data, editing_permissions);
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name) =>
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), (AlgoEditingPermissions)int.MaxValue);
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoEditingPermissions init_permissions) {
// init core data
_model = model;
_permissions_mask.mask = init_permissions;
@@ -80,7 +80,6 @@ namespace mbts.spark {
// init backing props
_name = backing(nameof(name), def_data.name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
_selected_format = backing(nameof(selected_format), init_format);
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
_is_expanded = backing(nameof(is_expanded), true);
_is_selected = backing(nameof(is_selected), true);
@@ -133,17 +132,6 @@ namespace mbts.spark {
break;
}
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeEach(t => {
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(t.fmt);
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(t.fmt));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}
return Disposable.empty;
}));
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
resources.Add(this.connect_data(permission_src));
resources.Add(this.connect_data(Observable.Return(model)));
@@ -202,9 +190,6 @@ namespace mbts.spark {
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
public Brush badge_fill => _badge_fill;
// format
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
// visual states
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
@@ -270,17 +255,14 @@ namespace mbts.spark {
void IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)>.OnError(Exception error) { }
#endregion
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
var orig_algo = algo_root?.id;
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
if (!try_select(orig_algo) && !try_select(default_algo))
algo_root = algo_roots.FirstOrDefault();
//
bool try_select(AlgoKey? algo_key) {
if (algo_key.HasValue && _algo_roots.TryGetValue(algo_key.Value, out IAlgoVM match)) {
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> data, AlgoKey? default_algo) {
Disposable.dispose(ref _loaded_data_hook);
_algo_roots.SyncWithDictionary(data, d => OrderTypeVMFactory.create(d, is_expanded: true));
_loaded_data_hook = ObservableAutoWrapper.connect_item_hooks(algo_roots, vm =>
ObservableAutoWrapper.connect_item_actions(vm.child_algo_selectors, s => s.load_algo_data(data, null)));
if (algo_root == null || !_algo_roots.ContainsKey(algo_root.id)) {
if (default_algo.HasValue && _algo_roots.TryGetValue(default_algo.Value, out IAlgoVM match))
algo_root = match;
return true;
} else return false;
}
}
@@ -346,7 +328,7 @@ namespace mbts.spark {
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
public AlgoDefinitionVM to_persisted(AlgoData def) =>
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
new AlgoDefinitionVM(_model, def, editing_permissions_mask);
public AlgoData create_algo_def() =>
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
@@ -368,7 +350,6 @@ namespace mbts.spark {
void _assign_from_data(Snapshot state) {
_assign_from_data(state.data);
_selected_format.set(state.format);
_abbreviation.set(state.abbreviation);
custom_badge_color.set_nullable(state.custom_badge_color);
}
@@ -429,41 +410,36 @@ namespace mbts.spark {
}
Snapshot _snapshot() =>
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable(), selected_format);
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable());
public struct Snapshot : IEquatable<Snapshot> {
public readonly AlgoData data;
public readonly string abbreviation;
public readonly Color? custom_badge_color;
public readonly AlgoFormats format;
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color, AlgoFormats format) {
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color) {
this.data = data;
this.abbreviation = abbreviation;
this.custom_badge_color = custom_badge_color;
this.format = format;
}
public bool Equals(Snapshot other) =>
data == other.data &&
abbreviation == other.abbreviation &&
custom_badge_color == other.custom_badge_color &&
format == other.format;
custom_badge_color == other.custom_badge_color;
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
data.GetHashCode(),
abbreviation?.GetHashCode() ?? 0,
custom_badge_color?.GetHashCode() ?? 0,
(int)format);
custom_badge_color?.GetHashCode() ?? 0);
#if DEBUG
public override string ToString() {
var sb = new System.Text.StringBuilder();
sb.Append($"name: {data.name}; ");
sb.Append($"abbrev.: {abbreviation}; ");
sb.Append($"badge: {custom_badge_color} ");
sb.Append($"fmt: {format} ");
sb.Append($"def: {data.value}");
return sb.ToString();
}
@@ -477,7 +453,6 @@ namespace mbts.spark {
public bool Equals(Snapshot x, Snapshot y) =>
x.abbreviation == y.abbreviation &&
x.custom_badge_color == y.custom_badge_color &&
x.format == y.format &&
x.data.key == y.data.key &&
x.data.name == y.data.name &&
x.data.value.Equals(y.data.value);
@@ -1,7 +1,11 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
@@ -37,7 +41,6 @@ namespace mbts.spark {
}
class AlgoStudioVM : DisposableViewModelBase {
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
readonly OrdersModel _model;
readonly Property<AlgoDefinitionVM> _selected_definition;
readonly Property<IAlgoVM> _editable_content;
@@ -70,12 +73,24 @@ namespace mbts.spark {
_revert_cmd_tooltip = backing(nameof(revert_cmd_tooltip), get_revert_tooltip);
//
_dispose = new CompositeDisposable(
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
var orig_selected_def = selected_definition;
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
_model.subscribe_complete_algos(allow_undefined_child: true).OnDispatcher().Subscribe(_definitions_update_buffer),
_definitions_update_buffer.OnDispatcher()
.SubscribeEach(u => {
u.FilterIncremental(a => a.key.is_composite, out var added, out var changes, out var removed);
AlgoCollectionHelper.update_persisted_definitions(
_definitions, added, changes.New(), removed.Select(AlgoData.key_fn), algo => AlgoDefinitionVM.create(_model, algo));
if (selected_definition == null)
selected_definition = definitions.FirstOrDefault();
// Load the algo building blocks (i.e., factory and dynamic algos):
// - Composite algos cannot reference other composite algos - that would create awkward dependency issues as composite algos can be modified/deleted.
// - Spreader algos are filtered out as well, because that don't actually mean anything to the algo structure.
// Synthetic securities in our system are defined by a set of contract legs, and a [single]strategy.
// Note: synthetic strategy templates, however, could be introduced at a later date (no reason why 1 synthetic security couldn't have several ExecutionStrategies).
var algo_building_blocks = new FilteredDictionary<AlgoKey, AlgoData>(
u.state,
a => a.key.mode == AlgoKey.Mode.Dynamic || (a.key.mode == AlgoKey.Mode.Factory && a.key.factory != CustomOrderDefinition.CustomOneofCase.Spreader));
return ObservableAutoWrapper.connect_item_actions(_definitions.AsCollection(),
d => ((IAlgoSelector)d).load_algo_data(algo_building_blocks, null));
}),
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
_persisted_meta = meta;
@@ -125,7 +140,7 @@ namespace mbts.spark {
}
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name);
_definitions.Add(new_def);
selected_definition = new_def;
request_view_focus_name?.Invoke(new_def);
@@ -1,214 +0,0 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace mbts.spark {
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
IObserver<SecurityMD?>,
IObserver<SecurityImmutable?>,
IObserver<OrdersModel>,
IObserver<AlgoEditingPermissions> {
static readonly HashSet<string>
_algo_root_echo_props = new HashSet<string> {
nameof(is_expanded),
nameof(editing_permissions),
nameof(definition),
nameof(display_name)
};
static readonly IReadOnlyDictionary<string, string>
_value_echo_props = new Dictionary<string, string> {
{ nameof(AlgoDefinitionVM.name), nameof(display_name) },
{ nameof(AlgoDefinitionVM.abbreviation), nameof(abbreviation) },
{ nameof(AlgoDefinitionVM.badge_fill), nameof(meta) }
};
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly DisposableContainer _value_hooks = new DisposableContainer();
Action _definition_changed = () => { };
_ObservedData? _data_cache;
AlgoDefinitionVM _value;
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
value = algo_definition_vm;
_dispose = new CompositeDisposable(_value_hooks) {
_child_algo_selectors.clear_collections,
_algo_params.clear_collections,
() => _value?.Dispose()
};
}
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
public AlgoDefinitionVM value {
get => _value;
set {
if (_value != value) {
_value?.Dispose();
_value = value;
_value_hooks.replace(_set_hooks(value));
RaisePropertyChanged(nameof(value));
}
}
}
public AlgoKey id => _value.key;
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
public OrderMetadata meta {
get {
if (_value != null) {
if (_value.custom_badge_color.enabled)
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
else if (_value.algo_root != null)
return _value.algo_root.meta;
}
return OrderDataHelper.get_metadata(order_type);
}
}
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
public bool can_expand => _algo_root?.can_expand ?? false;
public bool is_expanded {
get => _value.algo_root?.is_expanded ?? false;
set {
if (_value != null)
_value.is_expanded = value;
}
}
public string display_name => _value?.name ?? string.Empty;
public string abbreviation => _value?.abbreviation;
public AlgoDisplayGroup group => _value?.group ?? AlgoDisplayGroup.BTS;
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
[EditorBrowsable(EditorBrowsableState.Never)]
public ObservableCollection<IAlgoSelector> child_algo_selectors_view {
get {
_value?.algo_root?.ensure_init();
return _child_algo_selectors;
}
}
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
[EditorBrowsable(EditorBrowsableState.Never)]
public dynamic children_vm => _algo_root?.children_vm;
IAlgoVM _algo_root => _value?.algo_root;
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
IDisposable _set_hooks(AlgoDefinitionVM vm) {
var algo_root_subs = new DisposableContainer();
vm.PropertyChanged += on_value_prop_changed;
return new CompositeDisposable(
vm.observe_algo_root().Subscribe(r => {
algo_root_subs.replace(null);
_child_algo_selectors.clear_collections();
_algo_params.clear_collections();
if (r != null) {
if (_data_cache.HasValue) {
_data_cache.Value.forward_to(r);
}
_child_algo_selectors.add_collection(r.child_algo_selectors);
_algo_params.add_collection(r.algo_params);
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
algo_root_subs.replace(new CompositeDisposable(
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
});
}
RaisePropertyChanged(nameof(group));
}),
algo_root_subs) {
() => vm.PropertyChanged -= on_value_prop_changed
};
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_algo_root_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
}
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
RaisePropertyChanged(wrapper_prop);
}
}
// permissions
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
}
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
}
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnCompleted() { }
// security
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
}
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
void IObserver<SecurityImmutable?>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
struct _ObservedData {
public readonly AlgoEditingPermissions permissions;
public readonly OrdersModel model;
public readonly SecurityImmutable? security;
public readonly SecurityMD? security_md;
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
this.permissions = permissions;
this.model = model;
this.security = security;
this.security_md = security_md;
}
public void forward_to(IAlgoVM recipient) {
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
if (model != null)
(recipient as IObserver<OrdersModel>)?.OnNext(model);
if (security_md.HasValue)
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
if (security.HasValue)
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
}
public _ObservedData with(AlgoEditingPermissions? permissions) =>
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
public _ObservedData with(OrdersModel model) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityImmutable? security) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityMD? security_md) =>
new _ObservedData(permissions, model, security, security_md);
}
}
}
@@ -302,7 +302,7 @@
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource DoubleTextConverter}}"/>
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Enum-->
@@ -406,7 +406,7 @@
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownDouble.CommandBindings>
@@ -39,31 +39,6 @@ namespace mbts.spark {
}
}
static IDisposable _wrap_rows(AlgoDefinitionVM algo_def, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) {
AlgoParamRowVM root = null;
ObservableAggregateCollection<AlgoParamRowVM> algo_params;
if (include_algo_type_rows) {
// while normal params live and die with the selected algo-type (when the algo-type changes, they are removed, and replaced with params for the new algo type),
// the algo-type row is constant (it is the row with the algo-type dropdown, which the user may use to select different algos).
algo_params = new ObservableAggregateCollection<AlgoParamRowVM>();
rows.add_collection(new ObservableAppendCollection<AlgoParamRowVM>(algo_params, root = new AlgoSelectorParamRowVM(new AlgoVMWrapper(algo_def), algo_def, null)).AsReadOnly());
} else {
algo_params = rows;
}
return algo_def.observe_algo_root().SubscribeEach(a => {
algo_params.clear_collections();
if (a == null)
return Disposable.empty;
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
/// Note: the order of operations here matters
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
var hook = _wrap_rows(a, root, child_rows, include_algo_type_rows);
algo_params.add_collection(child_rows.AsReadOnly());
return hook;
});
}
static IDisposable _wrap_rows(IAlgoVM algo_root, AlgoParamRowVM parent, ObservableAggregateCollection<AlgoParamRowVM> all_rows, bool include_algo_type_rows) {
var wrapped_params = new ObservableAutoWrapper<AlgoParamRowVM, IAlgoParamVM>(algo_root.algo_params, pvm => new AlgoParamRowVM(algo_root, pvm, parent));
var selector_params = new ObservableCollection<AlgoParamRowVM>();
@@ -85,4 +60,4 @@ namespace mbts.spark {
}
}
}
}
}
@@ -106,7 +106,7 @@
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
Content="{Binding algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
@@ -100,6 +100,7 @@ namespace bts.shell {
ShellWindowVM shell_window_vm = null;
IShellWindow shell_window = null;
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
LadderVM vm = null;
var view = new Lazy<UIElement>(() => {
// resources
var model = new LadderModel(md, _orders_model, _mbo);
@@ -113,7 +114,6 @@ namespace bts.shell {
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
bool initialized = false, needs_resync = false;
LadderVM vm = null;
var settings_subject = new Subject<LadderSettings>();
var settings_source = Observable.Create<LadderSettings>(o => {
ensure_synced();
@@ -272,10 +272,11 @@ namespace bts.shell {
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
icon: Icons.Ladder,
thread_id: _thread_id_provider.next_id(),
vm => {
shell_window_vm = vm;
swvm => {
shell_window_vm = swvm;
title_source.connect();
});
},
key_event_args => vm?.key_down(key_event_args));
}
async void _show_windows(IImmutableSet<uint> securities) {
@@ -144,7 +144,7 @@ namespace mbts.spark {
string candidate = string.Empty;
if (a.HasValue) {
var algo_name = a.Value.name;
var words = algo_name.Split(delim);
var words = algo_name.Split(delim, StringSplitOptions.RemoveEmptyEntries);
if (words.Length == 1) {
var single_word = words[0];
if (single_word.Length > 1) {
@@ -153,7 +153,7 @@ namespace mbts.spark {
candidate = single_word;
}
} else {
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
candidate = string.Join(string.Empty, words.Take(2).Select(w => w.Substring(0, 1)));
}
}
return candidate.ToUpper();
@@ -7,6 +7,7 @@ using bts.proto;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.color_picker;
@@ -115,7 +116,16 @@ namespace mbts.spark {
foreach (var c in _observable_components)
c.PropertyChanged += on_prop_changed;
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
_algo_definitions.AddRange(_model.get_permitted_algos(security).factory_types
.Select(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable)
.Select(AlgoData.from_custom_def)
.Select(algo_factory));
_algo_definitions.AddRange(_model.get_permitted_algos(security).dynamic_types
.Select(dyn_type => new CustomOrderDefinitionImmutable(new CustomOrderDefinition {
Dynamic = _model.dynamic_helper.create_def(dyn_type)
}))
.Select(AlgoData.from_custom_def)
.Select(algo_factory));
_dispose = new CompositeDisposable(
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
@@ -144,7 +154,7 @@ namespace mbts.spark {
c.PropertyChanged -= on_prop_changed;
}
AccountTuple? account_factory(uint key, string name) => new AccountTuple(key, name);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, is_synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange);
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
}
public event Action result_changed;
@@ -260,7 +260,11 @@ namespace mbts.spark {
public IBus bus => _bus;
public event Action on_tick, request_settings_dialog, settings_invalidated;
public event Action
on_tick,
request_settings_dialog,
request_recenter,
settings_invalidated;
public MDConsumerHandle md => _model.md;
// we return only entries who have piqs and whose piqs are not uint.Max
@@ -394,7 +398,8 @@ namespace mbts.spark {
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
public uint actual_alternate_order_size =>
(current_settings?.alternate_order_size ?? 0) * Math.Max(1, display_quantity_divisor);
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
public bool enforce_max_clip_from_settings => current_settings.enforce_max_clip;
public uint max_clip_from_settings => current_settings.max_clip;
@@ -448,15 +453,27 @@ namespace mbts.spark {
public void key_down(KeyEventArgs e) {
if (order_properties_vm == null)
return;
if (KeyboardExtensions.IsDown(Key.Space)) {
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
if (!KeyboardExtensions.IsCtrlDown()) return;
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
if (new_algo != null) {
order_properties_vm.selected_algo_definition = new_algo;
e.Handled = true;
switch (e.Key) {
case Key.Space:
if (KeyboardExtensions.IsShiftDown()) {
// only recenter this ladder
request_recenter?.Invoke();
} else {
// recenter all ladders
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
e.Handled = true;
break;
default:
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
if (KeyboardExtensions.IsCtrlDown()) {
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
if (new_algo != null) {
order_properties_vm.selected_algo_definition = new_algo;
e.Handled = true;
}
}
break;
}
}
@@ -473,11 +490,11 @@ namespace mbts.spark {
public Task send_order_async((double price, Side? side, MouseButton button) args) {
if (order_properties_vm != null && args.button != MouseButton.Middle) {
var size_from_alternate = args.button == MouseButton.Right;
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || current_settings.alternate_order_size > 0)) {
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || actual_alternate_order_size > 0)) {
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
} else {
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
uint size = size_from_alternate ? actual_alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
// enforce max clip
if (enforce_max_clip_from_settings && size > max_clip_from_settings) {
size = max_clip_from_settings;
@@ -514,7 +531,7 @@ namespace mbts.spark {
if (fav_algo != null) {
if (_favorite_algo_map.TryGetValue(fav_algo.key, out var d)) {
if (!d.flags.HasFlag(LadderFavoriteAlgoFlags.Sticky) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
var key = new AlgoKey(OrderDataHelper.get_default_algo(security.ContractCase));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
@@ -53,7 +53,7 @@ namespace mbts.spark {
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container =
new RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>>();
IDisposable _settings_sub, _volume_sub, _tick_sub;
IDisposable _settings_sub, _volume_sub, _tick_sub, _vm_sub;
ScrollViewer _scrollViewer;
LadderSettings _settings;
LadderVM _vm;
@@ -353,7 +353,9 @@ namespace mbts.spark {
return;
}
} else if (_vm != null) {
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
uint size = e.ChangedButton == MouseButton.Right ?
_vm.actual_alternate_order_size :
_vm.size_calculator.value ?? 0;
// does the size violate the local max clip
if (_vm.enforce_max_clip_from_settings && size > _vm.max_clip_from_settings) {
@@ -697,10 +699,22 @@ namespace mbts.spark {
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
_price_dirty = true;
_piq_dirty = true;
_vm.on_tick += _tick;
// **use local vm to protect against re-assignment,
// so the events are unhooked from the same instance to which they were added
// when the subs are disposed.
var local_vm = _vm;
local_vm.on_tick += _tick;
local_vm.request_recenter += on_request_recenter;
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
_volume_sub = _vm.model.order_gateway.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
_tick_sub = Disposable.Create(() => _vm.on_tick -= _tick);
// **unhook from local_vm.request_recenter.
// todo: organize these subs into a single entity, prob _vm_subs.
// This hasn't already been done bc the ladder a bit fragile, so changes must be carefully implemented,
// with meticulous attention to detail.
_tick_sub = Disposable.Create(() => local_vm.on_tick -= _tick);
_vm_sub = Disposable.Create(() => local_vm.request_recenter -= on_request_recenter);
void on_request_recenter() =>
RecenterCommand?.ExecuteIfCan(null);
}
}
@@ -93,6 +93,7 @@ namespace mbts.spark {
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
this.PreviewMouseDown += LadderView_PreviewMouseDown;
PreviewKeyDown += (s, e) => vm.key_down(e);
}
IDisposable _connect_left_panel_scroll() {
@@ -26,7 +26,7 @@ namespace bts.shell.manual_fills {
readonly Property<bool> _is_dirty;
readonly Property<EditableItemState> _editable_item_state;
readonly DelegateProperty<bool> _is_valid;
readonly DisplayQuantityMultiplier _size = new DisplayQuantityMultiplier();
readonly DisplayQuantityMultiplier<uint> _size = DisplayQuantityMultiplier.create_uint();
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
SecurityImmutable? _security;
AccountTuple? _account;
@@ -29,7 +29,7 @@ namespace mbts.spark {
int _depth_level;
SecurityImmutable? _security => order_tuple.order.security;
// for mini-ob
DisplayQuantityMultiplier _prospective_qty_translator_backing;
DisplayQuantityMultiplier<uint> _prospective_qty_translator_backing;
uint _display_qty_divisor_backing;
bool _display_qty_divisor_dirty = true;
double _prospective_price;
@@ -239,10 +239,9 @@ namespace mbts.spark {
IDisplayTranslator<uint> _prospective_qty_translator {
get {
if (_prospective_qty_translator_backing == null) {
_prospective_qty_translator_backing = new DisplayQuantityMultiplier {
conversion_data = new QuantityConversionData(_display_qty_divisor, QuantityConversionData.OpType.DivideActual),
actual_value = quantity
};
_prospective_qty_translator_backing = DisplayQuantityMultiplier.create_uint();
_prospective_qty_translator_backing.conversion_data = new QuantityConversionData(_display_qty_divisor, QuantityConversionData.OpType.DivideActual);
_prospective_qty_translator_backing.actual_value = quantity;
_prospective_qty_translator.value_changed += () =>
RaisePropertyChanged(nameof(is_qty_dirty));
} else {
@@ -86,7 +86,7 @@ namespace mbts.spark {
_model = model;
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle, k => _cached_orders.TryGetValue(k, out var o) ? o.order : (OrderSecurityAlgo?)null);
ladder_vm = new LadderVMContainer(bus, model);
order_mod_vm = new OrderParamsVM(this, AlgoFormats.Exchange, AlgoEditingPermissions.AllowEditValues);
order_mod_vm = new OrderParamsVM(this, AlgoEditingPermissions.AllowEditValues);
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
order_mod_vm.algo_vm.algo_root = null;
_show_order_params = backing(nameof(show_order_params), () => (_right_panel.visible_components & OrderBookRightPanelSettings.Components.Params) != 0);
@@ -99,7 +99,7 @@ namespace mbts.spark {
};
_can_attach_selected_orders = backing(nameof(can_attach_selected_orders), () => _attachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
_can_detach_selected_orders = backing(nameof(can_detach_selected_orders), () => _detachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
attach_vm = new OrderParamsVM(this, AlgoFormats.Attachable, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
attach_vm = new OrderParamsVM(this, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
_settings_manager = settings_manager;
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
@@ -244,7 +244,22 @@ namespace mbts.spark {
}),
grid_settings_manager,
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
order_mod_vm.algo_vm.connect_data(
/// This is not the same security source used to populate <see cref="OrderParamsVM.security_md"/>.
/// The latter is always synced with the root of the selected row(s),
/// while this source is synced with that row with one exception:
/// If the root row's algo has a spreader algo (possibly nested),
/// the synthetic security is taken instead of the root security.
/// This has the affect of passing the synthetic security to the spreader algo viewmodel,
/// which then uses the security to populate the synthetic params ui.
/// TODO (maybe): clean this design up; the different security sources make it precarious.
/// One idea is to pass a special data type to the algo vm (which contains the spreader
/// security and execution strategy), which is only observed by the spreader algo vm.
_selected_params_mon.subscribe_security().StartWith((SecurityImmutable?)null).CombineLatest(
_selected_params_mon.subscribe_spreader_security().StartWith((SecurityImmutable?)null),
(root_sec, spreader_sec) => (root_sec, spreader_sec))
.Select(tup => tup.spreader_sec ?? tup.root_sec)
.OnDispatcher()),
order_mod_vm,
attach_vm,
_show_fills.observe_current(true)
@@ -329,32 +344,32 @@ namespace mbts.spark {
}
void on_begin_attaching() {
Disposable.replace(ref attach_vm_hooks,
Disposable.Create(
connect_common_selected_params(attach_vm),
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
AlgoParamPermissions bool_to_perm(bool b) =>
b ? AlgoParamPermissions.ReadWrite : AlgoParamPermissions.None;
}),
// custom templates (created by user)
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_algo_def_vm))));
// vm factory
AlgoDefinitionVM mk_algo_def_vm(AlgoData fd) {
var vm = AlgoDefinitionVM.create(_model, fd, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
return vm;
}
Disposable.dispose(ref attach_vm_hooks);
attach_vm_hooks = Disposable.Create(
_selected_security_mon.SubscribeThrottled(_selection_change_throttle).to_security_md(bus).OnDispatcher().SubscribeEach(newval => {
attach_vm.security_md = newval;
if (newval.HasValue) {
return _model.subscribe_algos(newval.Value.security, true)
.OnDispatcher()
.Subscribe(t => ((IAlgoSelector)attach_vm.algo_vm).load_algo_data(t.state, null));
}
return null;
}),
connect_common_selected_params(attach_vm),
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
bool show_price, show_size;
if (algo_root == null) {
show_price = false;
show_size = false;
} else {
show_price = AttachConf.show_price(algo_root.order_type);
show_size = AttachConf.show_size(algo_root.order_type);
}
attach_vm.price_permissions = bool_to_perm(show_price);
attach_vm.size_permissions = bool_to_perm(show_size);
AlgoParamPermissions bool_to_perm(bool b) =>
b ? AlgoParamPermissions.ReadWrite : AlgoParamPermissions.None;
}));
}
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
@@ -365,11 +380,11 @@ namespace mbts.spark {
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
revert_selected_orders_cmd.RaiseCanExecuteChanged();
}),
_selected_params_mon.subscribe_security().to_security_md(bus).OnDispatcher().Subscribe(newval => order_mod_vm.security_md = newval),
connect_common_selected_params(order_mod_vm));
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
@@ -807,12 +822,11 @@ namespace mbts.spark {
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
SecurityMD? _security_md;
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
public OrderParamsVM(OrderBookVM owner, AlgoEditingPermissions init_permissions) {
_owner = owner;
algo_vm = AlgoDefinitionVM.create(
owner._model,
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
init_format: init_format,
editing_permissions: init_permissions);
_accounts = backing(
nameof(selected_account),
@@ -879,6 +893,7 @@ namespace mbts.spark {
public ReadOnlyObservableCollection<AccountTuple> accounts => _accounts.items;
public AccountTuple? selected_account { get => _accounts.selected_item; set => _accounts.selected_item = value; }
public SecurityImmutable? security => _owner._selected_params_mon.security;
/// <see cref="security_md"/> should be synced with the root of the selected row(s),
public SecurityMD? security_md {
get => _security_md;
set {
@@ -282,15 +282,18 @@ namespace bts.shell.pnl {
public readonly WorkingOrderData working;
public readonly double
cpp,
default_cpp,
bid_price,
ask_price,
last_price,
settlement_price;
public readonly double? override_cpp;
public PNLRowData(
PNLKey key,
double cpp = 0,
double default_cpp = 0,
double? override_cpp = null,
double bid_price = 0,
double ask_price = 0,
double last_price = 0,
@@ -305,7 +308,8 @@ namespace bts.shell.pnl {
QuantityConversionData? qty_conversion = null) {
//
this.key = key;
this.cpp = cpp;
this.default_cpp = default_cpp;
this.override_cpp = override_cpp;
this.bid_price = bid_price;
this.ask_price = ask_price;
this.last_price = last_price;
@@ -335,6 +339,9 @@ namespace bts.shell.pnl {
AskLTESettle = Ask | Settle | 1 << 7
}
public string symbol => security?.pnl_symbol() ?? string.Empty;
public double effective_cpp => override_cpp ?? default_cpp;
/// Market/Calc
public double mid_price => (bid_price + ask_price) / 2;
public double pnl_price {
@@ -374,26 +381,26 @@ namespace bts.shell.pnl {
}
}
/// Sod
public double sod_pnl => cpp *
public double sod_pnl => effective_cpp *
DoubleUtil.nan_to_zero_sum(
sod_long_avg_price_delta_raw * sod_long_pos,
sod_short_avg_price_delta_raw * sod_short_pos
);
// long
public double sod_long_pos => sod.long_pos;
public double sod_long_avg_price => cpp * sod.long_avg;
public double sod_long_avg_price => effective_cpp * sod.long_avg;
public double sod_long_avg_price_raw => sod.long_avg;
public double sod_long_avg_price_delta => cpp * sod_long_avg_price_delta_raw;
public double sod_long_avg_price_delta => effective_cpp * sod_long_avg_price_delta_raw;
public double sod_long_avg_price_delta_raw => sod.long_pos == 0 ? double.NaN : long_pnl_price - sod.long_avg;
// short
public double sod_short_pos => sod.short_pos;
public double sod_short_avg_price => cpp * sod.short_avg;
public double sod_short_avg_price => effective_cpp * sod.short_avg;
public double sod_short_avg_price_raw => sod.short_avg;
public double sod_short_avg_price_delta => cpp * sod_short_avg_price_delta_raw;
public double sod_short_avg_price_delta => effective_cpp * sod_short_avg_price_delta_raw;
public double sod_short_avg_price_delta_raw => sod.short_pos == 0 ? double.NaN : sod.short_avg - short_pnl_price;
// net
public double sod_net_pos => sod.net_pos;
public double sod_net_avg_price_delta => cpp * sod_net_avg_price_delta_raw;
public double sod_net_avg_price_delta => effective_cpp * sod_net_avg_price_delta_raw;
public double sod_net_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_long_avg_price_delta_raw, sod.long_pos,
@@ -404,27 +411,27 @@ namespace bts.shell.pnl {
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
/// Day
public double day_pnl => cpp *
public double day_pnl => effective_cpp *
DoubleUtil.nan_to_zero_sum(
day_long_avg_price_delta_raw * day_long_pos,
day_short_avg_price_delta_raw * day_short_pos
);
// long
public double day_long_pos => day.long_pos;
public double day_long_avg_price => cpp * day.long_avg;
public double day_long_avg_price => effective_cpp * day.long_avg;
public double day_long_avg_price_raw => day.long_avg;
public double day_long_avg_price_delta => cpp * day_long_avg_price_delta_raw;
public double day_long_avg_price_delta => effective_cpp * day_long_avg_price_delta_raw;
public double day_long_avg_price_delta_raw => day.long_pos == 0 ? double.NaN : long_pnl_price - day.long_avg;
// short
public double day_short_pos => day.short_pos;
public double day_short_avg_price => cpp * day.short_avg;
public double day_short_avg_price => effective_cpp * day.short_avg;
public double day_short_avg_price_raw => day.short_avg;
public double day_short_avg_price_delta => cpp * day_short_avg_price_delta_raw;
public double day_short_avg_price_delta => effective_cpp * day_short_avg_price_delta_raw;
public double day_short_avg_price_delta_raw => day.short_pos == 0 ? double.NaN : day.short_avg - short_pnl_price;
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
// net
public double day_net_pos => day.net_pos;
public double day_net_avg_price_delta => cpp * day_net_avg_price_delta_raw;
public double day_net_avg_price_delta => effective_cpp * day_net_avg_price_delta_raw;
public double day_net_avg_price_delta_raw =>
MathUtils.weighted_average(
day_long_avg_price_delta_raw, day.long_pos,
@@ -447,25 +454,25 @@ namespace bts.shell.pnl {
public double total_pnl => DoubleUtil.nan_to_zero_sum(sod_pnl, day_pnl);
// long
public double total_long_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, day.long_pos);
public double total_long_avg_price => cpp * total_long_avg_price_raw;
public double total_long_avg_price => effective_cpp * total_long_avg_price_raw;
public double total_long_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.long_avg, day.long_avg);
public double total_long_avg_price_delta => cpp * total_long_avg_price_delta_raw;
public double total_long_avg_price_delta => effective_cpp * total_long_avg_price_delta_raw;
public double total_long_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_long_avg_price_delta_raw, sod.long_pos,
day_long_avg_price_delta_raw, day.long_pos);
// short
public double total_short_pos => DoubleUtil.nan_to_zero_sum(sod.short_pos, day.short_pos);
public double total_short_avg_price => cpp * total_short_avg_price_raw;
public double total_short_avg_price => effective_cpp * total_short_avg_price_raw;
public double total_short_avg_price_raw => DoubleUtil.nan_to_zero_sum(sod.short_avg, day.short_avg);
public double total_short_avg_price_delta => cpp * total_short_avg_price_delta_raw;
public double total_short_avg_price_delta => effective_cpp * total_short_avg_price_delta_raw;
public double total_short_avg_price_delta_raw =>
MathUtils.weighted_average(
sod_short_avg_price_delta_raw, sod.short_pos,
day_short_avg_price_delta_raw, day.short_pos);
// net
public double total_net_pos => DoubleUtil.nan_to_zero_sum(sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos);
public double total_net_avg_price_delta => cpp * total_net_avg_price_delta_raw;
public double total_net_avg_price_delta => effective_cpp * total_net_avg_price_delta_raw;
public double total_net_avg_price_delta_raw =>
MathUtils.weighted_average(
total_long_avg_price_delta_raw, total_long_pos,
@@ -478,23 +485,23 @@ namespace bts.shell.pnl {
#region Working
// long
public double working_long_pos => working.long_data.working_total;
public double working_long_avg_price => cpp * working_long_avg_price_raw;
public double working_long_top_price => cpp * working_long_top_price_raw;
public double working_long_avg_price => effective_cpp * working_long_avg_price_raw;
public double working_long_top_price => effective_cpp * working_long_top_price_raw;
public double working_long_top_price_raw => working.top_bid;
public double working_long_avg_price_raw => working.long_data.avg_price;
public double working_long_avg_price_delta => cpp * working_long_avg_price_delta_raw;
public double working_long_avg_price_delta => effective_cpp * working_long_avg_price_delta_raw;
public double working_long_avg_price_delta_raw => working_long_pos == 0 ? double.NaN : long_pnl_price - working_long_avg_price_raw;
// short
public double working_short_pos => working.short_data.working_total;
public double working_short_avg_price => cpp * working_short_avg_price_raw;
public double working_short_top_price => cpp * working_short_top_price_raw;
public double working_short_avg_price => effective_cpp * working_short_avg_price_raw;
public double working_short_top_price => effective_cpp * working_short_top_price_raw;
public double working_short_top_price_raw => working.top_ask;
public double working_short_avg_price_raw => working.short_data.avg_price;
public double working_short_avg_price_delta => cpp * working_short_avg_price_delta_raw;
public double working_short_avg_price_delta => effective_cpp * working_short_avg_price_delta_raw;
public double working_short_avg_price_delta_raw => working_short_pos == 0 ? double.NaN : working_short_avg_price_raw - short_pnl_price;
// net
public double working_net_pos => working_long_pos - working_short_pos;
public double working_net_avg_price_delta => cpp * working_net_avg_price_delta_raw;
public double working_net_avg_price_delta => effective_cpp * working_net_avg_price_delta_raw;
public double working_net_avg_price_delta_raw =>
MathUtils.weighted_average(
working_long_avg_price_delta_raw, working_long_pos,
@@ -505,6 +512,24 @@ namespace bts.shell.pnl {
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
#endregion
public PNLRowData with(SecurityImmutable? security = null, string account_name = null, WorkingOrderData? working = null, PNLCalculationType? calc_type = null) =>
new PNLRowData(
key,
default_cpp,
override_cpp,
bid_price,
ask_price,
last_price,
settlement_price,
sod,
day,
raw_day,
working ?? this.working,
security ?? this.security,
account_name ?? this.account_name,
calc_type ?? this.calc_type,
qty_conversion: qty_conversion);
WaterfallPriceFlags _waterfall_flags() {
var flags = WaterfallPriceFlags.None;
if (!double.IsNaN(bid_price)) flags |= WaterfallPriceFlags.Bid;
@@ -546,7 +571,8 @@ namespace bts.shell.pnl {
public bool Equals(PNLRowData other) =>
key == other.key &&
cpp == other.cpp &&
default_cpp == other.default_cpp &&
override_cpp == other.override_cpp &&
security.HasValue == other.security.HasValue &&
account_name == other.account_name &&
sod == other.sod &&
@@ -574,9 +600,12 @@ namespace bts.shell.pnl {
}
static class PNLRowDataFactory {
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value, QuantityConversionData qty_conversion) t) =>
new PNLRowData(new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
t.value.CashValue,
public static PNLRowData create((SecurityImmutable? sec, PNLUnitImmutable value, QuantityConversionData qty_conversion) t) {
var (default_cpp, override_cpp) = t.value.pnl_cpp_tuple(t.sec);
return new PNLRowData(
new PNLKey(t.value.Pos.AccountId, t.value.Pos.SecurityId),
default_cpp,
override_cpp,
t.value.BidPrice,
t.value.AskPrice,
t.value.LastPrice,
@@ -588,25 +617,25 @@ namespace bts.shell.pnl {
t.sec,
t.value.Pos.Account,
qty_conversion: t.qty_conversion);
}
}
static class PNLDataExtensions {
// Note: PNLRowData is passed by ref because it is fairly large struct, and since it is a readonly struct, we know it cannot be modified (so might as well avoid excessive copying).
public static PNLRowData with(this PNLRowData pnl_data, SecurityImmutable? security = null, string account_name = null, WorkingOrderData? working = null, PNLCalculationType? calc_type = null) =>
new PNLRowData(
pnl_data.key,
pnl_data.cpp,
pnl_data.bid_price,
pnl_data.ask_price,
pnl_data.last_price,
pnl_data.settlement_price,
pnl_data.sod,
pnl_data.day,
pnl_data.raw_day,
working ?? pnl_data.working,
security ?? pnl_data.security,
account_name ?? pnl_data.account_name,
calc_type ?? pnl_data.calc_type,
qty_conversion: pnl_data.qty_conversion);
public static string pnl_symbol(this SecurityImmutable security) {
return bts.api.ExchangeSpecificUitlities.get_pnl_symbol(security);
}
// while override_cpp could use double.NaN to indicate the lack of an override,
// the nullable double makes it clear that it is an optional value.
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable? security) =>
security.HasValue ?
pnl_cpp_tuple(pnl_unit, security.Value) :
(pnl_unit.CashValue, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable security) {
// TODO: actual impl
double calced_pnl = bts.api.ExchangeSpecificUitlities.get_cpp(pnl_unit,security);
return (calced_pnl, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
}
}
}
@@ -1,4 +1,5 @@
using bts.api;
using bts.api.position;
using bts.collections;
using bts.settings;
using bts.shell.pnl;
@@ -11,6 +12,7 @@ using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.property_editor;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
@@ -22,6 +24,7 @@ using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
@@ -34,11 +37,13 @@ namespace bts.shell {
readonly UserSettingsHub _settings_hub;
readonly PNLConfig _pnl_conf;
readonly IShellWindowThreadIDProvider _thread_id_provider;
readonly IPositionModel _position_model;
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, _window_type) {
public PNLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model, IPositionModel position_model) : base(owner, _window_type) {
_bus = bus;
_orders_model = orders_model;
_settings_hub = settings_hub;
_position_model = position_model;
_pnl_conf = new PNLConfig(Configuration.get<bts.config.PNLOptions>());
_thread_id_provider = ShellWindowThreadIDFactory.create(_window_type, Configuration.get<bts.config.MultiThreadedWindows>().pnl);
_dispose = bus.subscribe(SparkTags.ShowPNLTag).OnDispatcher().Subscribe(_show);
@@ -105,6 +110,8 @@ namespace bts.shell {
menu_display: menu_display_vm.create());
return new PNLSaveState { use_defaults = false, settings = state };
};
IDisposable cpp_override_dialog = null;
IEnumerable<SparkCommand> commands = new[] {
new SparkCommandCommand("Collapse All", Icons.RightArrow, new DelegateCommand(() => vm.set_all_expanded(false))),
new SparkCommandCommand("Expand All", Icons.DownArrow, new DelegateCommand(() => vm.set_all_expanded(true))),
@@ -116,6 +123,26 @@ namespace bts.shell {
show_in_context_menu = _pnl_conf.show_pause_resume,
show_in_toolbar = _pnl_conf.show_pause_resume
},
new SparkCommandCommand("Override CPP...", Icons.CurrencyExchange, new DelegateCommand(() => {
Disposable.dispose(ref cpp_override_dialog);
var security_keys = vm.selected_rows.Select(r => r.security_key.value).Distinct().ToList();
double initial_cpp = vm.selected_rows.Select(r => r.cpp.value).Distinct().SafeSingleOrDefault();
override_cpp_async().ignore();
async Task override_cpp_async() {
try {
var cts = new CancellationTokenSource();
cpp_override_dialog = Disposable.Create(new Action(cts.Cancel));
var (cpp, committed) = await PropertyEditor.get_double_async("Override CPP", "value", cts.Token, initial_cpp);
if (committed && cpp.is_real_positive())
await _position_model.override_cpp_async(security_keys, cpp);
} catch (Exception ex) {
Log.Ui.warn($"Error overriding cpp: {ex}.");
}
}
})) {
show_in_context_menu = true,
show_in_toolbar = false
},
SparkCommands.window_settings_command(new DialogCommand(() => {
settings_manager.instance_settings = get_save_state().settings;
var dialog_vm = new ShellSettingsDialogVM<PNLSettings, PNLSettingsVM>(
@@ -190,6 +217,7 @@ namespace bts.shell {
cached_columns.RemoveRange(e.OldItems.OfType<DIYGrid.ColumnBase>());
}),
Disposable.wrap_collection(commands.OfType<IDisposable>()),
Disposable.Create(() => Disposable.dispose(ref cpp_override_dialog)),
menu_display_vm,
vm);
return (content, commands, get_save_state, dispose);
@@ -384,5 +412,17 @@ namespace bts.shell {
return false;
}
}
[Export(typeof(IPNLSettingsMigrationRule))]
class InitializeDataFormattingMigrationRule : IPNLSettingsMigrationRule {
public bool apply(PNLSettings settings, out PNLSettings new_settings) {
if (settings != null && settings.data_formatting == null) {
new_settings = settings.with(data_formatting: PNLSettings.Default.data_formatting);
return true;
}
new_settings = null;
return false;
}
}
}
}
@@ -204,11 +204,11 @@ namespace bts.shell.pnl {
// impl (add all)
bd.add_discrete_values(fs.account, d => d.key.account_id);
bd.add_discrete_values(fs.exchange, d => d.security.exchange_or_default());
bd.add_discrete_values(fs.symbol, d => d.security.symbol_or_default());
bd.add_discrete_values(fs.symbol, d => d.symbol);
bd.add_discrete_values(fs.security_type, d => d.security.HasValue ? d.security.Value.ContractCase : Security.ContractOneofCase.None);
bd.add_regex(fs.security, d => d.security.ToString());
bd.add_regex(fs.security_key, d => d.key.security_id.ToString());
bd.add_numeric(fs.cpp, d => d.cpp);
bd.add_numeric(fs.cpp, d => d.effective_cpp);
/// sod
bd.add_numeric(fs.sod_pnl, d => d.sod_pnl);
bd.add_numeric(fs.sod_long_pos, d => d.sod_long_pos);
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,52 @@
using bts.utils;
using mbts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
namespace bts.shell.pnl {
class PNLDataFormattingVM : ViewModelBase, IValueFactory<PNLDataFormatting> {
const uint _min_decimal_places = 1, _max_decimal_places = 6;
uint _market_price_decimal_places, _fill_price_decimal_places;
public uint market_price_decimal_places {
get => _market_price_decimal_places;
set => Set(ref _market_price_decimal_places, value);
}
public uint fill_price_decimal_places {
get => _fill_price_decimal_places;
set => Set(ref _fill_price_decimal_places, value);
}
public IEnumerable<uint> decimal_place_options {
get {
for (uint n = _min_decimal_places; n <= _max_decimal_places; n++)
yield return n;
}
}
public PNLDataFormatting create() =>
new PNLDataFormatting {
fill_price_display = new DoubleFormatting {
tick_factors = new mbts.user.settings.TickFactors(
Math.Pow(10, MathUtils.within_range(_min_decimal_places, _max_decimal_places, fill_price_decimal_places)),
1)
},
market_price_display = new DoubleFormatting {
tick_factors = new mbts.user.settings.TickFactors(
Math.Pow(10, MathUtils.within_range(_min_decimal_places, _max_decimal_places, market_price_decimal_places)),
1)
}
};
public void load_from(PNLDataFormatting value) {
if (value != null) {
if (value.market_price_display?.tick_factors != null) {
market_price_decimal_places = (uint)Math.Log10(value.market_price_display.tick_factors.tick_base);
}
if (value.fill_price_display?.tick_factors != null) {
fill_price_decimal_places = (uint)Math.Log10(value.fill_price_display.tick_factors.tick_base);
}
}
}
}
}
@@ -14,7 +14,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
@@ -52,6 +51,7 @@ namespace bts.shell.pnl {
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLDataFormattingVM data_formatting_vm { get; } = new PNLDataFormattingVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
@@ -63,6 +63,7 @@ namespace bts.shell.pnl {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
s.data_formatting = data_formatting_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
List<GridColumnSettings> new_cols = null;
IEnumerable<GridColumnSettings> cols = s.grid.columns;
@@ -136,6 +137,7 @@ namespace bts.shell.pnl {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
data_formatting_vm.load_from(value.data_formatting);
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
@@ -154,7 +156,7 @@ namespace bts.shell.pnl {
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
@@ -55,11 +55,11 @@ namespace bts.shell.pnl {
}
class PNLCellVM<TRow, TValue> : ViewModelBase, IObserver<PNLAnimation>, IPNLCellVM<TRow>, IComparable<PNLCellVM<TRow, TValue>>, IComparable {
readonly Func<TValue, string> _fmt;
readonly Func<TRow, TValue> _get;
readonly IEqualityComparer<TValue> _eq_comparer;
readonly IComparer<TValue> _comparer;
readonly PNLView.Columns _column;
Func<TValue, string> _fmt;
IComparer _value_comparer;
Color
_foreground = Colors.Black,
@@ -123,6 +123,15 @@ namespace bts.shell.pnl {
set => Set(ref _font_weight, value);
}
public Func<TValue, string> format {
get => _fmt;
set {
_fmt = value;
_text = null;
RaisePropertyChanged(nameof(text));
}
}
public void on_row(TRow row) =>
value = _get(row);
@@ -22,7 +22,13 @@ namespace bts.shell.pnl {
bool _refresh_normalized_data = true, _is_selected;
PNLNormalizedData _normalized_data;
public PNLRowVM(PNLRowData data, ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
public PNLRowVM(
PNLRowData data,
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
Func<double, string> fmt_fill_price,
Func<double, string> fmt_market_price) {
//
this.data = data;
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
_animator.on_row(data);
@@ -30,10 +36,10 @@ namespace bts.shell.pnl {
account_name_cell = _cell_map.add(ColumnKeys.Account, d => d.account_name);
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.security?.Symbol ?? string.Empty);
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
security_cell = _cell_map.add(ColumnKeys.Security, d => (d.security), null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
cpp = _cell_map.add(ColumnKeys.CPP, d => d.cpp);
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
// sod
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.display_sod_net_pos);
@@ -73,6 +79,7 @@ namespace bts.shell.pnl {
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
_cell_map.on_row(data);
_cell_map.set_appearances(_animator.cell_appearances);
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
@@ -185,6 +192,7 @@ namespace bts.shell.pnl {
public PNLCellVM<PNLRowData, double> pnl_price { get; }
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
public bool is_selected {
get => _is_selected;
set {
@@ -193,6 +201,35 @@ namespace bts.shell.pnl {
}
}
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
get {
yield return pnl_price;
yield return last_price;
yield return bid_price;
yield return mid_price;
yield return ask_price;
yield return settlement_price;
}
}
// includes sod, day, total, and working avg price columns
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
get {
// sod
yield return sod_long_avg_price;
yield return sod_short_avg_price;
// day
yield return day_long_avg_price;
yield return day_short_avg_price;
// total
yield return total_long_avg_price;
yield return total_short_avg_price;
// working
yield return working_long_avg_price;
yield return working_short_avg_price;
}
}
public void update(PNLRowData data) {
_refresh_normalized_data = true;
this.data = data;
@@ -201,9 +238,10 @@ namespace bts.shell.pnl {
_cell_map.set_appearances(_animator.cell_appearances);
}
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers) {
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
_animator.on_settings(defaults, triggers);
_cell_map.set_appearances(_animator.cell_appearances);
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
}
public bool Equals(PNLRowVM other) =>
@@ -214,5 +252,14 @@ namespace bts.shell.pnl {
public override string ToString() =>
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
foreach (var cell in _fill_price_cells) {
cell.format = fmt_fill_price;
}
foreach (var cell in _market_price_cells) {
cell.format = fmt_market_price;
}
}
}
}
@@ -41,6 +41,9 @@ namespace bts.shell.pnl {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly IObservableCollectionAddRemoveOnly<PNLRowVM> _selected_rows = new ObservableSortedCollection<PNLRowVM>(PNLRowVM.key_comparer);
/// treat <see cref="_rows_data_wrapper"/> and <see cref="_rows_key_wrapper"/>
/// as IReadOnlyLists - don't call any mutation methods.
/// The only reason they are ILists is for the binary_search methods.
readonly IList<PNLRowData> _rows_data_wrapper;
readonly IList<PNLKey> _rows_key_wrapper;
readonly DeferrableActionWrapper _refresh_filter;
@@ -51,6 +54,7 @@ namespace bts.shell.pnl {
IReadOnlyList<(ColumnKeys key, ListSortDirection direction)> _sorting_hierarchy;
Brush _selection_highlight;
Color _grid_background_color = Colors.Transparent;
Func<double, string> _fmt_fill_price_backing, _fmt_market_price_backing;
bool _is_paused;
// default appearances
ImmutableDictionary<ColumnKeys, PNLCellAppearance>
@@ -74,7 +78,7 @@ namespace bts.shell.pnl {
PNLVM(IBus bus, OrdersModel orders_model, PNLSettings settings, IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) {
_bus = bus;
_orders_model = orders_model;
_rows_data_wrapper = rows.WrapSelector(r => r.data, d => new PNLRowVM(d, _default_appearances, _triggers));
_rows_data_wrapper = rows.WrapSelector(r => r.data, null);
_rows_key_wrapper = rows.WrapSelector(r => r.key, null);
_rows_view = new PNLRowCollectionView(rows, _default_group_appearances, _group_triggers);
_calculation_type = backing(nameof(calculation_type), PNLCalculationType.Waterfall);
@@ -88,6 +92,12 @@ namespace bts.shell.pnl {
});
// filters
account_filter = DiscreteValuesFilterVM.Common.account<PNLRowData>(orders_model, r => new AccountTuple(r.key.account_id, r.account_name), nameof(PNLRowVM.account_name));
symbol_filter = DiscreteValuesFilterVM.CreateTableUpdate<PNLRowData, string, string>(
r => r.symbol,
sym => sym,
out var symbol_filter_items_observer,
out var symbol_filter_items_view);
symbol_filter_items_view.CustomSort = Comparer<string>.Default.to_safe().to_non_generic();
_refresh_filter = new DeferrableActionWrapper(() => _filter_changed?.Invoke(get_filters_states().build_predicate()));
var filter_src = Observable.Create<Func<PNLRowData, bool>>(o => {
o.OnNext(get_filters_states().build_predicate());
@@ -108,9 +118,11 @@ namespace bts.shell.pnl {
).FilterWithRaw(filter_src, PNLRowData.key_fn, null)
.OnDispatcher()
.Subscribe(_update),
_symbols_source(pnl_source).OnDispatcher().Subscribe(symbol_filter_items_observer),
Disposable.Create(() => {
foreach (var f in _all_filters)
f.filter_changed -= _refresh_filter.execute;
_all_filters.OfType<IDisposable>().DisposeAll();
rows.DisposeAll();
_rows_view.Groups.OfTypeSafe<IDisposable>().DisposeAll();
}));
@@ -187,14 +199,14 @@ namespace bts.shell.pnl {
/// security
public IDataGridColumnPersistentFilter<PNLRowData, uint[]> account_filter { get; }
public IDataGridColumnPersistentFilter<PNLRowData, Venue[]> exchange_filter { get; } = DiscreteValuesFilterVM.Common.exchange<PNLRowData>(r => r.security.exchange_or_default(), nameof(PNLRowVM.exchange));
public IDataGridColumnPersistentFilter<PNLRowData, string[]> symbol_filter { get; } = DiscreteValuesFilterVM.Common.symbol<PNLRowData>(r => r.security.symbol_or_default(), nameof(PNLRowVM.symbol));
public IDataGridColumnPersistentFilter<PNLRowData, string[]> symbol_filter { get; }
public IDataGridColumnPersistentFilter<PNLRowData, string> security_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.security?.ToString(), nameof(PNLRowData.security));
public IDataGridColumnPersistentFilter<PNLRowData, string> security_key_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.key.security_id.ToString());
public IDataGridColumnPersistentFilter<PNLRowData, SecurityType[]> security_type_filter { get; } = DiscreteValuesFilterVM.Create<PNLRowData, SecurityType>(
EnumHelper.get_values_except(SecurityType.Option, SecurityType.Synthetic).ToArray(),
r => r.security?.ContractCase ?? SecurityType.None,
nameof(PNLRowData.security));
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.cpp);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.effective_cpp);
/// sod
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_pnl);
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.long_pos);
@@ -238,6 +250,15 @@ namespace bts.shell.pnl {
IEnumerable<IDataGridColumnFilter<PNLRowData>> _all_filters =>
_filter_getters.Select(get => get(this));
Func<double, string> _fmt_fill_price {
get => _fmt_fill_price_backing ?? FormattingDelegates.Price.Value;
set => _fmt_fill_price_backing = value;
}
Func<double, string> _fmt_market_price {
get => _fmt_market_price_backing ?? FormattingDelegates.Price.Value;
set => _fmt_market_price_backing = value;
}
public static IComparer<PNLRowVM> get_column_comparer(ColumnKeys column) =>
_get_column_comparer(column).row_comparer;
@@ -360,8 +381,10 @@ namespace bts.shell.pnl {
_triggers = settings.triggers();
_group_triggers = settings.group_triggers();
_header_triggers = settings.header_triggers();
_fmt_fill_price = settings.data_formatting?.fill_price_display?.get_formatting_delegate();
_fmt_market_price = settings.data_formatting?.market_price_display?.get_formatting_delegate();
foreach (var r in rows)
r.on_settings(_default_appearances, _triggers);
r.on_settings(_default_appearances, _triggers, _fmt_fill_price, _fmt_market_price);
total_aggregate.on_settings(settings.header_background_color, _default_header_appearances, _header_triggers);
selected_aggregate.on_settings(settings.header_background_color,_default_header_appearances, _header_triggers);
void map_appearances(ref ImmutableDictionary<ColumnKeys, PNLCellAppearance> from, IEnumerable<GridColumnSettings> col_settings, PNLCellAppearance fallback_appearance) {
@@ -542,6 +565,16 @@ namespace bts.shell.pnl {
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
}
// combines the known system symbols
static IObservable<TableUpdate<string, string>> _symbols_source(IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) =>
pnl_source
// ensure this fires once immediatey, because the known symbols are appended to the first update
// (observers shouldn't have to wait for the pnl source to tick to see them).
.StartWith(TableUpdate<PNLRowData, PNLKey>.mk_empty(PNLRowData.key_fn))
.distinct_on(PNLRowData.key_fn, r => r.symbol, s => s)
.Filter(sym => !string.IsNullOrEmpty(sym))
.AppendTable(mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList());
#region Fills Popup Utils
static void _append_key_filters(fills.FillsFilterStates f, ColumnKeys key) {
switch (key) {
@@ -734,7 +767,7 @@ namespace bts.shell.pnl {
}
}
foreach (var v in filtered.added) {
var r = new PNLRowVM(v.with(calc_type: calculation_type), _default_appearances, _triggers);
var r = new PNLRowVM(v.with(calc_type: calculation_type), _default_appearances, _triggers, _fmt_fill_price, _fmt_market_price);
rows.InsertSorted(r, PNLRowVM.key_comparer);
total_pnl += r.normalized_data;
}
@@ -619,7 +619,7 @@
<!--Symbol-->
<pnl:PNLColumn
c:DataGridFilterProperties.Filter="{Binding DataContext.symbol_filter, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DiscreteValues}}"
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DiscreteValuesCollectionView}}"
c:DataGridFilterProperties.FilterActive="{Binding DataContext.symbol_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.symbol_filter.open, Source={u:DirectReference _grid}}"
key="Symbol"
@@ -1,4 +1,5 @@
using bts.api;
using bts.api.position;
using bts.shell.fills;
using bts.shell.manual_fills;
using bts.spark;
@@ -38,6 +39,7 @@ namespace bts.shell {
UserSettingsHub settings_hub,
OrdersModel orders_model,
md.MBOService mbo,
IPositionModel position_model,
ErrorReporter error_reporter) {
/// Unlike our <see cref="SparkCommand"/> members, <see cref="IShellWindowFactory"/> members must be instantiated immediately,
/// as they are responsible for launching windows in response to a SparkTag being published to the bus.
@@ -47,7 +49,7 @@ namespace bts.shell {
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
_pnl_conf = new PNLConfig(mbts.config.Configuration.get<bts.config.PNLOptions>());
if (_pnl_conf.show_new_pnl)
_plwf = new PNLWindowFactory(this, bus, settings_hub, orders_model);
_plwf = new PNLWindowFactory(this, bus, settings_hub, orders_model, position_model);
if (_pnl_conf.show_old_pnl)
_old_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
+26 -273
View File
@@ -1,5 +1,6 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.spark;
@@ -26,11 +27,13 @@ namespace mbts.spark.utilities {
public readonly Func<CustomOrderDefinition, CustomOrderDefinition> get_child;
public readonly Action<CustomOrderDefinition, CustomOrderDefinition> assign_child;
public ChildAlgoData(string name, Func<IMessage, CustomOrderDefinition> get_child_from_impl,
Action<IMessage, CustomOrderDefinition> assign_child_to_impl,
Func<CustomOrderDefinition, CustomOrderDefinition> get_child,
Action<CustomOrderDefinition, CustomOrderDefinition> assign_child)
{
public ChildAlgoData(
string name,
Func<IMessage, CustomOrderDefinition> get_child_from_impl,
Action<IMessage, CustomOrderDefinition> assign_child_to_impl,
Func<CustomOrderDefinition, CustomOrderDefinition> get_child,
Action<CustomOrderDefinition, CustomOrderDefinition> assign_child) {
//
this.name = name;
this.get_child = get_child;
this.assign_child = assign_child;
@@ -46,13 +49,19 @@ namespace mbts.spark.utilities {
static readonly OrderMetadata _default_order_metadata = new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.default_order };
readonly AdminModel _admin_model;
readonly DynamicOrderHelper _dynamic_helper;
readonly IAlgoProvider _security_algo_provider;
readonly IReadOnlyDictionary<OneOf<CustomOrderDefinition.CustomOneofCase, string>, string> _algo_abbreviation_index;
readonly IReadOnlyDictionary<string, DynamicOrderDescriptor> _dynamic_descriptors;
IDisposable _dispose;
[ImportingConstructor]
public OrderFactory(AdminModel admin_model, DynamicOrderHelper dynamic_helper) {
public OrderFactory(OrderService service, AdminModel admin_model, DynamicOrderHelper dynamic_helper, IAlgoProvider security_algo_provider) {
_admin_model = admin_model;
_dynamic_helper = dynamic_helper;
_dynamic_descriptors = service.get_registered_dynamic_order_types(new VoidMessage())
.DynamicOrderDesc
.ToDictionary(d => d.DynamicOrderType);
_security_algo_provider = security_algo_provider;
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
// add standard types
algo_name_index.AddRange(
@@ -86,66 +95,13 @@ namespace mbts.spark.utilities {
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types() =>
OrderDataHelper.exchange_order_types;
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security) =>
security.HasValue ? default_order_types(security.Value) : OrderDataHelper.exchange_order_types;
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable security) {
switch (security.ContractCase) {
case Security.ContractOneofCase.Synthetic: return OrderDataHelper.spreader_order_types;
default: return OrderDataHelper.exchange_order_types;
}
}
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child = false) =>
security.HasValue ? default_order_types(security.Value, is_child) : default_order_types();
public IEnumerable<TimeInForce> default_time_in_forces() =>
EnumHelper.get_values_except(TimeInForce.Undefined);
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child = false) =>
default_time_in_forces();
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, CustomOrderDefinition.CustomOneofCase order_type, bool is_child = false) {
var tifs = default_time_in_forces(security, is_child);
var tifs = EnumHelper.get_values_except(TimeInForce.Undefined);
if (order_type != CustomOrderDefinition.CustomOneofCase.Limit) {
tifs = tifs.Except(new[] { TimeInForce.Ioc });
}
return tifs;
}
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child = false) =>
security.HasValue ? default_time_in_forces(security.Value, is_child) : default_time_in_forces();
public LadderSettings default_ladder_settings(SecurityImmutable security) {
var settings = LadderSettings.Default;
if (security.ContractCase == Security.ContractOneofCase.Synthetic)
settings.default_order_type = CustomOrderDefinition.CustomOneofCase.Spreader;
return settings;
}
public Dictionary<AlgoKey, AlgoData> default_algos() =>
default_algos((SecurityImmutable?)null);
public Dictionary<AlgoKey, AlgoData> default_algos(SecurityImmutable? security) =>
default_algos(default_order_types(security));
public Dictionary<AlgoKey, AlgoData> default_algos(AlgoFormats algo_format) =>
default_algos(OrderDataHelper.get_format_algo_types(algo_format));
public Dictionary<AlgoKey, AlgoData> default_algos(IEnumerable<CustomOrderDefinition.CustomOneofCase> types) {
var def_map = new Dictionary<AlgoKey, AlgoData>();
foreach (var ot in types.Except(new[] { CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite })) {
var data = AlgoData.from_custom_def(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(ot));
def_map[data.key] = data;
}
var dynamic_defs = new List<CustomOrderDefinitionImmutable>();
_dynamic_helper.add_dynamic_order_defs(dynamic_defs, custom_order);
foreach (var dd in dynamic_defs) {
var data = AlgoData.from_custom_def(dd);
def_map[data.key] = data;
}
return def_map;
}
#endregion
public OrderDefinitionImmutable order(SecurityImmutable security) =>
@@ -156,7 +112,7 @@ namespace mbts.spark.utilities {
SecurityKey = security.Id,
Side = Side.Buy,
TimeInForce = TimeInForce.Day,
CustomDef = custom_order(),
CustomDef = OrderDataHelper.Defaults.CustomOrderDefinition(),
CtiCode = CTICode.CtiCode1,
CustomerOrFirm = CustomerOrFirm.Firm,
// this will be the tag 50 on CME orders
@@ -173,9 +129,6 @@ namespace mbts.spark.utilities {
return rv;
}
public string abbreviate_algo(CustomOrderDefinitionImmutable defi) =>
abbreviate_algo(_simple_algo_id(defi));
public string abbreviate_algo(OneOf<CustomOrderDefinition.CustomOneofCase, string> id) {
if (id == null)
throw new ArgumentNullException(nameof(id));
@@ -187,26 +140,6 @@ namespace mbts.spark.utilities {
}
// mutable definitions
public CustomOrderDefinition custom_order() => OrderDataHelper.Defaults.CustomOrderDefinition();
public MarketOrderDefinition market_order() => OrderDataHelper.Defaults.MarketOrderDefinition();
public LimitOrderDefinition limit_order() => OrderDataHelper.Defaults.LimitOrderDefinition();
public StopOrderDefinition stop_order() => OrderDataHelper.Defaults.StopOrderDefinition();
public StopLimitOrderDefinition stop_limit_order() => OrderDataHelper.Defaults.StopLimitOrderDefinition();
public GLBXIcebergOrderDefinition glbx_iceberg_order() => OrderDataHelper.Defaults.GLBXIcebergOrderDefinition();
public IcebergOrderDefinition iceberg_order() => OrderDataHelper.Defaults.IcebergOrderDefinition();
public TimeBoxOrderDefinition time_box_order() => OrderDataHelper.Defaults.TimeBoxOrderDefinition();
public TickOrderDefinition tick_order() => OrderDataHelper.Defaults.TickOrderDefinition();
public LeanOrderDefinition lean_order() => OrderDataHelper.Defaults.LeanOrderDefinition();
public JoinOrderDefinition join_order() => OrderDataHelper.Defaults.JoinOrderDefinition();
public TopOrderDefinition top_order() => OrderDataHelper.Defaults.TopOrderDefinition();
public CoverOrderDefinition cover_order() => OrderDataHelper.Defaults.CoverOrderDefinition();
public OCOOrderDefinition oco_order() => OrderDataHelper.Defaults.OCOOrderDefinition();
public PricerOrderDefinition pricer_order() => OrderDataHelper.Defaults.PricerOrderDefinition();
public OffsetOrderDefinition offset_order() => OrderDataHelper.Defaults.OffsetOrderDefinition();
public TriggerOrderDefinition trigger_order() => OrderDataHelper.Defaults.TriggerOrderDefinition();
public DynamicOrderDefinition dynamic_order() => OrderDataHelper.Defaults.DynamicOrderDefinition();
public SpreaderOrderDefinition spreader_order() => OrderDataHelper.Defaults.SpreaderOrderDefinition();
public SplitterOrderDefinition splitter_order() => OrderDataHelper.Defaults.SplitterOrderDefinition();
public SpreaderOrderDefinition spreader_order(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
new SpreaderOrderDefinition {
Security = security.CloneToMutable(),
@@ -214,40 +147,8 @@ namespace mbts.spark.utilities {
};
// immutable definitions
public CustomOrderDefinitionImmutable custom_orderi(CustomOrderDefinition.CustomOneofCase order_type) => new CustomOrderDefinitionImmutable(custom_order(order_type));
public SpreaderOrderDefinitionImmutable spreader_orderi(SecurityImmutable security, ExecutionStrategyImmutable strategy) => new SpreaderOrderDefinitionImmutable(spreader_order(security, strategy));
public CustomOrderDefinition custom_order(CustomOrderDefinition.CustomOneofCase order_type) {
var rv = new CustomOrderDefinition();
switch (order_type) {
case CustomOrderDefinition.CustomOneofCase.Market: rv.Market = market_order(); break;
case CustomOrderDefinition.CustomOneofCase.Limit: rv.Limit = limit_order(); break;
case CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = stop_order(); break;
case CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = stop_limit_order(); break;
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = glbx_iceberg_order(); break;
case CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = iceberg_order(); break;
case CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = time_box_order(); break;
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
case CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = lean_order(); break;
case CustomOrderDefinition.CustomOneofCase.Join: rv.Join = join_order(); break;
case CustomOrderDefinition.CustomOneofCase.Top: rv.Top = top_order(); break;
case CustomOrderDefinition.CustomOneofCase.Cover: rv.Cover = cover_order(); break;
case CustomOrderDefinition.CustomOneofCase.Oco: rv.Oco = oco_order(); break;
case CustomOrderDefinition.CustomOneofCase.Pricer: rv.Pricer = pricer_order(); break;
case CustomOrderDefinition.CustomOneofCase.Offset: rv.Offset = offset_order(); break;
case CustomOrderDefinition.CustomOneofCase.Trigger: rv.Trigger = trigger_order(); break;
case CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = spreader_order(); break;
case CustomOrderDefinition.CustomOneofCase.Splitter: rv.Splitter = splitter_order(); break;
case CustomOrderDefinition.CustomOneofCase.Dynamic: rv.Dynamic = dynamic_order(); break;
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
default:
OrderTypeVMFactory.GenericOrderFactory.setup_generic_custom_order(rv, order_type);
break;
}
return rv;
}
public OrderMetadata get_metadata(IMessage order_type_def) => get_metadata(OrderDataHelper.get_order_type(order_type_def));
public OrderMetadata get_metadata(OrderRecordImmutable order) => get_metadata(order.Def.CustomDef.CustomCase);
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) {
switch (order_type) {
@@ -278,14 +179,6 @@ namespace mbts.spark.utilities {
public void Dispose() =>
Disposable.dispose(ref _dispose);
static OneOf<CustomOrderDefinition.CustomOneofCase, string> _simple_algo_id(CustomOrderDefinitionImmutable defi) {
switch (defi.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Dynamic:
return new OneOf<CustomOrderDefinition.CustomOneofCase, string>(defi.Dynamic.DynamicOrderType);
default: return new OneOf<CustomOrderDefinition.CustomOneofCase, string>(defi.CustomCase);
}
}
}
static class OrderDataHelper {
@@ -301,8 +194,6 @@ namespace mbts.spark.utilities {
}
}
public static OneofDescriptor oneof_descriptor => _oneof_desc;
const double _price_error_margin = 0.0000001;
const decimal _price_error_margin_decimal = 0.0000001m;
@@ -320,10 +211,10 @@ namespace mbts.spark.utilities {
});
public static readonly IComparer<decimal> PriceComparerDecimal = Comparer<decimal>.Create((a, b) => {
decimal diff = a - b;
if (diff <= _price_error_margin_decimal && diff >= -_price_error_margin_decimal)
return 0;
return diff > 0 ? 1 : -1;
decimal diff = a - b;
if (diff <= _price_error_margin_decimal && diff >= -_price_error_margin_decimal)
return 0;
return diff > 0 ? 1 : -1;
});
const string _primary_child_field = "child";
@@ -378,7 +269,7 @@ namespace mbts.spark.utilities {
switch (cd.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: studio = cd.Composite.AlgoTemplateKey; break;
case CustomOrderDefinition.CustomOneofCase.Dynamic: dyn = cd.Dynamic.DynamicOrderType; break;
case CustomOrderDefinition.CustomOneofCase.Spreader: spreader_sec = cd.Spreader.Security.Id; break;
case CustomOrderDefinition.CustomOneofCase.Spreader: spreader_sec = cd.Spreader.Security?.Id ?? 0; break;
}
yield return (cd.CustomCase, dyn, studio, spreader_sec);
}
@@ -466,22 +357,6 @@ namespace mbts.spark.utilities {
child_data.TryGetFirst(out best_match) ? best_match : (ChildAlgoData?)null;
}
public static CustomOrderDefinition create_algo(CustomOrderDefinition.CustomOneofCase algo_type) {
var algo = new CustomOrderDefinition();
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData desc))
desc.type_case_descriptor.Accessor.SetValue(algo, ProtoHelper.mutable_factory(ProtoHelper.get_field_type(desc.type_case_descriptor)));
return algo;
}
public static bool append_algo_node(CustomOrderDefinition target, CustomOrderDefinition.CustomOneofCase algo_type) {
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out var desc)) {
var node = ProtoHelper.mutable_factory(ProtoHelper.get_field_type(desc.type_case_descriptor));
desc.type_case_descriptor.Accessor.SetValue(target, node);
return true;
}
return false;
}
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinition.CustomOneofCase algo_type) =>
_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData d) ? d.child_algo_data : Array.Empty<ChildAlgoData>();
#endregion
@@ -535,122 +410,10 @@ namespace mbts.spark.utilities {
}
}
public static AlgoFormats get_algo_format(Security.ContractOneofCase? security_type) =>
security_type.HasValue ? get_algo_format(security_type.Value) : AlgoFormats.Exchange;
public static AlgoFormats get_algo_format(Security.ContractOneofCase security_type) =>
security_type == Security.ContractOneofCase.Synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange;
public static HashSet<CustomOrderDefinition.CustomOneofCase> get_format_algo_types(AlgoFormats f) {
var rv = new HashSet<CustomOrderDefinition.CustomOneofCase>();
if ((f & AlgoFormats.Exchange) == AlgoFormats.Exchange) {
rv.AddRange(exchange_order_types);
if ((f & AlgoFormats.Spreader) == AlgoFormats.Spreader) {
rv.IntersectWith(spreader_order_types);
}
} else if ((f & AlgoFormats.Spreader) == AlgoFormats.Spreader) {
rv.AddRange(spreader_order_types);
}
if ((f & AlgoFormats.Attachable) == AlgoFormats.Attachable)
rv.AddRange(attachable_order_types);
if ((f & AlgoFormats.DefaultPlaceholder) == AlgoFormats.DefaultPlaceholder)
rv.Add(CustomOrderDefinition.CustomOneofCase.None);
return rv;
}
public static CustomOrderDefinition.CustomOneofCase get_default_algo(AlgoFormats f) {
switch (f) {
case AlgoFormats.Exchange: return CustomOrderDefinition.CustomOneofCase.Limit;
case AlgoFormats.Spreader: return CustomOrderDefinition.CustomOneofCase.Spreader;
default: return CustomOrderDefinition.CustomOneofCase.None;
}
}
public static IReadOnlyDictionary<AlgoKey, AlgoData> get_format_algo_data(AlgoFormats f) {
var algo_types = get_format_algo_types(f);
var algo_dict = algo_types.Select(at => AlgoData.from_custom_def(Defaults.CustomOrderDefinitionImmutable(at))).ToDictionaryCrushed(a => a.key);
return algo_dict;
}
public static HashSet<CustomOrderDefinition.CustomOneofCase> get_algo_types_present(CustomOrderDefinitionImmutable def) {
return new HashSet<CustomOrderDefinition.CustomOneofCase>(impl(def.CloneToMutable()));
// hide mutable-def implementation
IEnumerable<CustomOrderDefinition.CustomOneofCase> impl(CustomOrderDefinition mdef) {
yield return mdef.CustomCase;
foreach (var sd in get_child_algo_data(mdef)) {
var child = sd.get_child(mdef);
if (child != null) {
yield return child.CustomCase;
foreach (var t in impl(child))
yield return t;
}
}
}
}
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type, bool fit_all_to_spreader = false) {
var mdef = defi.CloneToMutable();
public static CustomOrderDefinition.CustomOneofCase get_default_algo(Security.ContractOneofCase security_type) {
switch (security_type) {
case Security.ContractOneofCase.Synthetic:
mdef = spreader_impl(mdef);
break;
default:
mdef = outright_impl(mdef);
break;
}
if (mdef == null)
return null;
return new CustomOrderDefinitionImmutable(mdef);
// local methods
CustomOrderDefinition spreader_impl(CustomOrderDefinition def) {
if (def == null) {
def = new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() };
} else {
switch (def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Limit:
case CustomOrderDefinition.CustomOneofCase.None:
def = new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() };
break;
case CustomOrderDefinition.CustomOneofCase.Stop:
case CustomOrderDefinition.CustomOneofCase.StopLimit:
case CustomOrderDefinition.CustomOneofCase.Market:
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg:
def = fit_all_to_spreader ? new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() } : null;
break;
default:
var desc_data = _algo_type_case_descriptor_data[def.CustomCase];
foreach (var cad in desc_data.child_algo_data) {
var child = spreader_impl(cad.get_child(def));
if (child == null)
return null;
var type_def = (IMessage)desc_data.type_case_descriptor.Accessor.GetValue(def);
cad.assign_child_to_impl(type_def, child);
}
break;
}
}
return def;
}
CustomOrderDefinition outright_impl(CustomOrderDefinition def) {
if (def == null) {
def = new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
} else {
switch (def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Spreader:
case CustomOrderDefinition.CustomOneofCase.None:
def = new CustomOrderDefinition { Limit = new LimitOrderDefinition() };
break;
default:
foreach (var cad in get_child_algo_data(def)) {
var child = outright_impl(cad.get_child(def));
if (child == null)
return null;
cad.assign_child(def, child);
}
break;
}
}
return def;
case Security.ContractOneofCase.Synthetic: return CustomOrderDefinition.CustomOneofCase.Spreader;
default: return CustomOrderDefinition.CustomOneofCase.Limit;
}
}
@@ -678,7 +441,7 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Iceberg,
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Splitter,
CustomOrderDefinition.CustomOneofCase.Scratch,
@@ -764,12 +527,6 @@ namespace mbts.spark.utilities {
public static bool equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) => custom_order_defi_comparer.Equals(a, b);
public static bool structure_equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) {
return impl(a.CloneToMutable(), b.CloneToMutable());
bool impl(CustomOrderDefinition ma, CustomOrderDefinition mb) =>
ma.CustomCase == mb.CustomCase && get_child_algo_data(ma).All(cad => impl(cad.get_child(ma), cad.get_child(mb)));
}
public static bool iceberg_equals(IcebergOrderDefinitionImmutable a, IcebergOrderDefinitionImmutable b) =>
a.ShowQuantity.Equals(b.ShowQuantity) && a.HasChild ? b.HasChild && equals(a.Child, b.Child) : !b.HasChild;
@@ -1031,7 +788,6 @@ namespace mbts.spark.utilities {
}
};
public static OrderDefinitionImmutable OrderDefinitionImmutable { get; } = new OrderDefinitionImmutable(OrderDefinition);
public static OrderRecordImmutable OrderRecordImmutable { get; } = new OrderRecordImmutable(OrderRecord);
public static CustomOrderDefinition CustomOrderDefinition() => new CustomOrderDefinition { Limit = LimitOrderDefinition() };
@@ -1084,9 +840,6 @@ namespace mbts.spark.utilities {
public static TriggerOrderDefinition TriggerOrderDefinition() => new TriggerOrderDefinition { Child = CustomOrderDefinition(), TriggerCondition = new TriggerCondition(), TriggerAction = new TriggerAction(), };
public static DynamicOrderDefinition DynamicOrderDefinition() => new DynamicOrderDefinition();
public static SpreaderOrderDefinition SpreaderOrderDefinition() => new SpreaderOrderDefinition { ExecutionStrategy = new ExecutionStrategy() };
public static SplitterOrderDefinition SplitterOrderDefinition() => new SplitterOrderDefinition();
}
}
}
@@ -82,13 +82,8 @@ namespace mbts.spark {
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic(),
IsLiveSorting = true
};
algo_definitions_view.Filter = d => {
if (d.key.mode == AlgoKey.Mode.Persisted && _algo_metadata.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden)
return false;
var sec_fmt = OrderDataHelper.get_algo_format(security.ContractCase);
return (d.selected_format & sec_fmt) == sec_fmt;
};
_algo_definitions_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionVM.selected_format));
algo_definitions_view.Filter = d =>
!(d.key.mode == AlgoKey.Mode.Persisted && _algo_metadata.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden);
_algo_definitions_view.LiveSortingProperties.Add(nameof(AlgoDefinitionVM.group));
_algo_definitions_view.LiveSortingProperties.Add(nameof(AlgoDefinitionVM.name));
algo_definitions_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(AlgoDefinitionVM.group)));
@@ -167,33 +162,27 @@ namespace mbts.spark {
undo_definition_observer,
redo_definition_observer,
revert_definition_observer),
security_md_source.Select(smd => model.factory.default_algos(smd.security)).Table(s => s.key).Subscribe(factory_defs => {
using (_selected_algo_definition.begin_update())
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, factory_defs, fd => {
var vm = AlgoDefinitionVM.create(_model, fd, init_format: OrderDataHelper.get_algo_format(security.ContractCase), editing_permissions: AlgoEditingPermissions.AllowEditValues);
vm.selected_format = OrderDataHelper.get_algo_format(security.ContractCase);
return vm;
});
}),
model.algo_model.definitions.Subscribe(_definitions_buffer),
_definitions_buffer.OnDispatcher().Subscribe(u => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, u, def_data => {
var def_vm = AlgoDefinitionVM.create(model, def_data, OrderDataHelper.get_algo_format(security.ContractCase), AlgoEditingPermissions.AllowEditValues);
def_vm.selected_format = OrderDataHelper.get_algo_format(security.ContractCase);
def_vm.favorite_index = _get_favorite_algo_index(def_vm.key);
return def_vm;
});
security_source.SelectManyInSeries(model.subscribe_algo_meta_tuples)
.OnDispatcher()
.Subscribe(table => {
using (_selected_algo_definition.begin_update()) {
foreach (var (algo, meta) in table.removed)
_algo_definitions.Remove(algo.key);
foreach (var (algo, meta) in table.changed) {
var vm = _algo_definitions[algo.key];
vm.load(algo);
vm.favorite_index = _get_favorite_algo_index(vm.key);
}
foreach (var (algo, meta) in table.added) {
var vm = AlgoDefinitionVM.create(_model, algo, AlgoEditingPermissions.AllowEditValues);
vm.favorite_index = _get_favorite_algo_index(vm.key);
_algo_definitions.Add(vm);
}
}
}),
model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(_on_template_metadata),
ObservableAutoWrapper.connect_item_hooks(algo_definitions, vm =>
Disposable.Create(
vm.connect_data(nullable_security_md_source),
security_source.Subscribe(s => {
var fmt = OrderDataHelper.get_algo_format(s.ContractCase);
foreach (var d in algo_definitions)
d.selected_format = fmt;
}),
vm),
Disposable.Create(vm.connect_data(nullable_security_md_source), vm),
true)) {
() => Disposable.dispose(ref _algo_param_rows_hook)
};
@@ -325,13 +314,11 @@ namespace mbts.spark {
public bool open_algo_popup_on_select { get => _open_algo_popup_on_select; set => Set(ref _open_algo_popup_on_select, value); }
// commands
public ICommand save_definition_cmd { get; }
public ICommand save_template_cmd { get; }
public ICommand revert_definition_cmd { get; }
public ICommand set_default_algo_params_cmd { get; }
public ICommand revert_algo_params_cmd { get; }
public ICommand undo_definition_cmd { get; }
public ICommand redo_definition_cmd { get; }
public ICommand remove_template_cmd { get; }
public ICommand toggle_is_favorite_cmd { get; }
public IObservable<AccountTuple?> observe_account(bool start_immediately = false) =>
@@ -396,4 +383,4 @@ namespace mbts.spark {
algo_definitions_view.Refresh();
}
}
}
}
@@ -4,9 +4,7 @@ using mbts.ui;
using System;
using System.Collections.Specialized;
using System.Reactive.Linq;
using System.Threading.Tasks;
using AlgoDef = bts.api.orderman.CustomOrderDefinition;
using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
using CompositeDef = bts.api.orderman.CompositeOrderDefinition;
using CompositeDefi = bts.api.orderman.CompositeOrderDefinitionImmutable;
@@ -21,6 +19,7 @@ namespace mbts.spark {
: base(id, display_name, editing_permissions) { }
public override string abbreviation => _studio_meta?.abbreviation ?? base.abbreviation;
public override AlgoDisplayGroup group => AlgoDisplayGroup.Studio;
public override OrderMetadata meta {
get {
var m = new OrderMetadata { KeyBinding = string.Empty, NoProps = false };
@@ -66,4 +65,4 @@ namespace mbts.spark {
protected override void validate() => base.validate();
}
}
}
}
@@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using AlgoDef = bts.api.orderman.CustomOrderDefinition;
using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
@@ -19,7 +18,6 @@ namespace mbts.spark {
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly VersionContainer<AlgoDefi> _definition;
AlgoFormats _format;
AlgoEditingPermissions _editing_permissions;
SecurityMD? _security_md;
OrdersModel _model;
@@ -84,10 +82,8 @@ namespace mbts.spark {
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
var orig_group = group;
_security_md = security_md;
if (security_md.HasValue)
_format = OrderDataHelper.get_algo_format(security_md.Value.security.ContractCase);
var orig_def = definition;
var fmt_type = OrderDataHelper.get_default_algo(_format);
var fmt_type = OrderDataHelper.get_default_algo(security_md.HasValue ? security_md.Value.security.ContractCase : bts.api.Security.ContractOneofCase.None);
switch (fmt_type) {
case AlgoType.None: _clear_impl(); break;
default:
@@ -2,7 +2,6 @@ using bts.api;
using bts.api.orderman;
using bts.utils;
using Google.Protobuf.Reflection;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -15,13 +14,15 @@ namespace mbts.spark {
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
readonly RegisteredDynamicOrderTypes _dynamic_types;
readonly Dictionary<string, DynamicOrderDescriptor> _dyn_types = new Dictionary<string, DynamicOrderDescriptor>(StringComparer.CurrentCultureIgnoreCase);
[ImportingConstructor]
DynamicOrderHelper(OrderService order_service) =>
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
DynamicOrderHelper(OrderService order_service) {
foreach (var dt in order_service.get_registered_dynamic_order_types(new VoidMessage()).DynamicOrderDesc)
_dyn_types[dt.DynamicOrderType] = dt;
}
public IEnumerable<string> dynamic_types => _dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType);
public IEnumerable<string> dynamic_types => _dyn_types.Keys;
public static string fmt_param(DynamicOrderParam dyn_param) {
if (dyn_param == null)
@@ -50,7 +51,7 @@ namespace mbts.spark {
}
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs, Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory) {
foreach (var d in _dynamic_types.DynamicOrderDesc) {
foreach (var d in _dyn_types.Values) {
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
dorder.Dynamic.DynamicOrderType = d.DynamicOrderType;
var ddef = dorder.Dynamic;
@@ -60,8 +61,20 @@ namespace mbts.spark {
}
}
public bool try_parse(string str, out string dynamic_type) {
if (_dyn_types.TryGetValue(str, out var desc)) {
dynamic_type = desc.DynamicOrderType;
return true;
}
dynamic_type = null;
return false;
}
internal DynamicOrderDescriptor get_descriptor(string dynamic_type) =>
_dynamic_types.DynamicOrderDesc.FirstOrDefault(d => d.DynamicOrderType == dynamic_type) ?? new DynamicOrderDescriptor();
_dyn_types.TryGetValue(dynamic_type, out var desc) ? desc : new DynamicOrderDescriptor();
internal CustomOrderDefinitionImmutable create_custom_defi(string dynamic_type) =>
new CustomOrderDefinitionImmutable(new CustomOrderDefinition { Dynamic = create_def(dynamic_type) });
internal DynamicOrderDefinition create_def(string dynamic_type) {
var desc = get_descriptor(dynamic_type);
@@ -122,12 +122,6 @@ namespace mbts.spark {
public static Color safe_get_badge_color(this IAlgoVM vm) =>
vm == null ? default : vm.meta.Color.rgb_color();
public static IDisposable load_algo_data_recursive(this IAlgoSelector selector, IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
selector.load_algo_data(algo_data, default_algo);
return ObservableAutoWrapper.connect_item_hooks(selector.algos, vm =>
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
}
public static IEnumerable<IAlgoParamVM> enumerate_all_params(this IAlgoVM vm) =>
vm == null ? Enumerable.Empty<IAlgoParamVM>() : vm.algo_params.Concat(vm.child_algo_selectors.SelectMany(s => enumerate_all_params(s.selected_algo)));
@@ -1,16 +1,82 @@
using bts.api.orderman;
using mbts.ui;
using System;
using System.Reactive;
using System.Reactive.Linq;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class IcebergOrderVM : OrderTypeVMBase<IcebergOrderDefinition, IcebergOrderDefinitionImmutable> {
readonly Property<double> _show_quantity;
readonly DisplayQuantityMultiplier<double> _display_qty_translator = DisplayQuantityMultiplier.create_double();
new IDisposable _dispose;
public IcebergOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_show_quantity = add_property(nameof(show_quantity), IcebergOrderDefinition.ShowQuantityFieldNumber, double.NaN, double.NaN);
_show_quantity = add_property(
"show_quantity",
IcebergOrderDefinition.ShowQuantityFieldNumber,
double.NaN,
double.NaN,
to_display_value: v => _qty_conversion.to_display(v),
from_display_value: v => _qty_conversion.from_display(v),
refresh_display_value:
Observable.Create<Unit>(o => {
_request_refresh_display += refresh;
return Disposable.Create(() => _request_refresh_display -= refresh);
void refresh() =>
o.OnNext(Unit.Default);
}));
_show_quantity.Changed += on_show_quantity_changed;
_dispose = Disposable.Create(
_display_qty_translator.connect_owner(this, nameof(display_show_quantity)),
_display_qty_translator.subscribe_actual_value().Subscribe(v => _show_quantity.Value = v),
Disposable.Create(() => _show_quantity.Changed -= on_show_quantity_changed));
void on_show_quantity_changed(double oldval, double newval) =>
_display_qty_translator.actual_value = newval;
}
public double show_quantity { get => _show_quantity.Value; set => _show_quantity.set(value); }
event Action _request_refresh_display;
public double display_show_quantity {
get => _display_qty_translator.display_value;
set => _display_qty_translator.display_value = value;
}
QuantityConversionData _qty_conversion {
get => _display_qty_translator.conversion_data;
set {
if (_display_qty_translator.conversion_data != value) {
_display_qty_translator.conversion_data = value;
_request_refresh_display?.Invoke();
}
}
}
protected override void on_model_internal() =>
_refresh_display();
protected override void on_security_internal() =>
_refresh_display();
void _refresh_display() {
if (model == null) {
_qty_conversion = default;
} else {
var divisor = model.security_auxiliary_model.get_display_quantity_divisor(security);
if (divisor == 1 || divisor == 0) {
_qty_conversion = default;
} else {
_qty_conversion = new QuantityConversionData(divisor, QuantityConversionData.OpType.DivideActual);
}
}
}
protected override void Dispose(bool disposing) {
Disposable.dispose(ref _dispose);
base.Dispose(disposing);
}
}
}
}
}
@@ -18,6 +18,7 @@ using System.Collections.Specialized;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
@@ -61,7 +62,9 @@ namespace mbts.spark {
public static IAlgoVM composite(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new CompositeAlgoVM(key, display_name, editing_permissions);
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
var vm = def_data.key.is_persisted ?
new CompositeAlgoVM(def_data.key, def_data.name, editing_permissions) :
create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
vm.load_from(def_data.value);
return vm;
}
@@ -79,7 +82,7 @@ namespace mbts.spark {
case AlgoType.Join: vm = join(key, name, editing_permissions); break;
case AlgoType.Top: vm = top(key, name, editing_permissions); break;
case AlgoType.Cover: vm = cover(key, name, editing_permissions); break;
case AlgoType.Oco : return oco(key, name, editing_permissions);
case AlgoType.Oco: return oco(key, name, editing_permissions);
case AlgoType.Pricer: return pricer(key, name, editing_permissions);
case AlgoType.Offset: vm = offset(key, name, editing_permissions); break;
case AlgoType.Trigger: vm = trigger(key, name, editing_permissions); break;
@@ -87,13 +90,10 @@ namespace mbts.spark {
case AlgoType.Iceberg: vm = iceberg(key, name, editing_permissions); break;
case AlgoType.Spreader: vm = spreader(key, name, editing_permissions); break;
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
case AlgoType.Splitter: vm = splitter(key,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}.'");
// return new DefaultAlgoVM();
}
vm.is_expanded = is_expanded && vm.can_expand;
return vm;
@@ -269,12 +269,7 @@ namespace mbts.spark {
void selector_load_impl(AlgoSelectorData sd) {
var inner_def = sd.get_order(value);
if (inner_def.HasValue) {
if (security.HasValue) {
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
if (!inner_def.HasValue)
return;
}
if (inner_def.HasValue && inner_def.Value.CustomCase != AlgoType.None) {
var ival = inner_def.Value;
var vm = sd.selector.algos.GetOrAdd(
AlgoKey.from_custom_def(ival),
@@ -317,18 +312,12 @@ namespace mbts.spark {
}
AlgoKey key;
if (security.HasValue) {
init = OrderDataHelper.fit_to_type(init.Value, security.Value.ContractCase);
if (!init.HasValue)
return;
key = AlgoKey.from_custom_def(init.Value);
child_list.Add(init.Value);
for (int i = 1; i < algos.Count; i++) {
var child = sd.get_from_impl(algos[i]);
if (!child.HasValue || key != AlgoKey.from_custom_def(child.Value))
return;
child = OrderDataHelper.fit_to_type(child.Value, security.Value.ContractCase);
if (!child.HasValue)
return;
child_list.Add(child.Value);
}
} else {
@@ -372,7 +361,6 @@ namespace mbts.spark {
}),
c.assign_child);
}
_populate_selectors();
}
// local methods
IDisposable reset() {
@@ -415,9 +403,19 @@ namespace mbts.spark {
#endregion
#region Auto-Properties (non-child-algo params)
protected Property<T> add_property<T>(string prop_name, int target_field_number, T init_value = default, T non_uniform_value = default, bool validate_on_changed = false, AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite) {
protected Property<T> add_property<T>(
string prop_name,
int target_field_number,
T init_value = default,
T non_uniform_value = default,
bool validate_on_changed = false,
AlgoParamPermissions permissions = AlgoParamPermissions.ReadWrite,
Func<T, T> to_display_value = null,
Func<T, T> from_display_value = null,
IObservable<Unit> refresh_display_value = null) {
//
var prop = backing(prop_name, init_value);
_on_property_added(prop_name, target_field_number, prop, prop.set, non_uniform_value, validate_on_changed, permissions);
_on_property_added(prop_name, target_field_number, prop, prop.set, non_uniform_value, validate_on_changed, permissions, to_display_value, from_display_value, refresh_display_value);
return prop;
}
@@ -592,17 +590,25 @@ namespace mbts.spark {
}
}
void _on_property_added<T>(string prop_name, int target_field_number, IProperty<T> prop, Action<T> assign_to_prop, T non_uniform_value, bool validate_on_changed, AlgoParamPermissions permissions) {
void _on_property_added<T>(string prop_name, int target_field_number, IProperty<T> prop, Action<T> assign_to_prop, T non_uniform_value, bool validate_on_changed, AlgoParamPermissions permissions, Func<T, T> to_display_value = null, Func<T, T> from_display_value = null, IObservable<Unit> refresh_display_value = null) {
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
var param = new AlgoParamVM<T>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), prop.Value) { permissions = permissions };
var param = new AlgoParamVM<T>(prop_name, DisplayNameHelper.snake_to_title_case(prop_name), prop.Value, EqualityComparer<T>.Default, null, to_display_value, from_display_value) {
permissions = permissions
};
_algo_params.Add(param);
var prop_hook = _link_props(param, prop, assign_to_prop, non_uniform_value, validate_on_changed);
if (refresh_display_value != null) {
prop_hook = Disposable.Create(
refresh_display_value.Subscribe(_ => param.refresh_display_value()),
prop_hook);
}
_auto_props.Add(new AutoProp(prop_name, mval => {
if (mval != null && param.has_value)
field_desc.Accessor.SetValue(mval, prop.Value);
},
mval => assign_to_prop_and_commit(mval == null ? default : ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(mval))),
mvals => assign_to_prop_and_commit(mvals.Select(v => ConvertUtils.try_convert<T>(field_desc.Accessor.GetValue(v))).Distinct().SafeSingleOrDefault(non_uniform_value)),
_link_props(param, prop, assign_to_prop, non_uniform_value, validate_on_changed)));
prop_hook));
void assign_to_prop_and_commit(T value) {
assign_to_prop(value);
param.mark_committed();
@@ -814,8 +820,6 @@ namespace mbts.spark {
return Disposable.Create(() => {
if (security != old_sec) {
on_security_internal();
if (_editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditStructure))
_populate_selectors();
_security_changed?.Invoke(security);
}
if (security_md != old_sec_md)
@@ -823,21 +827,6 @@ namespace mbts.spark {
});
}
IDisposable _selector_data_hooks;
void _populate_selectors() {
if (model != null && security.HasValue) {
var algo_data = model.factory.default_algos(security.Value);
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.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;
});
}
}
class AutoProp : IDisposable {
public readonly string name;
public readonly Action<TMutable> assign, load;
@@ -924,6 +913,7 @@ namespace mbts.spark {
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
IReadOnlyDictionary<AlgoKey, AlgoData> _pending_data;
AlgoKey? _pending_default;
IDisposable _loaded_data_hook;
public AlgoSelector(string id, string display_name) {
this.id = id;
@@ -1020,8 +1010,11 @@ namespace mbts.spark {
void _sync_formats() {
if (initialized && _pending_data != null) {
var fmt_algos = _pending_data;
algos.SyncWithDictionary(fmt_algos, d => create(d, is_expanded: true));
Disposable.dispose(ref _loaded_data_hook);
var data = _pending_data;
algos.SyncWithDictionary(data, d => create(d, is_expanded: true));
_loaded_data_hook = ObservableAutoWrapper.connect_item_hooks(algos, vm =>
ObservableAutoWrapper.connect_item_actions(vm.child_algo_selectors, s => s.load_algo_data(data, null)));
if (_algo == null || !algos.ContainsKey(_algo.id)) {
if (_pending_default.HasValue && algos.TryGetValue(_pending_default.Value, out IAlgoVM match))
selected_algo = match;
@@ -138,7 +138,7 @@ namespace mbts.spark {
new DialogSkirtItem("Save Strategy", commit);
void _ensure_init() {
if (_vm == null && _model != null && _template.HasValue && _template.Value.HasSecurity) {
if (_vm == null && _model != null && _template.HasValue && _template.Value.HasSecurity && _template.Value.HasExecutionStrategy) {
Log.isnull(_save_strategy_item);
Log.isnull(_vm_strategy_sub);
var tmp = _template.Value;
+30 -8
View File
@@ -32,6 +32,7 @@ namespace mbts.spark {
public readonly ISecurityAuxiliaryDataModel security_auxiliary_model;
readonly IBus _bus;
readonly IAlgoProvider _algo_provider, _attachable_algo_provider;
readonly OrderFactory _factory;
readonly OrderService _service;
readonly PositionService _position_service;
@@ -57,6 +58,8 @@ namespace mbts.spark {
IAlgoStudioModel algo_model,
ISecurityAuxiliaryDataModel security_auxiliary_model,
MarketDataAuxiliaryModel md_auxiliary_model,
IAlgoProvider algo_provider,
IAttachableAlgoProvider attachable_algo_provider,
SecurityStore sstore,
ErrorReporter error) {
//
@@ -67,6 +70,8 @@ namespace mbts.spark {
_mbo_service = mbo_service;
_spreader = spreader;
_error = error;
_algo_provider = algo_provider;
_attachable_algo_provider = attachable_algo_provider;
_dynamic_helper = dynamic_helper;
this.admin_model = admin_model;
this.algo_model = algo_model;
@@ -314,6 +319,9 @@ namespace mbts.spark {
public MBOService mbo_service => _mbo_service;
public IAlgoStudioModel algo_model { get; }
public AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security) =>
_algo_provider.get_permitted_algos(security);
public IObservable<TableUpdate<OrderSecurity, uint>> on_security_working_orders_with_piq(uint security) =>
_security_working_orders_with_piq_demuxer.subscribe(security);
@@ -322,6 +330,15 @@ namespace mbts.spark {
public IObservable<TableUpdate<(SecurityImmutable?, PositionUnitImmutable), int>> on_security_positions(uint security) =>
_security_positions_demuxer.subscribe(security);
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(bool attachable_only = false) =>
(attachable_only ? _attachable_algo_provider : _algo_provider).subscribe_algos();
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security, bool attachable_only = false) =>
(attachable_only ? _attachable_algo_provider : _algo_provider).subscribe_algos(security);
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(bool attachable_only = false, bool allow_undefined_child = false) =>
(attachable_only ? _attachable_algo_provider : _algo_provider).subscribe_complete_algos(allow_undefined_child);
#endregion
#region Input
@@ -634,14 +651,10 @@ namespace mbts.spark {
}).Select(r => r.last_update);
public static IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> subscribe_all_algos(this OrdersModel orders_model) =>
orders_model.studio_algos_with_meta.MergeTable(Observable.Return(
new TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
ImmutableDictionary<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>.Empty.AddRange(orders_model.factory.default_algos(OrderDataHelper.all_order_types).Select(kv => new KeyValuePair<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>(kv.Key, (kv.Value, null)))),
Array.Empty<(AlgoData, AlgoDefinitionMetadata?)>(),
Array.Empty<Change<(AlgoData, AlgoDefinitionMetadata?)>>(),
Array.Empty<(AlgoData, AlgoDefinitionMetadata?)>(),
d => d.algo.key
).as_snapshot()));
_subscribe_algo_meta_tuples(orders_model, orders_model.subscribe_algos());
public static IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> subscribe_algo_meta_tuples(this OrdersModel orders_model, SecurityImmutable security) =>
_subscribe_algo_meta_tuples(orders_model, orders_model.subscribe_algos(security));
public static async Task<OrderRecordImmutable?> order_from_key(this OrdersModel orders_model, uint order_key) {
(var tu, var success) = await orders_model.on_orders.FirstAsyncTask(t => t.state.ContainsKey(order_key), TimeSpan.FromSeconds(3));
@@ -684,6 +697,15 @@ namespace mbts.spark {
return false;
}
static IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> _subscribe_algo_meta_tuples(OrdersModel orders_model, IObservable<TableUpdate<AlgoData, AlgoKey>> algos) =>
algos.LeftJoin(
orders_model.algo_model.metadata_service.subscribe_metadata().Table(AlgoDefinitionMetadata.key_fn),
AlgoData.key_fn,
AlgoDefinitionMetadata.key_fn,
(algo, key, meta) => (algo, (AlgoDefinitionMetadata?)meta),
(algo, key) => (algo, (AlgoDefinitionMetadata?)null),
tup => tup.algo.key);
class RootOrderData {
public readonly SetMap<uint, uint> parent_to_children = new SetMap<uint, uint>();
public TableUpdateTransform<OrderRecordImmutable, uint> last_update { get; set; } = new TableUpdateTransform<OrderRecordImmutable, uint>();
@@ -0,0 +1,44 @@
using bts.spark;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading.Tasks;
namespace bts.api.position {
interface IPositionModel {
Task<bool> override_cpp_async(IEnumerable<(uint security_key, double cpp)> cpp_overrides);
}
[Export(typeof(IPositionModel))]
class PositionModel : IPositionModel {
readonly PositionService _position_service;
readonly ErrorReporter _error_reporter;
[ImportingConstructor]
public PositionModel(PositionService position_service, ErrorReporter error_reporter) {
_position_service = position_service ?? throw new ArgumentNullException(nameof(position_service));
_error_reporter = error_reporter ?? throw new ArgumentNullException(nameof(error_reporter));
}
public Task<bool> override_cpp_async(IEnumerable<(uint security_key, double cpp)> cpp_overrides) {
var sec_params_batch_op = new SecurityParametersBatchOp();
sec_params_batch_op.OpParams.AddRange(cpp_overrides.Select(tup => new SecurityParameters {
SecurityKey = tup.security_key,
CppOverride = new CppOverride { Value = tup.cpp }
}));
return _error_reporter.safe_execute(
() => _position_service.update_security_parameters_async(sec_params_batch_op),
r => r.Ok,
r => r.Msg,
(v, success) => success,
"override cpp");
}
}
static class PositionModelExtensions {
public static Task<bool> override_cpp_async(this IPositionModel position_model, IEnumerable<uint> security_keys, double cpp) =>
(position_model ?? throw new ArgumentNullException(nameof(position_model)))
.override_cpp_async(security_keys.Select(k => (k, cpp)));
}
}
@@ -187,7 +187,7 @@ namespace mbts.collections {
}
}
class KeyedMultiSelector<T, TKey> : ViewModelBase where TKey : struct, IEquatable<TKey> {
class KeyedMultiSelector<T, TKey> : ViewModelBase where TKey : IEquatable<TKey> {
readonly Func<T, TKey> _key_selector;
HashSet<TKey> _desired_keys = new HashSet<TKey>();
@@ -204,7 +204,7 @@ namespace mbts.collections {
try_select(key, true);
public bool try_select(TKey key, bool reattempt_on_collection_change) {
if (items.TryGetFirst(i => _key_selector(i).Equals(key), out T result)) {
if (items.TryGetFirst(i => _key_selector(i).safe_equals(key), out T result)) {
selected_items.Add(result);
return true;
} else if (reattempt_on_collection_change) {
@@ -59,6 +59,7 @@ namespace mbts.spark.filters {
ULongMinMax = "ULongMinMaxTemplate",
TimeFilter = "TimeFilterTemplate",
DiscreteValues = "DiscreteValuesTemplate",
DiscreteValuesCollectionView = "DiscreteValuesCollectionViewTemplate",
EnumValues = "EnumValuesTemplate",
Regex = "RegexTemplate",
YesNo = "YesNoTemplate";
@@ -70,7 +70,7 @@ namespace mbts.spark.filters {
out IObserver<TableUpdate<TValue, TKey>> items_observer,
out ListCollectionView<TValue> items_view,
string prop_name = null)
where TKey : struct, IEquatable<TKey> =>
where TKey : IEquatable<TKey> =>
//
CreateTableUpdate(factory, key, keys => keys, keys => keys.ToArray(), out items_observer, out items_view, prop_name);
@@ -82,7 +82,7 @@ namespace mbts.spark.filters {
out IObserver<TableUpdate<TValue, TKey>> items_observer,
out ListCollectionView<TValue> items_view,
string prop_name = null)
where TKey : struct, IEquatable<TKey> {
where TKey : IEquatable<TKey> {
//
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TState>(factory, key, load_state, get_state, prop_name);
items_observer = filter;
@@ -282,7 +282,7 @@ namespace mbts.spark.filters {
}
}
class TableUpdateItemsFilter<TRow, TValue, TKey, TState> : FilterVMBase<TRow, TValue, TState>, IObserver<TableUpdate<TValue, TKey>> where TKey : struct, IEquatable<TKey> {
class TableUpdateItemsFilter<TRow, TValue, TKey, TState> : FilterVMBase<TRow, TValue, TState>, IObserver<TableUpdate<TValue, TKey>> where TKey : IEquatable<TKey> {
readonly ObservableKeyedCollection<TValue, TKey> _items;
readonly KeyedMultiSelector<TValue, TKey> _selection_model;
readonly Func<TValue, TKey> _key;
@@ -1,10 +1,13 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:msp="clr-namespace:mbts.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:filters="clr-namespace:mbts.spark.filters"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:msp="clr-namespace:mbts.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
@@ -147,6 +150,21 @@
/>
</usp:FilterContentControl>
</DataTemplate>
<DataTemplate x:Key="{x:Static filters:DataGridFilterKnownTemplates.DiscreteValuesCollectionView}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items_view}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
/>
</usp:FilterContentControl>
</DataTemplate>
<DataTemplate x:Key="AlgoTypeTemplate">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
@@ -947,7 +947,7 @@
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center">Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="Maximum size to show on exchange, this is the BTS provided iceberg"/>
</StackPanel>
<c:TickUpDownDouble x:Name="PART_ShowQuantity_TickUpDownDouble" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding show_quantity}"/>
<c:TickUpDownDouble x:Name="PART_ShowQuantity_TickUpDownDouble" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding display_show_quantity}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
Grid.Row="2"
@@ -15,8 +15,7 @@ namespace mbts.ui.spark {
AlgoDropDownStyleKeyConverter = ValueConverter.create(obj => {
if (obj is AlgoDefinitionVM) return DefinitionDropDownStyleKey;
return null;
}),
DataContextConverter = ValueConverter.create(obj => obj is AlgoVMWrapper w ? w.value.algo_root : obj);
});
public static IMultiValueConverter TriggerTimeoutStateConverter =
ValueConverter.create<bool, AlgoEditingPermissions, string>((is_timeout_enabled, editing_permissions) =>
@@ -0,0 +1,38 @@
<ResourceDictionary
x:Class="mbts.ui.controls.property_editor.PropertyEditorResources"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:pe="clr-namespace:mbts.ui.controls.property_editor"
>
<Thickness x:Key="PropertyLabelMargin" Left="0" Top="4" Right="4"/>
<Thickness x:Key="PropertyValueMargin" Top="4"/>
<DataTemplate DataType="{x:Static pe:PropertyEditorItemTypes.DoubleItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Margin="{StaticResource PropertyLabelMargin}" Text="{Binding name}"/>
<c:TickUpDownDouble Grid.Column="1" VerticalAlignment="Center" Margin="{StaticResource PropertyValueMargin}" Value="{Binding value}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type pe:PropertyEditorVM}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding properties}" Margin="8,4"/>
<c:DialogSkirt Grid.Row="1">
<c:DialogSkirt.RightItems>
<x:Array Type="{x:Type Button}">
<Button IsDefault="True" Command="{Binding commit_cmd}">OK</Button>
<Button IsCancel="True" Command="{Binding cancel_cmd}">Cancel</Button>
</x:Array>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</DataTemplate>
</ResourceDictionary>
@@ -0,0 +1,122 @@
using mbts.spark;
using mbts.ui.utilities;
using mbts.ui.utility;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace mbts.ui.controls.property_editor {
partial class PropertyEditorResources : ResourceDictionary {
public PropertyEditorResources() {
InitializeComponent();
}
}
static class PropertyEditor {
static int _window_id;
public static Task<(double value, bool committed)> get_double_async(
string title,
string prompt,
CancellationToken cancellation_token,
double initial_value = 0,
Func<double, string> validate = null,
WindowStartupLocation startup_location = WindowStartupLocation.Manual,
int? offset_x = -72,
int? offset_y = -40) {
//
PropertyEditorItem<double> double_item = null;
return get_result_async(create_vm, _ => (double_item?.value ?? double.NaN, true), cancellation_token, init, tcs => tcs.TrySetResult((double.NaN, false)));
PropertyEditorVM create_vm() {
var vm = new PropertyEditorVM(title, prompt);
vm.properties.Add(double_item = new PropertyEditorItem<double>(prompt, validate) {
value = initial_value
});
return vm;
}
IDisposable init(PropertyEditorVM vm, Window dialog) {
_position_window(dialog, startup_location, offset_x, offset_y);
return null;
}
}
public static Task<TResult> get_result_async<TResult>(
Func<PropertyEditorVM> create_vm,
Func<PropertyEditorVM, TResult> get_result,
CancellationToken cancellation_token,
Func<PropertyEditorVM, Window, IDisposable> init = null,
Action<TaskCompletionSource<TResult>> premature_close_callback = null) {
//
var tcs = new TaskCompletionSource<TResult>();
var window_name = $"{nameof(PropertyEditor)} - {Interlocked.Increment(ref _window_id)}";
var launcher = new ThreadedWindowLauncher(window_name, 1);
launcher.show(create_win_impl);
return tcs.Task;
Window create_win_impl() {
var vm = create_vm();
var win = new Window {
DataContext = vm,
Resources = new PropertyEditorResources(),
SizeToContent = SizeToContent.WidthAndHeight
};
win.SetResourceReference(FrameworkElement.StyleProperty, "BTSDialog");
win.SetBinding(ContentControl.ContentProperty, new Binding());
win.SetBinding(Window.TitleProperty, nameof(PropertyEditorVM.title));
var dispose = init?.Invoke(vm, win);
IDisposable check_canceled_loop = null;
vm.RequestCommit += on_request_commit;
vm.RequestClose += on_request_close;
win.Closed += on_premature_closed;
check_canceled_loop = WorkLoop.Start($"{window_name} cancellation loop", () => {
if (cancellation_token.IsCancellationRequested && win != null) {
Disposable.dispose(ref check_canceled_loop);
WindowUtilities.force_close(win);
}
});
return win;
void on_request_commit(object sender, EventArgs e) {
detach_and_dispose();
WindowUtilities.force_close(win);
win = null;
end_task()?.TrySetResult(get_result(vm));
}
void on_request_close(object sender, EventArgs e) {
detach_and_dispose();
WindowUtilities.force_close(win);
win = null;
premature_close_callback?.Invoke(end_task());
}
void on_premature_closed(object sender, EventArgs e) {
detach_and_dispose();
win = null;
premature_close_callback?.Invoke(end_task());
}
void detach_and_dispose() {
Disposable.dispose(ref check_canceled_loop);
Disposable.dispose(ref dispose);
win.Closed -= on_premature_closed;
vm.RequestCommit -= on_request_commit;
vm.RequestClose -= on_request_close;
}
TaskCompletionSource<TResult> end_task() {
var temp_tcs = tcs;
tcs = null;
return temp_tcs;
}
}
}
static void _position_window(Window window, WindowStartupLocation startup_location, int? offset_x = null, int? offset_y = null) {
window.WindowStartupLocation = startup_location;
if (startup_location == WindowStartupLocation.Manual && (offset_x.HasValue || offset_y.HasValue))
window.move_to_mouse(offset_x ?? 0, offset_y ?? 0);
}
}
static class PropertyEditorItemTypes {
public static Type DoubleItem => typeof(PropertyEditorItem<double>);
}
}
@@ -0,0 +1,62 @@
using mbts.ui.shell;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace mbts.ui.controls.property_editor {
interface IPropertyEditorItem : INotifyPropertyChanged {
string name { get; }
object value { get; set; }
}
interface IPropertyEditorItem<T> : IPropertyEditorItem {
new T value { get; set; }
}
class PropertyEditorItem<T> : ViewModelBaseWithValidation, IPropertyEditorItem<T> {
readonly Func<T, string> _validate;
T _value;
public PropertyEditorItem(string name, Func<T, string> validate) {
this.name = name ?? throw new ArgumentNullException(nameof(name));
_validate = validate;
}
public string name { get; }
public T value {
get => _value;
set {
if (_validate == null) {
Set(ref _value, value);
} else {
using (validation(nameof(value))) {
Set(ref _value, value);
string error = _validate(value);
validation_assert(error != null, error, nameof(value));
}
}
}
}
object IPropertyEditorItem.value { get => value; set => value = (T)value; }
}
class PropertyEditorVM : ViewModelBase, IRequestViewClose {
public PropertyEditorVM(string title, string prompt) {
this.title = title ?? throw new ArgumentNullException(nameof(title));
this.prompt = prompt ?? throw new ArgumentNullException(nameof(prompt));
commit_cmd = new DelegateCommand(() => RequestCommit?.Invoke(this, EventArgs.Empty));
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, EventArgs.Empty));
}
public event EventHandler RequestCommit, RequestClose;
public string title { get; }
public string prompt { get; }
public ObservableCollection<object> properties { get; } = new ObservableCollection<object>();
public ICommand commit_cmd { get; }
public ICommand cancel_cmd { get; }
}
}
@@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace mbts.ui {
@@ -51,15 +52,19 @@ namespace mbts.ui {
public void thread_init() { }
}
public static IShellWindow create(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement, Stream> get_state = null, ImageSource icon = null, string thread_id=null, Action<ShellWindowVM> on_shell_window_vm = null) {
public static IShellWindow create(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement, Stream> get_state = null, ImageSource icon = null, string thread_id=null, Action<ShellWindowVM> on_shell_window_vm = null, Action<KeyEventArgs> on_key_down = null) {
IShellWindow rv = null;
if (string.IsNullOrEmpty(thread_id)) {
return create<AnonShellWindow>(owner, title, window_type, content, get_state, icon, on_shell_window_vm);
rv = create<AnonShellWindow>(owner, title, window_type, content, get_state, icon, on_shell_window_vm);
if (on_key_down != null)
rv = new KeyListenerWrapper(rv, on_key_down);
}
else {
var res = create<LazyAnonShellWindow>(owner, title, window_type, content, get_state, icon, on_shell_window_vm);
res.thread_id = thread_id;
return res;
rv = on_key_down == null ? (ISharedWindow)res : new SharedKeyListenerWrapper(res, on_key_down);
}
return rv;
}
static T create<T>(IShellModule owner, string title, string window_type, Lazy<UIElement> content, Action<UIElement,Stream> get_state=null, ImageSource icon=null, Action<ShellWindowVM> on_shell_window_vm = null) where T : AnonShellWindow, new() {
var result = new T() {
@@ -121,5 +126,56 @@ namespace mbts.ui {
if (cleanup != null) { window.Closed += (o, e) => cleanup.Dispose(); }
return window;
}
class KeyListenerWrapper<T> : IShellWindow, IKeyListener where T : class, IShellWindow {
readonly T _model;
readonly Action<KeyEventArgs> _key_down;
public KeyListenerWrapper(T model, Action<KeyEventArgs> key_down) {
_model = model ?? throw new ArgumentNullException(nameof(model));
_key_down = key_down ?? throw new ArgumentNullException(nameof(key_down));
}
public event Action TitleChanged {
add => _model.TitleChanged += value;
remove => _model.TitleChanged -= value;
}
public event Action IconChanged {
add => _model.IconChanged += value;
remove => _model.IconChanged -= value;
}
public string Title { get => _model.Title; set => _model.Title = value; }
public string window_type => _model.window_type;
public ImageSource Icon => _model.Icon;
public IShellModule Owner => _model.Owner;
public void Dispose() => _model.Dispose();
public ShellWindowUI GetContent() => _model.GetContent();
public void GetState(Stream save_here) => _model.GetState(save_here);
public void on_shell_window_vm(ShellWindowVM shell_window_vm) => _model.on_shell_window_vm(shell_window_vm);
public void key_down(KeyEventArgs e) =>
_key_down(e);
}
class KeyListenerWrapper : KeyListenerWrapper<IShellWindow> {
public KeyListenerWrapper(IShellWindow model, Action<KeyEventArgs> key_down)
: base(model, key_down) { }
}
class SharedKeyListenerWrapper : KeyListenerWrapper<ISharedWindow>, ISharedWindow {
readonly ISharedWindow _model;
readonly Action<KeyEventArgs> _key_down;
public SharedKeyListenerWrapper(ISharedWindow model, Action<KeyEventArgs> key_down)
: base(model, key_down) {
_model = model ?? throw new ArgumentNullException(nameof(model));
_key_down = key_down ?? throw new ArgumentNullException(nameof(key_down));
}
public string thread_id => _model.thread_id;
public void thread_init() => _model.thread_init();
}
}
}
@@ -87,6 +87,25 @@
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type pnl:PNLDataFormattingVM}">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Market Price Decimal Places</TextBlock>
<ComboBox Grid.Column="2" ItemsSource="{Binding decimal_place_options}" SelectedItem="{Binding market_price_decimal_places}"/>
<TextBlock Grid.Row="2">Fill Price Decimal Places</TextBlock>
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{Binding decimal_place_options}" SelectedItem="{Binding fill_price_decimal_places}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type pnl:PNLCellAppearanceVM}">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
@@ -744,6 +763,7 @@
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>
<TabItem Header="Data Formatting" Content="{Binding data_formatting_vm}"/>
</TabControl>
</Grid>
</DataTemplate>
@@ -10,6 +10,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace mbts.ui.shell {
// This Class owns the hierarchy of shell Windows
@@ -59,6 +60,14 @@ namespace mbts.ui.shell {
viewmodel.host_id = state.host_id ?? "";
viewmodel.TitleOverride = state.custom_title;
}
if (model is IKeyListener key_listener) {
content.KeyDown += on_key_down;
void on_key_down(object sender, KeyEventArgs e) {
viewmodel.Dispatcher.InvokeAsync(() => {
key_listener.key_down(e);
});
}
}
var window = await CreateWindow(content, state);
window.Show();
@@ -43,24 +43,24 @@ namespace mbts.spark {
foreach (var selected_item in all_securities.Where(s => selected_securities.Contains(s.Key))) {
// verify clause isn't already in the list
if (!security_criteria_clauses.Any(x => x.pretty_name == selected_item.Value.security.ToString())) {
security_criteria_clauses.Insert(0, new SecurityCriteriaClause() {
pretty_name = selected_item.Value.security.ToString(),
exchanges = selected_exchanges.ToArray(),
security_types = selected_security_types.ToArray(),
symbols = selected_symbols.ToArray(),
security_keys = new List<uint>() { selected_item.Key }
});
security_criteria_clauses.Insert(0, new SecurityCriteriaClause(
pretty_name: selected_item.Value.security.ToString(),
exchanges: selected_exchanges.ToArray(),
security_types: selected_security_types.ToArray(),
symbols: selected_symbols.ToArray(),
security_keys: new[] { selected_item.Key }
));
}
}
} else {
if (!security_criteria_clauses.Any(x => x.pretty_name == criteria_input_name)) {
security_criteria_clauses.Insert(0, new SecurityCriteriaClause() {
pretty_name = criteria_input_name,
exchanges = selected_exchanges.ToArray(),
security_types = selected_security_types.ToArray(),
symbols = selected_symbols.ToArray(),
security_keys = selected_securities
});
security_criteria_clauses.Insert(0, new SecurityCriteriaClause(
pretty_name: criteria_input_name,
exchanges: selected_exchanges.ToArray(),
security_types: selected_security_types.ToArray(),
symbols: selected_symbols.ToArray(),
security_keys: selected_securities.ToArray()
));
}
}
criteria_input_name = string.Empty;
@@ -97,12 +97,12 @@ namespace mbts.spark {
add_symbol_command = new DelegateCommand<string>(symbol => {
_compose_criteria_clause_name(true);
if (!security_criteria_clauses.Any(x => x.pretty_name == _criteria_input_name_from_double_click)) {
security_criteria_clauses.Insert(0, new SecurityCriteriaClause() {
pretty_name = _criteria_input_name_from_double_click,
exchanges = selected_exchanges.ToArray(),
security_types = selected_security_types.ToArray(),
symbols = new string[] { symbol },
});
security_criteria_clauses.Insert(0, new SecurityCriteriaClause(
pretty_name: _criteria_input_name_from_double_click,
exchanges: selected_exchanges.ToArray(),
security_types: selected_security_types.ToArray(),
symbols: new string[] { symbol }
));
}
ok_button_command.TryRaiseCanExecuteChanged();
remove_criteria_command.TryRaiseCanExecuteChanged();
@@ -110,11 +110,11 @@ namespace mbts.spark {
add_security_type_command = new DelegateCommand<SecurityType>(sec_type => {
_compose_criteria_clause_name(true, true);
if (!security_criteria_clauses.Any(x => x.pretty_name == _criteria_input_name_from_double_click)) {
security_criteria_clauses.Insert(0, new SecurityCriteriaClause() {
pretty_name = _criteria_input_name_from_double_click,
exchanges = selected_exchanges.ToArray(),
security_types = new SecurityType[] { sec_type },
});
security_criteria_clauses.Insert(0, new SecurityCriteriaClause(
pretty_name: _criteria_input_name_from_double_click,
exchanges: selected_exchanges.ToArray(),
security_types: new SecurityType[] { sec_type }
));
}
ok_button_command.TryRaiseCanExecuteChanged();
remove_criteria_command.TryRaiseCanExecuteChanged();
@@ -122,10 +122,10 @@ namespace mbts.spark {
add_exchange_command = new DelegateCommand<Venue>(exchange => {
_compose_criteria_clause_name(true, true, true);
if (!security_criteria_clauses.Any(x => x.pretty_name == _criteria_input_name_from_double_click)) {
security_criteria_clauses.Insert(0, new SecurityCriteriaClause() {
pretty_name = _criteria_input_name_from_double_click,
exchanges = new Venue[] { exchange },
});
security_criteria_clauses.Insert(0, new SecurityCriteriaClause(
pretty_name: _criteria_input_name_from_double_click,
exchanges: new Venue[] { exchange }
));
}
ok_button_command.TryRaiseCanExecuteChanged();
remove_criteria_command.TryRaiseCanExecuteChanged();
@@ -343,12 +343,16 @@ namespace mbts.spark {
}
}
// TODO: this should return a nullable (if no specific security is selected, the result should be null)
public SecurityMD result {
get {
SecurityCriteriaClause scc = new SecurityCriteriaClause();
scc.security_keys = selected_securities;
SecurityCriteria crit = new SecurityCriteria(new SecurityCriteriaClause[] { scc });
return all_securities.Where(s => security_in_criteria(crit, s.Value.security)).FirstOrDefault().Value;
if (selected_securities.Count == 0)
throw new InvalidOperationException("No securities selected.");
foreach (var k in selected_securities) {
if (all_securities.TryGetValue(k, out var smd))
return smd;
}
throw new InvalidOperationException("None of the selected security keys are present in the security map.");
}
}
@@ -427,7 +431,7 @@ namespace mbts.spark {
bool check_exchanges = !(x.exchanges == null || x.exchanges.Length == 0);
bool check_security_types = !(x.security_types == null || x.security_types.Length == 0);
bool check_symbols = !(x.symbols == null || x.symbols.Length == 0);
bool check_security_keys = !(x.security_keys == null || x.security_keys.Count == 0);
bool check_security_keys = !(x.security_keys == null || x.security_keys.Length == 0);
bool check_something_in_clause = check_exchanges || check_security_types || check_symbols || check_security_keys;
if (check_something_in_clause) {
@@ -8,8 +8,7 @@ using System.Reactive.Linq;
namespace mbts.spark {
[ProtoContract]
public class SecurityCriteria : IEquatable<SecurityCriteria>
{
class SecurityCriteria : IEquatable<SecurityCriteria> {
public SecurityCriteria()
{
clauses = new SecurityCriteriaClause[] { };
@@ -29,9 +28,7 @@ namespace mbts.spark {
internal static SecurityCriteria from_keys(IEnumerable<uint> keys) =>
new SecurityCriteria(new[] {
new SecurityCriteriaClause {
security_keys = keys.ToList()
}
new SecurityCriteriaClause (security_keys: keys.ToArray())
});
internal static SecurityCriteria from_securities(ISet<SecurityImmutable> securities) =>
@@ -49,27 +46,38 @@ namespace mbts.spark {
}
[ProtoContract]
public class SecurityCriteriaClause : IEquatable<SecurityCriteriaClause>
{
public SecurityCriteriaClause() { }
internal SecurityCriteriaClause(IEnumerable<SecurityImmutable> securities) {
security_keys = securities.Select(s => s.Id).ToList();
readonly struct SecurityCriteriaClause : IEquatable<SecurityCriteriaClause> {
public SecurityCriteriaClause(
Venue[] exchanges = null,
SecurityType[] security_types = null,
string[] symbols = null,
uint[] security_keys = null,
string pretty_name = null) {
//
this.exchanges = exchanges;
this.security_types = security_types;
this.symbols = symbols;
this.security_keys = security_keys;
this.pretty_name = pretty_name;
}
public SecurityCriteriaClause(IEnumerable<SecurityImmutable> securities)
: this(security_keys: securities.Select(s => s.Id).ToArray()) { }
[ProtoMember(1)]
public string pretty_name;
public readonly string pretty_name;
[ProtoMember(2)]
public Venue[] exchanges;
public readonly Venue[] exchanges;
[ProtoMember(3)]
public SecurityType[] security_types;
public readonly SecurityType[] security_types;
[ProtoMember(4)]
public string[] symbols;
public readonly string[] symbols;
[ProtoMember(5)]
public List<uint> security_keys;
public readonly uint[] security_keys;
public bool is_defined =>
!string.IsNullOrEmpty(pretty_name) ||
@@ -84,7 +92,6 @@ namespace mbts.spark {
}
public bool Equals(SecurityCriteriaClause other) =>
other != null &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(security_types, other.security_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
@@ -90,7 +90,6 @@ namespace mbts.spark {
/// <param name="clause">A SecurityCriteriaClause.</param>
/// <returns>true if the clause is non-null and specifies one security-key, and nothing else; otherwise false.</returns>
public static bool is_single_security(this SecurityCriteriaClause clause) =>
clause != null &&
clause.exchanges.is_null_or_empty() &&
clause.symbols.is_null_or_empty() &&
clause.security_types.is_null_or_empty() &&
@@ -98,6 +97,45 @@ namespace mbts.spark {
!clause.security_keys.Distinct().Skip(1).Any();
public static bool includes(this SecurityCriteria security_criteria, SecurityImmutable security) =>
security_criteria != null && ContractPickerModel.security_in_criteria(security_criteria, security);
security_criteria.is_defined() && ContractPickerModel.security_in_criteria(security_criteria, security);
public static bool includes(this SecurityCriteriaClause clause, SecurityImmutable security) =>
_to_predicates(clause).AnyAndAll(p => p(security));
public static bool is_subset_of(this SecurityCriteriaClause clause, SecurityCriteriaClause other) =>
// exchanges
(other.exchanges.is_null_or_empty() ||
(!clause.exchanges.is_null_or_empty() && clause.exchanges.All(other.exchanges.Contains))) &&
// symbols
(other.symbols.is_null_or_empty() ||
(!clause.symbols.is_null_or_empty() && clause.symbols.All(other.symbols.Contains))) &&
// security_types
(other.security_types.is_null_or_empty() ||
(!clause.security_types.is_null_or_empty() && clause.security_types.All(other.security_types.Contains))) &&
// security_keys
(other.security_keys.is_null_or_empty() ||
(!clause.security_keys.is_null_or_empty() && clause.security_keys.All(other.security_keys.Contains)));
public static IEnumerable<string> fmt_diagnostic_msgs(this SecurityCriteriaClause clause) {
if (clause.exchanges != null && clause.exchanges.Length > 0)
yield return "Exchanges: " + string.Join(", ", clause.exchanges);
if (clause.symbols != null && clause.symbols.Length > 0)
yield return "Symbols: " + string.Join(", ", clause.symbols);
if (clause.security_types != null && clause.security_types.Length > 0)
yield return "Security Types: " + string.Join(", ", clause.security_types);
if (clause.security_keys != null && clause.security_keys.Length > 0)
yield return "Security Keys: " + string.Join(", ", clause.security_keys);
}
static IEnumerable<Func<SecurityImmutable, bool>> _to_predicates(SecurityCriteriaClause clause) {
if (clause.exchanges != null && clause.exchanges.Length > 0)
yield return sec => clause.exchanges.Contains(sec.Exchange);
if (clause.symbols != null && clause.symbols.Length > 0)
yield return sec => clause.symbols.Contains(sec.Symbol);
if (clause.security_types != null && clause.security_types.Length > 0)
yield return sec => clause.security_types.Contains(sec.security_type());
if (clause.security_keys != null && clause.security_keys.Length > 0)
yield return sec => clause.security_keys.Contains(sec.Id);
}
}
}
@@ -111,7 +111,9 @@ namespace mbts.spark {
raw_mid = (bid + ask) / 2.0;
}
double tick_size = spec.ticksize(raw_mid);
return raw_mid - (raw_mid % tick_size);
return raw_mid < 0 ?
raw_mid + (raw_mid % tick_size) :
raw_mid - (raw_mid % tick_size);
}
public static TickFactors deduce_ticks(IEnumerable<ContractSpecification> contract_specs) {
@@ -40,25 +40,53 @@ namespace mbts.ui {
public static bool operator !=(QuantityConversionData x, QuantityConversionData y) => !x.Equals(y);
}
class DisplayQuantityMultiplier : IDisplayTranslator<uint> {
uint _actual_value;
static class DisplayQuantityMultiplier {
public static DisplayQuantityMultiplier<uint> create_uint() =>
new DisplayQuantityMultiplier<uint>(
DisplayTranslatorExtensions.to_display,
DisplayTranslatorExtensions.from_display);
public static DisplayQuantityMultiplier<double> create_double() =>
new DisplayQuantityMultiplier<double>(
DisplayTranslatorExtensions.to_display,
DisplayTranslatorExtensions.from_display);
}
class DisplayQuantityMultiplier<T> : IDisplayTranslator<T> {
readonly Func<QuantityConversionData, T, T> _to_display, _from_display;
readonly IEqualityComparer<T> _comparer;
T _actual_value;
QuantityConversionData _conversion_data;
public DisplayQuantityMultiplier(
Func<QuantityConversionData, T, T> to_display,
Func<QuantityConversionData, T, T> from_display,
IEqualityComparer<T> comparer = null,
T actual_value = default,
QuantityConversionData conversion_data = default) {
//
_to_display = to_display ?? throw new ArgumentNullException(nameof(to_display));
_from_display = from_display ?? throw new ArgumentNullException(nameof(from_display));
_comparer = comparer ?? EqualityComparer<T>.Default;
_actual_value = actual_value;
_conversion_data = conversion_data;
}
public event Action value_changed;
public uint actual_value {
public T actual_value {
get => _actual_value;
set {
if (_actual_value != value) {
if (!_comparer.Equals(_actual_value, value)) {
_actual_value = value;
value_changed?.Invoke();
}
}
}
public uint display_value {
get => _conversion_data.to_display(_actual_value);
set => actual_value = _conversion_data.from_display(value);
public T display_value {
get => _to_display(_conversion_data, _actual_value);
set => actual_value = _from_display(_conversion_data, value);
}
public QuantityConversionData conversion_data {
@@ -168,5 +196,13 @@ namespace mbts.ui {
default: return display_value;
}
}
public static double from_display(this QuantityConversionData quantity_conversion_data, double display_value) {
switch (quantity_conversion_data.op) {
case QuantityConversionData.OpType.DivideActual: return display_value * quantity_conversion_data.value;
case QuantityConversionData.OpType.MultiplyActual: return display_value.safe_divide(quantity_conversion_data.value);
default: return display_value;
}
}
}
}
@@ -259,6 +259,7 @@ namespace mbts.co2 {
}
}
// If Size is wrong remember to update from
[StructLayout(LayoutKind.Sequential, Size = 704)]
internal struct ColdEntry {
public int exchange_status;
@@ -0,0 +1,128 @@
using bts.api;
using bts.api.orderman;
using bts.api.position;
using bts.utils;
using mbts;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace bts.api {
static class ExchangeSpecificUitlities{
private static string _default_hub = "Henry";
static class ICE{
#region Contract Type
public static bool is_spread(SecurityImmutable sec){
return sec.HasSpread && sec.Ice.StripName.Contains("/");
}
public static bool is_strip(SecurityImmutable sec){
return sec.HasSpread && !is_spread(sec);
}
public static bool is_ice_lots(SecurityImmutable sec){
return sec.Ice.MarketDescription.Contains("ICE Lots");
}
public static bool is_tas(SecurityImmutable sec){
return sec.Ice.MarketDescription.Contains("TAS");
}
#endregion
#region CPP
public static double get_future_cpp (SecurityImmutable sec){
if(sec.Symbol == "HHD")
// only futures in HHD are the dailies, everything
// else is a strip or spread
return 1;
if(is_ice_lots(sec))
return 2500;
else{
var days_in_month = DateTime.DaysInMonth(Convert.ToInt32(sec.Future.ExpirationYear),
Convert.ToInt32(sec.Future.ExpirationMonth));
return days_in_month; // because the size is multiplied by 2500 already
}
}
public static double get_spread_cpp(SecurityImmutable sec){
return 0;
}
public static double get_strip_cpp(PNLUnitImmutable pnl,
SecurityImmutable sec){
// add up all the legs
double rv = 0;
foreach (var l in sec.Spread.Legs)
if(l.Security.HasFuture)
rv += get_cpp(pnl,l.Security);
return rv;
}
public static double get_cpp (PNLUnitImmutable pnl,
SecurityImmutable sec){
if(sec.HasFuture)
return get_future_cpp(sec);
else if(is_spread(sec))
return get_spread_cpp(sec);
else if(is_strip(sec))
return get_strip_cpp(pnl,sec);
return Double.NaN;
}
#endregion
#region Symbol
public static string get_pnl_symbol (SecurityImmutable sec){
// for pnl purposes, for a symbol, the division is :
// Futures and Strips
// ICE Lots
// Spreads
var rv = String.Format("{0}{1}{2}",
sec.Symbol,
is_ice_lots(sec)? " ICE LOTS" : String.Empty,
is_spread(sec)? " Spread" : String.Empty);
return rv;
}
#endregion
#region Formatting
public static string format_security (SecurityImmutable? security){
if(security.HasValue && security.Value.HasIce){
bool ice_lots = is_ice_lots(security.Value),
tas = is_tas(security.Value);
return String.Format("{0}{1}{2}{3}{4}",
security?.Symbol,
ice_lots? " ICE Lots" : String.Empty,
tas ? " TAS" : String.Empty,
String.Compare(security.Value.Ice.HubAlias, _default_hub,true)==0 ? String.Empty :
" " + security.Value.Ice.HubAlias,
" " + security?.Ice.StripName);
}
return String.Empty;
}
#endregion
}
public static double get_cpp (PNLUnitImmutable pnl,
SecurityImmutable sec){
if(sec.HasIce)
return ICE.get_cpp(pnl,sec);
return pnl.CashValue;
}
public static string get_pnl_symbol (SecurityImmutable sec){
if(sec.HasIce)
return ICE.get_pnl_symbol(sec);
return sec.Symbol;
}
public static string format_security(SecurityImmutable? security){
if(security?.HasIce == true)
return ICE.format_security(security);
return security?.ToString();
}
}}
@@ -1,10 +1,15 @@
using System;
using bts.api;
namespace bts.api
{
public static class SecurityExtensions
{
namespace bts.api {
public static class SecurityExtensions {
public static SecurityType security_type(this Security s) { return ((SecurityImmutable)s).security_type(); }
public static SecurityType security_type(this Security.ContractOneofCase contract_case) {
switch (contract_case) {
case Security.ContractOneofCase.Future: return SecurityType.Future;
case Security.ContractOneofCase.Option: return SecurityType.Option;
case Security.ContractOneofCase.Spread: return SecurityType.Spread;
case Security.ContractOneofCase.Synthetic: return SecurityType.Synthetic;
default: return SecurityType.Unset;
}
}
}
}
@@ -41,7 +41,7 @@ namespace bts.api {
switch (security?.ContractCase) {
case ContractOneofCase.Future:
if(security?.HasIce == true)
return _format_ICE(security);
return bts.api.ExchangeSpecificUitlities.format_security(security);
return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
case ContractOneofCase.Spread:
if (security?.HasGlbx == true) {
@@ -50,7 +50,7 @@ namespace bts.api {
return security?.Glbx.SecurityDesc;
}
else if(security?.HasIce == true)
return _format_ICE(security);
return bts.api.ExchangeSpecificUitlities.format_security(security);
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security?.Spread.Legs)));
case ContractOneofCase.Synthetic: return security?.Synthetic.Name;
default: return "Unknown-security";
@@ -84,28 +84,5 @@ namespace bts.api {
default: return $"{fl.quantity:+0;-0}{sym_exp_name}";
}
}
static string _format_ICE (SecurityImmutable? security){
string symbol = security?.Symbol,
strip = security?.Ice.StripName,
md_desc = security?.Ice.MarketDescription,
type_append = "";
if(md_desc.Contains("25k ICE Lots")){
if(md_desc.Contains("TAS"))
type_append = "TAS";
}
else if(md_desc.Contains("ICE Lots")){
if(md_desc.Contains("TAS"))
type_append = "ICE Lots TAS";
else
type_append = "ICE Lots";
}
else if(md_desc.Contains("TAS"))
type_append = "TAS";
type_append = string.IsNullOrEmpty(type_append) ?
" " : $" {type_append} ";
return $"{symbol}{type_append}{strip}";
}
}
}
+30 -19
View File
@@ -83,26 +83,37 @@ namespace bts.api {
}
public static int Compare(SecurityImmutable x, SecurityImmutable y) {
if (x.ContractCase == ContractOneofCase.Future) {
return y.ContractCase == ContractOneofCase.Future ? Compare(x.Symbol, x.Future, y.Symbol, y.Future) : -1;
} else if (y.ContractCase == ContractOneofCase.Future) {
return 1;
int c = non_deterministic_impl();
if (c == 0) {
c = x.ToString().CompareTo(y.ToString());
if (c == 0)
c = x.Id.CompareTo(y.Id);
}
var xlegs = get_flat_future_legs(x).ToList();
var ylegs = get_flat_future_legs(y).ToList();
int max = Math.Min(xlegs.Count, ylegs.Count);
for (int i = 0; i < max; i++) {
int rv = Compare(xlegs[i].symbol, xlegs[i].future, ylegs[i].symbol, ylegs[i].future);
if (rv != 0)
return rv;
rv = xlegs[i].quantity.CompareTo(ylegs[i].quantity);
if (rv != 0)
return rv;
rv = xlegs[i].exchange.CompareTo(ylegs[i].exchange);
if (rv != 0)
return rv;
return c;
// this has been observed to be non-determinisitc with certain ICE securities
// (it is determenistic with other exchanges..as far as we know).
int non_deterministic_impl() {
if (x.ContractCase == ContractOneofCase.Future) {
return y.ContractCase == ContractOneofCase.Future ? Compare(x.Symbol, x.Future, y.Symbol, y.Future) : -1;
} else if (y.ContractCase == ContractOneofCase.Future) {
return 1;
}
var xlegs = get_flat_future_legs(x).ToList();
var ylegs = get_flat_future_legs(y).ToList();
int max = Math.Min(xlegs.Count, ylegs.Count);
for (int i = 0; i < max; i++) {
int rv = Compare(xlegs[i].symbol, xlegs[i].future, ylegs[i].symbol, ylegs[i].future);
if (rv != 0)
return rv;
rv = xlegs[i].quantity.CompareTo(ylegs[i].quantity);
if (rv != 0)
return rv;
rv = xlegs[i].exchange.CompareTo(ylegs[i].exchange);
if (rv != 0)
return rv;
}
return xlegs.Count.CompareTo(ylegs.Count);
}
return xlegs.Count.CompareTo(ylegs.Count);
}
public static int Compare(string x_sym, FutureContract x, string y_sym, FutureContract y) {
@@ -116,4 +127,4 @@ namespace bts.api {
}
}
}
}
}
+1
View File
@@ -36,6 +36,7 @@ namespace bts {
Plugin,
Sdk,
Http,
iLink3
// Please keep this list in sync with logging.h/logging.cpp
}
@@ -7,6 +7,42 @@ using System.Reactive.Linq;
namespace System.Collections.Generic {
public static class EnumerableExtensions {
/// <summary>
/// Determines whether all elements of a sequence are true.
/// </summary>
/// <param name="source">An <see cref="IEnumerable{bool}"/> that contains the elements to which to apply the predicate.</param>
/// <returns>True if every element of the source sequence is true, or if the sequence is empty; otherwise false.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is null</exception>
public static bool All(this IEnumerable<bool> source) {
if (source is null)
throw new ArgumentNullException(nameof(source));
foreach (var v in source) {
if (!v)
return false;
}
return true;
}
/// <summary>
/// Similar to <see cref="Enumerable.All{TSource}(IEnumerable{TSource}, Func{TSource, bool})"/>, but returns false if the <paramref name="source"/> is empty.
/// </summary>
/// <typeparam name="T">The type of elements in the source.</typeparam>
/// <param name="source">The sequence of elements to which the predicate will be applied.</param>
/// <param name="predicate">The predicate applied to each element in the specified sequence.</param>
/// <returns>True if the source sequence is not empty, and every element within it passes the test in the specified predicate; otherwise, false.</returns>
public static bool AnyAndAll<T>(this IEnumerable<T> source, Func<T, bool> predicate) {
using (var en = source.GetEnumerator()) {
if (en.MoveNext()) {
do {
if (!predicate(en.Current))
return false;
} while (en.MoveNext());
return true;
}
return false;
}
}
#region Ordered
public static IOrderedEnumerable<T> Ordered<T>(this IEnumerable<T> enumerable) =>
enumerable.OrderBy(t => t);
@@ -165,6 +201,9 @@ namespace System.Collections.Generic {
}
}
public static IList<T> ToListLazy<T>(this IEnumerable<T> source) =>
new LazyList<T>(source);
public static bool TryGetFirst<T>(this IEnumerable<T> source, out T result) {
if (source == null) throw new ArgumentNullException(nameof(source));
using (var en = source.GetEnumerator()) {
@@ -590,6 +629,9 @@ namespace System.Collections.Generic {
public static bool is_null_or_empty<T>(this IEnumerable<T> source) =>
source == null || !source.Any();
public static bool is_null_or_empty<T>(this T[] array) =>
array == null || array.Length == 0;
public static IEnumerable<T> empty_if_null<T>(this IEnumerable<T> source) =>
source ?? Enumerable.Empty<T>();
@@ -634,5 +676,44 @@ namespace System.Collections.Generic {
public int? Count => _collection?.Count ?? _ro_collection?.Count;
}
class LazyList<T> : IList<T> {
IEnumerable<T> _source;
IList<T> _list_backing;
public LazyList(IEnumerable<T> source) =>
_source = source;
public T this[int index] { get => _list[index]; set => _list[index] = value; }
public int Count => _list.Count;
public bool IsReadOnly => _list.IsReadOnly;
IList<T> _list {
get {
if (_list_backing == null) {
if (_source == null) {
_list_backing = new List<T>(0);
} else {
_list_backing = new List<T>(_source);
_source = null;
}
}
return _list_backing;
}
}
public void Add(T item) => _list.Add(item);
public void Clear() => _list.Clear();
public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
public int IndexOf(T item) => _list.IndexOf(item);
public void Insert(int index, T item) => _list.Insert(index, item);
public bool Remove(T item) => _list.Remove(item);
public void RemoveAt(int index) => _list.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
}
}
}
@@ -11,6 +11,46 @@ using System.Reactive.Linq;
namespace bts.utils {
public static class UpdateExtensions {
public static IObservable<TableUpdate<T, TKey>> AppendTable<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, IEnumerable<T> append_items) where TKey : IEquatable<TKey> {
HashSet<TKey> append_keys = null;
Func<T, bool> append_key_filter = null;
return Observable.Create<TableUpdate<T, TKey>>(o => {
TableUpdate<T, TKey> output = null;
return source.Subscribe(table => {
if (output == null) {
// first update - initialize output, and (maybe) append_keys/append_key_filter
// Note: append_keys/filter can be shared among all subscriptions because, and only needs to be initialized once;
// once initialized, the append_keys/filter will not change.
output = table;
if (append_keys == null) {
append_keys = new HashSet<TKey>(append_items.Select(table.key_extractor));
append_key_filter = v => !append_keys.Contains(table.key_extractor(v));
}
// filter append_items out of the normal source (ensures that they will be immune to removes/changes).
output = TableUpdate<T, TKey>.FilterHelper.on_predicate_changed(output, table.state, null, append_key_filter);
// add the append_items to the output table (only need to do this once)
output = TableUpdate<T, TKey>.update(output, append_items, Enumerable.Empty<Change<T>>(), Enumerable.Empty<T>(), table.key_extractor);
output = output.as_snapshot();
} else {
output = TableUpdate<T, TKey>.FilterHelper.on_source_changed(
output, table.added, table.changes, table.removed, append_key_filter);
}
o.OnNext(output);
});
});
}
public static void FilterIncremental<T, TKey>(this TableUpdate<T, TKey> table, Func<T, bool> predicate, out IEnumerable<T> added, out IEnumerable<Change<T>> changes, out IEnumerable<T> removed) where TKey : IEquatable<TKey> {
// added
added = table.added.Where(predicate).Concat(
table.changes.Where(ch => predicate(ch.New) && !predicate(ch.Old)).Select(ch => ch.New));
// changes
changes = table.changes.Where(ch => predicate(ch.New) && predicate(ch.Old));
// removed
removed = table.removed.Where(predicate).Concat(
table.changes.Where(ch => !predicate(ch.New) && predicate(ch.Old)).Select(ch => ch.Old));
}
public static TableUpdate<T, TKey> with_added<T, TKey>(this TableUpdate<T, TKey> table, IEnumerable<T> added) where TKey : IEquatable<TKey> =>
TableUpdate<T, TKey>.update(table, added, Enumerable.Empty<Change<T>>(), Enumerable.Empty<T>(), table.key_extractor);
@@ -52,7 +92,8 @@ namespace bts.utils {
void update(TableUpdate<T, TKey> input) {
state = input.state;
var builder = output.to_builder();
// execute all parent_to_children changes first, so descendent-propagation will be efficient (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
// execute all parent_to_children changes first, so descendent-propagation will be efficient
// (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
foreach (var v in input.removed.Concat(input.changes.Select(ch => ch.Old))) {
var k = input.key_extractor(v);
parent_to_children.remove(parent_key(v), v);
+24 -13
View File
@@ -1,4 +1,5 @@
using bts.api;
using bts.collections;
using bts.resources;
using bts.test;
using bts.utils.proto;
@@ -10,7 +11,6 @@ namespace bts.mcore {
class SecurityTests {
[Test]
public void test_security_equal() {
SecurityImmutable a = new SecurityImmutable(new Security() { Id = 42 });
SecurityImmutable b = new SecurityImmutable(new Security() { Id = 42 });
SecurityImmutable c = new SecurityImmutable(new Security() { Id = 4 });
@@ -29,20 +29,31 @@ namespace bts.mcore {
[Test]
public void test_security_compare() {
const string securities_resource = "KrandallOrderBookCrash.security_proto";
// verify that the sample size doesn't shrink for some reason.
// if it grows a bit that's fine; the generic comparison test
// asserts against collections that are too large.
const int min_sample_size = 91;
foreach (var file_path in SecurityFiles.all())
_test_security_compare(_read_securities(file_path));
}
var securities = StreamHelper.ReadProtos<Security>(
ResourceHelper.LoadResource(securities_resource))
.Select(s => new SecurityImmutable(s))
.ToList();
Assert.IsTrue(securities.Count >= min_sample_size);
void _test_security_compare(IEnumerable<SecurityImmutable> securities) {
const int max_count = 2048;
var sec_list = securities as IList<SecurityImmutable> ?? securities.ToList();
if (sec_list.Count > max_count)
sec_list = sec_list.WrapSubRange(0, max_count);
ComparisonTests.transitive_comparison(sec_list, SecurityHelper.Comparer.InstanceImmutable);
ComparisonTests.identity_comparison(sec_list, SecurityHelper.Comparer.InstanceImmutable);
}
ComparisonTests.transitive_comparison(securities, SecurityHelper.Comparer.InstanceImmutable);
ComparisonTests.identity_comparison(securities, SecurityHelper.Comparer.InstanceImmutable);
IEnumerable<SecurityImmutable> _read_securities(string file_path) =>
StreamHelper.ReadProtos<Security>(ResourceHelper.LoadResource(file_path)).Select(s => new SecurityImmutable(s));
static class SecurityFiles {
public const string
KrandallOrderBookCrash = "KrandallOrderBookCrash.security_proto",
NonDeterministicICEFutures = "NonDeterministicICEFutures.security_proto";
public static IEnumerable<string> all() {
yield return KrandallOrderBookCrash;
yield return NonDeterministicICEFutures;
}
}
}
}
@@ -4,14 +4,14 @@ using System.Linq;
namespace bts.mcore {
static class ComparisonTests {
public static void transitive_comparison<T>(IEnumerable<T> items, IComparer<T> comparer) {
// Verify that the sample size isn't too big.
// This test compares each item to every other item
// (technically twice; lhs/rhs is flipped the second time),
// so performance is O(n^2).
const int max_sample_size = 4092;
/// <summary>
/// Compares every permutation of pairs in the specified items.
/// Be wary of use with large collections, as performance is O(n^2).
/// </summary>
public static void transitive_comparison<T>(IEnumerable<T> items, IComparer<T> comparer, int min_item_count = 2, int max_item_count = 5000) {
var sorted_items = items.ToList();
Assert.That(sorted_items.Count <= max_sample_size);
Assert.That(sorted_items.Count >= min_item_count);
Assert.That(sorted_items.Count <= max_item_count);
sorted_items.Sort(comparer);
for (int i = 0; i < sorted_items.Count - 1; i++) {
for (int j = i + 1; j < sorted_items.Count; j++) {
+1
View File
@@ -16,5 +16,6 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\KrandallOrderBookCrash.security_proto" />
<EmbeddedResource Include="Resources\NonDeterministicICEFutures.security_proto" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -1 +1 @@
set(sources files/cert_secure_logon.txt;files/futures.coconut.xz;files/onixs_ice/ConnectivityConfiguration.APITest.xml;files/onixs_ice/ConnectivityConfiguration.PerfTest.xml;files/onixs_ice/ConnectivityConfiguration.Production.xml;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
set(sources files/audit_ilink3.py;files/audit_upload.sh;files/audit_uploader.py;files/cert_secure_logon.txt;files/example.conf;files/futures.coconut.xz;files/onixs_ice/ConnectivityConfiguration.APITest.xml;files/onixs_ice/ConnectivityConfiguration.APITest.xml.orig;files/onixs_ice/ConnectivityConfiguration.PerfTest.xml;files/onixs_ice/ConnectivityConfiguration.Production.xml;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js)
+405
View File
@@ -0,0 +1,405 @@
import json
import csv
import datetime, time
import argparse, os
def valid_file(f):
if not os.path.exists(f):
# Argparse uses the ArgumentTypeError to give a rejection message like:
# error: argument input: x does not exist
raise argparse.ArgumentTypeError("{0} does not exist".format(f))
return f
def valid_date(s):
try:
return datetime.datetime.strptime(s, "%Y%m%d")
except ValueError:
raise argparse.ArgumentTypeError("Not a valid date: '{0}'.".format(s))
def print_headers():
# From https://www.cmegroup.com/confluence/display/EPICSANDBOX/iLink+3+-+Minimum+Acceptable+Audit+Trail+Elements+-+Data+Definitions+and+Validation+Rules
line_to_write = "Sending Timestamps,Message Direction,Operator ID,Session ID,Manual Order Indicator,Message Type,Instrument,Order Flow ID,\
CME Globex Order ID,Client Order ID,Order Request ID,Buy/Sell Indicator,Quantity,Limit Price,Stop Price,Order Type,Order Qualifier,IFM Flag,\
Display Quantity,Minimum Quantity,Country of Origin,Fill Price,Fill Quantity,Cumulative Quantity,Remaining Quantity,Aggressor Flag,Source of Cancellation,\
Reject Reason,CME Globex Message ID,Cross ID,Quote Request ID,Message Quote ID,Quote Set ID,Quote Entry ID,Bid Price,Bid Size,Offer Price,Offer Size,\
Processed Entries,Party Details List Request ID,ListUpdateAction,Self-Matching Prevention ID,Self-Match Prevention Instr,Customer Type Indicator,\
AvgPxGroupID,Average Px Ind,CmtaGiveUpCD,Origin,Clearing Trade Price Type,Customer Handling Instr,Executing Firm ID,Account Number,Take Up Firm,Take up Account"
with open (args.output_file, 'w') as the_file:
the_file.write(line_to_write)
the_file.write("\n")
def set_blanks():
return ""
def get_value(json_key):
prices = ["Price", "LastPx", "StopPx"]
if json_key in json_data:
if json_key in prices:
x = json_data[json_key]
return x['mantissa']/100000000000
else:
return json_data[json_key]
else:
return ""
# Column 1 - Sending Timestamps
def SendingTimeEpoch(epochtime): #1
#TIME = time.strftime('%Y%m%d-%H:%M:%S.{}'.format(epochtime/1000000000), time.gmtime(epochtime/1000000000))
TIME = datetime.datetime.fromtimestamp(int(epochtime) / 1e9).strftime('%Y%m%d-%H:%M:%S.%f')
return TIME
# Column 2 - Message Direction
def direction(whichway):
if whichway == '<':
DIRECTION = "FROM CME"
else:
DIRECTION = "TO CME"
return DIRECTION
# Column 3 - Operator ID
def SenderID(sender):
if "SenderID" in json_data:
SENDER = json_data["SenderID"]
return SENDER
else:
return SESSIONID
# Column 4 - Session ID
# Column 5 - Manual Order Indicator
def ManualOrderIndicator(manual):
if "ManualOrderIndicator" in json_data:
if manual == "Manual":
return "1"
else:
return "0"
# Column 6 - Message Type
def MessageType(msg_type,orderstatus):
if msg_type == "NewOrderSingle514":
return "D"
elif msg_type == "ExecutionReportNew522":
return "8--0"
if "ExecutionReportTrade" in msg_type:
if orderstatus == "PartiallyFilled":
return "8--1"
else:
return "8--2"
elif msg_type == "ExecutionReportCancel534":
return "8--4"
elif msg_type == "ExecutionReportModify531":
return "8--5"
elif msg_type == "ExecutionReportCancel534":
return "8--H"
elif msg_type == "ExecutionReportReject523":
return "8--8"
elif msg_type == "OrderCancelReplaceReject536":
return "9--2"
elif msg_type == "OrderCancelReject535":
return "9--1"
elif msg_type == "PartyDetailsDefinitionRequest518":
return "CX"
elif msg_type == "PartyDetailsDefinitionRequestAck519":
return "CY"
elif msg_type == "OrderCancelRequest516":
return "F"
elif msg_type == "OrderCancelReplaceRequest515":
return "G"
# Column 12 - Buy-Sell Indicator/Side
def Side(indicator):
if "Side" in json_data:
if indicator == "Buy":
return "1"
else:
return "2"
# Column 16 - Order Type/OrdType
def OrdType(ordertype):
if "OrdType" in json_data:
if ordertype == "MarketWithProtection" or ordertype == "MarketwithProtection":
return "1"
elif ordertype == "Limit":
return "2"
elif ordertype == "StopWithProtection" or ordertype == "StopwithProtection":
return "3"
elif ordertype == "StopLimit":
return "4"
elif ordertype == "MarketLimit":
return "K"
else:
return ""
# Column 17 - Order Qualifier/TimeInForce
def TimeInForce(orderqual):
if "TimeInForce" in json_data:
if orderqual == "Day":
return "0"
elif orderqual == "GTC":
return "1"
elif orderqual == "FAK":
return "3"
elif orderqual == "GTD":
return "6"
else:
return ""
# Column 18 IFM Flag/OFMOverride
def OFMOverride(imfflag):
if "OFMOverride" in json_data:
if imfflag == "Enabled":
return "1"
else:
return "0"
# Column 19 Display Quantity/DisplayQty
def DisplayQty(qty):
if "DisplayQty" in json_data:
if str(qty) == "0":
return ""
elif str(qty) == "4294967295":
return ""
else:
return qty
# Column 26 - Aggressor Flag/AggressorIndicator
def AggressorIndicator(aggressor):
if "AggressorIndicator" in json_data:
if aggressor == "True":
return "1"
else:
return "0"
# Column 27 - Source of Cancellation/ExecRestatementReason
def ExecRestatementReason(sourcecancel):
if "ExecRestatementReason" in json_data:
if sourcecancel == "Exchange":
return "8"
elif sourcecancel == "CancelledNotBest":
return "9"
elif sourcecancel == "CancelOnDisconnect":
return "100"
elif sourcecancel == "CancelRestingDueToSelfMatchPrevention":
return "103"
elif sourcecancel == "CancelFromCMEGlobexCreditControlsViolation":
return "104"
elif sourcecancel == "CancelFromCMEOne":
return "105"
elif sourcecancel == "CancelFromRiskManagementAPI":
return "106"
elif sourcecancel == "CancelAggressingDueToSelfMatchPrevention":
return "107"
elif sourcecancel == "CancelDueToVolatilityQuoteOptionLessThanMinOrderSize":
return "108"
else:
return ""
# Column 43 - Self-Match Prevention Instr/SelfMatchPreventionInstruction
def SelfMatchPreventionInstruction(instr):
if "SelfMatchPreventionInstruction" in json_data:
if instr == "CancelOldest":
return "O"
elif instr == "CancelNewest":
return "N"
else:
return ""
# Column 44 - Customer Type Indicator/CustOrderCapacity
def CustOrderCapacity(custord):
if "CustOrderCapacity" in json_data:
if custord == "NULL_VALUE":
return ""
elif custord == "Membertradingfortheirownaccount":
return "1"
elif custord == "ClearingFirmtradingforitsproprietaryaccount":
return "2"
elif custord == "Membertradingforanothermember":
return "3"
else:
return "4"
# Column 46 - Average Px Ind/AvgPxInd
def AvgPxInd(avgpxindicator):
if "AvgPxInd" in json_data:
if avgpxindicator == "NULL_VALUE":
return ""
else:
return avgpxindicator
# Column 48 - Origin/ClearingAccountType
def ClearingAccountType(accounttype):
if "ClearingAccountType" in json_data:
if accounttype == "Customer":
return "0"
elif accounttype == "Firm":
return "1"
else:
return ""
# Column 49 - Clearing Trade Price Type/ClearingTradePriceType
def ClearingTradePriceType(clrtrdprice):
if "ClearingTradePriceType" in json_data:
if clrtrdprice == "NULL_VALUE":
return "0"
else:
return "3"
# Column 50 - Customer Handling Instr/CustOrderHandlingInst
def CustOrderHandlingInst(custorderinst):
if "CustOrderHandlingInst" in json_data:
if custorderinst == "ClientElectronic":
return "C"
elif custorderinst == "TEST":
return "D"
else:
return ""
# Column 51 and 52 - Getting the Firm and Account Number
def partyid(partydetailid,column):
for x in partydetailid:
role = x['PartyDetailRole']
id = x['PartyDetailID']
if column == "51" and role == "ExecutingFirm":
return id
if column == "52" and role == "CustomerAccount":
return id
parser = argparse.ArgumentParser()
parser.add_argument("json_log", help="the json log[.gz] you wish to process", type=valid_file)
parser.add_argument("output_file", help="the destination csv file you wish to write")
parser.add_argument("-d", "--date", help="the date you wish to parse from the log, specified as YYYYmmdd",
default=datetime.datetime.today().strftime("%Y%m%d"), type=valid_date)
args = parser.parse_args()
print_headers()
list_types = ['NewOrderSingle514', 'OrderCancelReplaceRequest515', 'OrderCancelRequest516', 'PartyDetailsDefinitionRequest518', 'PartyDetailsDefinitionRequestAck519', \
'ExecutionReportNew522', 'ExecutionReportReject523', 'ExecutionReportTradeOutright525', 'ExecutionReportTradeSpread526', 'ExecutionReportTradeSpreadLeg527', \
'ExecutionReportModify531', 'ExecutionReportCancel534', 'OrderCancelReject535', 'OrderCancelReplaceReject536', ]
# Note that the timedeltas specificed in trade_day_start and trade_day_end
# are based on Chicago times; as such, this script should run on a machine
# set to a Chicago timezone. A future impromovement would be to determine
# the current machine's timezone and determine the appropriate offset
# versus Chicago time, but for now we assume this will run on a Chicago
# tz machine.
input_as_datetime = args.date
trade_day_start = input_as_datetime - datetime.timedelta(days=1) + datetime.timedelta(hours=16, minutes=30)
trade_day_utc_start = time.gmtime((time.mktime(trade_day_start.timetuple())))
trade_day_end = input_as_datetime + datetime.timedelta(hours=16)
trade_day_utc_end = time.gmtime((time.mktime(trade_day_end.timetuple())))
file_opener = None
if args.json_log.endswith(".gz"):
import gzip
file_opener = gzip.open
else:
file_opener = open
#inputFile = open(fileInput) #open json file
#outputFile = open(fileOutput, 'w') #load csv file
# Get Session
with file_opener(args.json_log) as json_file:
for line in json_file:
data = json.loads(line)
json_data = data['msg_json']
if "Session" in json_data:
SESSIONID = json_data["Session"]
#inputFile = open(fileInput) #open json file
#outputFile = open(fileOutput, 'w') #load csv file
with file_opener(args.json_log) as json_file:
for line in json_file:
data = json.loads(line)
line_timestamp = time.strptime(data['time'], "%Y%m%d-%H:%M:%S.%f")
if trade_day_utc_start <= line_timestamp <= trade_day_utc_end:
json_data = data['msg_json']
NAME = json_data["Name"]
if NAME in list_types:
EXECUTING_FIRM_ID = ""
ACCOUNT_NUMBER = ""
DIRECTION = direction(data['direction'])
# Set Blanks
blank_json = {'QuoteReqID': '', 'QuoteID': '', 'QuoteSetID': '', 'QuoteEntryID': '', 'BidPx': '', 'BidSize': '', 'OfferPx': '', \
'OfferSize': '', 'NoProcessedEntries': '', 'ListUpdateAction': '', 'MinQty': '', 'AvgPxGroupID': '', \
'CmtaGiveUpCD': '', 'TAKE_UP_FIRM': '', 'TAKE_UP_ACCOUNT': '',}
# No Data Processing
straight_json = {'SecurityID': '', 'ClOrdID': '', 'OrderID': '', 'OrderRequestID': '', 'OrderQty': '', \
'Location': '', 'LastQty': '', 'CumQty': '', 'LeavesQty': '', 'Text': '', 'SideTradeID': '', 'CrossID': '', 'SelfMatchPreventionID': '', \
'Price': '', 'StopPx': '', 'LastPx': '', 'PartyDetailsListReqID': '', 'OrdStatus': ''}
# Processing Data
process_json = {'SendingTimeEpoch': '', 'ManualOrderIndicator': '', 'SenderID': '', 'Side': '', 'OrdType': '', 'TimeInForce': '', \
'OFMOverride': '', 'AggressorIndicator': '', 'ExecRestatementReason': '', 'SelfMatchPreventionInstruction': '', 'CustOrderCapacity': '', \
'AvgPxInd': '', 'ClearingAccountType': '', 'ClearingTradePriceType': '', 'CustOrderHandlingInst': '', 'DisplayQty': ''}
for d in straight_json:
test = get_value(d)
if test != "None" or test != "":
if "," in str(test):
test = test.replace(",",";")
if "4294967295" in str(test):
test = ""
if "us;" in str(test):
test = test.upper()
if d == "SelfMatchPreventionID" and len(str(test)) > 8:
test = ""
straight_json[d] = test
else:
straight_json[d] = ''
for d in process_json:
if d in json_data:
function = locals()[d]
test = function(json_data[d])
if test != "None":
process_json[d] = test
else:
process_json[d] = ''
elif d == "SenderID":
function = locals()[d]
test = function(SenderID)
if test != "None":
process_json[d] = test
# Party Message Fields
# Column 51 - Executing Firm ID
if "NoPartyDetails" in json_data:
EXECUTING_FIRM_ID = partyid(json_data['NoPartyDetails'], "51")
# Column 52 - Party Account Number
ACCOUNT_NUMBER = partyid(json_data['NoPartyDetails'], "52")
line_to_write = process_json['SendingTimeEpoch'] + "," + DIRECTION + "," + process_json['SenderID'] + "," + SESSIONID + "," + process_json['ManualOrderIndicator'] + "," + \
MessageType(NAME,straight_json['OrdStatus']) + "," + str(straight_json['SecurityID']) + "," + str(straight_json['ClOrdID']) + "," + str(straight_json['OrderID']) + "," + str(straight_json['ClOrdID']) + "," + \
str(straight_json['OrderRequestID']) + "," + process_json['Side'] + "," + str(straight_json['OrderQty']) + "," + str(straight_json['Price']) + "," + \
str(straight_json['StopPx']) + "," + process_json['OrdType'] + "," + process_json['TimeInForce'] + "," + process_json['OFMOverride'] + "," + str(process_json['DisplayQty']) + "," + \
blank_json['MinQty'] + "," + straight_json['Location'] + "," + str(straight_json['LastPx']) + "," + str(straight_json['LastQty']) + "," + str(straight_json['CumQty']) + "," + \
str(straight_json['LeavesQty']) + "," + process_json['AggressorIndicator'] + "," + process_json['ExecRestatementReason'] + "," + str(straight_json['Text']) + "," + \
str(straight_json['SideTradeID']) + "," + str(straight_json['CrossID']) + "," + blank_json['QuoteReqID'] + "," + blank_json['QuoteID'] + "," + blank_json['QuoteSetID'] + "," + \
blank_json['QuoteEntryID'] + "," + blank_json['BidPx'] + "," + blank_json['BidSize'] + "," + blank_json['OfferPx'] + "," + blank_json['OfferSize'] + "," + \
blank_json['NoProcessedEntries'] + "," + str(straight_json['PartyDetailsListReqID']) + "," + blank_json['ListUpdateAction'] + "," + str(straight_json['SelfMatchPreventionID']) + "," + \
process_json['SelfMatchPreventionInstruction'] + "," + process_json['CustOrderCapacity'] + "," + blank_json['AvgPxGroupID'] + "," + process_json['AvgPxInd'] + "," + \
blank_json['CmtaGiveUpCD'] + "," + str(process_json['ClearingAccountType']) + "," + process_json['ClearingTradePriceType'] + "," + process_json['CustOrderHandlingInst'] + "," + \
EXECUTING_FIRM_ID + "," + str(ACCOUNT_NUMBER) + "," + blank_json['TAKE_UP_FIRM'] + "," + blank_json['TAKE_UP_ACCOUNT']
#print(line_to_write)
with open (args.output_file, 'a') as the_file:
the_file.write(line_to_write)
the_file.write("\n")
+73
View File
@@ -0,0 +1,73 @@
#/bin/bash
function help_and_exit()
{
echo
echo Usage: $0 [option]... config_file
echo
echo Can also be used in cron like the following
echo 40 16 * * 1-5 /usr/bin/docker exec spark_10000 bash /sparkbin/data_files/audit_upload.sh /sparkdata/data/audit_uploads/example.conf
echo example.conf included. The location of the conf file should be relative to the docker container, not the host
echo
echo Options are as follows:
echo " -d|--date YYYYMMDD Example is 20180731"
echo " Help: "
echo " -h|--help Display this message."
echo
exit 1
}
DATE=$(date +%Y%m%d)
ORIG_DIR=$(pwd)
ERROR_LEVEL="0"
while getopts "d:h" option; do
case $option in
d) DATE=$OPTARG ;;
h) help_and_exit;;
\?) echo "Invalid option: -$OPTARG" >&2;
exit 1;;
esac
done
shift $((OPTIND-1))
CONFIG_FILE=$1
declare -A conf_vars; while read line; do conf_vars[${line%=*}]=${line#*=}; done <$CONFIG_FILE
SESSION_ID=${conf_vars[session_id]}
FCM=${conf_vars[fcm]}
CLIENT=${conf_vars[client]}
cd /sparkbin/data_files
for SESSION in $(ls /sparkdata/data/ilink3/); do
if [[ $SESSION == *$SESSION_ID* ]]; then
echo "Session is $SESSION"
SESSION_DAY=$(date +%Y-%m-%d)
if [[ $SESSION == "cme_"* ]]; then
SESSION_DAY=$(date -d "$DATE -$(date -d $DATE +%u) days" +"%Y-%m-%d")
AUDIT_SCRIPT="audit_ilink3.py"
fi
echo $SESSION_DAY
if [[ ! -d /sparkdata/data/ilink3/$SESSION/audit_logs ]]; then
mkdir -p /sparkdata/data/ilink3/$SESSION/audit_logs
fi
if [[ -d /sparkdata/data/ilink3/$SESSION/$SESSION_DAY ]]; then
echo "Starting the creation of the audit log"
python $AUDIT_SCRIPT -d $DATE /sparkdata/data/ilink3/$SESSION/$SESSION_DAY/log /sparkdata/data/ilink3/$SESSION/audit_logs/"$FCM"_audit_"$SESSION"_"$DATE".csv
fi
if [[ ! -d /sparkdata/data/md_audit ]]; then
mkdir /sparkdata/data/md_audit
fi
if [[ -d /sparkdata/data/md_audit ]]; then
# Running the MD Audit csv generation
cd /sparkbin
./sparkd adminreport md > /sparkdata/data/md_audit/"$FCM"_md_"$CLIENT"_"$DATE".csv
cd /sparkbin/data_files
fi
echo "Uploading to the (s)FTP Server"
python audit_uploader.py --config_file="$CONFIG_FILE" --datestamp $DATE --session="$SESSION" --client="$CLIENT" --fcm="$FCM"
fi
done
+150
View File
@@ -0,0 +1,150 @@
import getopt, sys, datetime
import paramiko, ftplib
#########################################################
# Commandline Args #
#########################################################
def usage():
print "-------------------------------------------------------"
print "-Usage: -"
print "-------------------------------------------------------"
print "- --config_file= : Path to upload config file -"
print "- -if no path is specified look -"
print "- in the working directory. -"
print "- -"
print "- --help : Print this message -"
print "- -"
print "- --datestamp= : Optional, defaults to today -"
print "- -replaces *datestamp in the -"
print "- the upload config file -"
print "- -"
print "- --fcm : Optional, spark input -"
print "- -"
print "- --session : Optional, spark jf session -"
print "-------------------------------------------------------"
fcm = ''
session = ''
try:
options, arguments = getopt.getopt(sys.argv[1:],"h",['config_file=', 'datestamp=', 'help', 'fcm=', 'client=', 'session='])
except getopt.GetoptError, err:
print str(err)
usage()
sys.exit(2)
for option, value in options:
if option in ("-h","--h","-help","--help"):
usage()
sys.exit()
elif option in ("-config_file","--config_file"):
config_file = value
elif option in ("-datestamp","--datestamp"):
datestamp = value
elif option in ("-fcm","--fcm"):
fcm = value
elif option in ("-client","--client"):
client = value
elif option in ("-session","--session"):
session = value
for argument in arguments:
if argument in ("h", "help"):
usage()
sys.exit()
else:
print "Unrecognized commandline argument. Type help for usage."
sys.exit()
if len(options) <= 0:
print "Requires a config file to run."
usage()
sys.exit()
#########################################################
# Read Config Function #
#########################################################
def read_config_file(config_file, datestamp=datetime.datetime.today().strftime('%Y%m%d'), fcm=fcm, session=session):
'''
Parse our config file that gets passed in as a commandline parameter.
All config keys must be of the form key=value. White space is ignored.
All config keys must be seperated by a newline character.
Called during initialization.
'''
upload_config = {}
config_file = open(config_file, 'r')
config_file = config_file.read()
for config in config_file.split("\n"):
if '=' in config:
cleaned_key = config.split('=')[0].strip().upper()
cleaned_value = config.split('=')[1].strip()
if '*datestamp*' in cleaned_value:
cleaned_value = cleaned_value.replace('*datestamp*',datestamp)
if '*fcm*' in cleaned_value:
cleaned_value = cleaned_value.replace('*fcm*',fcm)
if '*client*' in cleaned_value:
cleaned_value = cleaned_value.replace('*client*',client)
if '*session*' in cleaned_value:
cleaned_value = cleaned_value.replace('*session*',session)
upload_config[cleaned_key] = cleaned_value
else:
pass
return upload_config
#########################################################
# FTP File Transfer Function #
#########################################################
def putfile_ftp(file, put_path, site, user, password, port=21, verbose=True):
if verbose: print 'Uploading', file
local = open(file, 'rb')
remote = ftplib.FTP(site)
remote.login(user, password)
remote.storbinary(put_path, local, 1024)
remote.quit()
local.close()
if verbose: print 'Upload done.'
#########################################################
# SFTP File Transfer Function #
#########################################################
def putfile_sftp(file, put_path, site, username, password, port=22, verbose=True):
transport = paramiko.Transport((site, port))
transport.connect(username = username, password = password)
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(file, put_path)
sftp.close()
transport.close()
#########################################################
# Main #
#########################################################
#if datestamp != None:
# upload_config = read_config_file(config_file, datestamp)
#else:
# upload_config = read_config_file(config_file)
try:
datestamp
except NameError:
upload_config = read_config_file(config_file)
else:
upload_config = read_config_file(config_file, datestamp)
print upload_config
if upload_config["PROTOCOL_PUT"] in ['FTP','ftp']:
print "Protocol = FTP"
putfile_ftp(upload_config["GET_ILINK3_FILE_PATH"] + upload_config["GET_ILINK3_FILE_NAME"], upload_config["PUT_ILINK3_FILE_PATH"] + upload_config["PUT_ILINK3_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], upload_config["PORT_PUT"] )
putfile_ftp(upload_config["GET_MD_FILE_PATH"] + upload_config["GET_MD_FILE_NAME"], upload_config["PUT_MD_FILE_PATH"] + upload_config["PUT_MD_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], upload_config["PORT_PUT"] )
elif upload_config["PROTOCOL_PUT"] in ['SFTP','sftp']:
print "Protocol = SFTP"
putfile_sftp(upload_config["GET_ILINK3_FILE_PATH"] + upload_config["GET_ILINK3_FILE_NAME"], upload_config["PUT_ILINK3_FILE_PATH"] + upload_config["PUT_ILINK3_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], int(upload_config["PORT_PUT"]) )
putfile_sftp(upload_config["GET_MD_FILE_PATH"] + upload_config["GET_MD_FILE_NAME"], upload_config["PUT_MD_FILE_PATH"] + upload_config["PUT_MD_FILE_NAME"], upload_config["PUT_HOST"], upload_config["USERNAME_PUT"], upload_config["PASSWORD_PUT"], int(upload_config["PORT_PUT"]) )
+19 -12
View File
@@ -1,12 +1,19 @@
Session ID Access ID Secret Key Creation Date Expiration Date Environment
M4D lLtjH74y1Kh5XUJTLyH2 f1K_tROQp2lTIplVp34WxKa7xDUgfFnIteolfik061o 2019-09-03 2020-09-03 CERT
MC1 VEMlkDuLz74OVHSuacQB IuHyYT3b-9KUo3WsuXykt-fXsjzp7L-JOrdCSQZLNl0 2019-09-03 2020-09-03 CERT
MC5 77lzSUwPLlM9obaXh2mK z6aA9qfASDKfZkYTVKFqJd-78i0HZ2Q9ec3CKCYIqHg 2019-09-03 2020-09-03 CERT
MC7 RphzI3rn71oGUEiYnX9e y6OvvPPyoyj2S8YAnE1gFduXT8v29HIMjO5EngYxSNg 2019-09-03 2020-09-03 CERT
MC8 PYBIm2TI9gFPIxmHbuYl 6o-ZolGH2DdeQOlGtJ5Zqoh_kg7VdbxkmjeOHmigKwU 2019-09-03 2020-09-03 CERT
Session ID Access ID Secret Key Creation Date Expiration Date Environment
M4D lLtjH74y1Kh5XUJTLyH2 f1K_tROQp2lTIplVp34WxKa7xDUgfFnIteolfik061o 2019-09-03 2020-09-03 CERT
MC1 VEMlkDuLz74OVHSuacQB IuHyYT3b-9KUo3WsuXykt-fXsjzp7L-JOrdCSQZLNl0 2019-09-03 2020-09-03 CERT
MC5 77lzSUwPLlM9obaXh2mK z6aA9qfASDKfZkYTVKFqJd-78i0HZ2Q9ec3CKCYIqHg 2019-09-03 2020-09-03 CERT
MC7 RphzI3rn71oGUEiYnX9e y6OvvPPyoyj2S8YAnE1gFduXT8v29HIMjO5EngYxSNg 2019-09-03 2020-09-03 CERT
MC8 PYBIm2TI9gFPIxmHbuYl 6o-ZolGH2DdeQOlGtJ5Zqoh_kg7VdbxkmjeOHmigKwU 2019-09-03 2020-09-03 CERT
NGG RYcigz3bSLIIBBF2A2Hq qB1dm9VkTKeXcdQpFzla8B3_jwTgya_-Fk5iyEJvr2E 2020-06-15 2021-06-15 NEWRELEASE
NGF o4OTmmTLQ7Lv4g6bCm26 HbD3FneMyb9Fu-FkCDLscCczHqFcFE2PyWv978RXGyA 2019-12-20 2020-12-20 NEWRELEASE
UZE EeFdZ1FPepk6U4k3xBZA 2LjEtdLvBLLB1QuDJ4KiJW6M4dF6IHWd_UKmo7p3_Us 2020-06-24 2021-06-24 NEWRELEASE
VAE Y8iqatTewuoHX16gghw8 FNcCB3Ks1qleS56quYzH4V7zAu78YY2bCdLRi9tmU9E 2020-06-24 2021-06-24 NEWRELEASE
+16
View File
@@ -0,0 +1,16 @@
fcm=FCM
client=NAME
session_id=ses
put_host=ftpc.example.com
port_put=2222
username_put=user
password_put=password
protocol_put=sftp
get_ilink3_file_path=/sparkdata/data/ilink3/*session*/audit_logs/
put_ilink3_file_path=/
get_ilink3_file_name=*fcm*_audit_*session*_*datestamp*.csv
put_ilink3_file_name=*fcm*_audit_*session*_*datestamp*.csv
get_md_file_path=/sparkdata/data/md_audit/
put_md_file_path=/
get_md_file_name=*fcm*_md_*client*_*datestamp*.csv
put_md_file_name=*fcm*_md_*client*_*datestamp*.csv
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metricsLine truncated
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/AlgoTypes.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cLine truncated
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthSessionManager.cpp;utils/AuthToken.cLine truncated
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/AlgoTypes.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/Product.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityMapper.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/service/MarketDataServiceServer.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;md/vendor/OnixsUtils.cpp;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthSessioLine truncated
+1 -1
View File
@@ -319,7 +319,7 @@ if __name__ == "__main__":
if DEBUG:
for f in response.file:
print f.content
print (f.content)
else:
output = response.SerializeToString()
sys.stdout.write(output)
@@ -580,7 +580,9 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(const bt
rv.set_session(conn_ptr->name());
rv.set_operator_id(cme_user_data->operator_id());
rv.set_location(cme_user_data->location());
rv.set_smp_id(cme_user_data->smp_id());
if (cme_user_data->smp_instruction()== "O")
rv.set_smp_instruction(api::orderman::SelfMatchPreventionInstruction::SELF_MATCH_PREVENTION_INSTRUCTION_CANCEL_RESTING);
else if (cme_user_data->smp_instruction()== "N")
@@ -80,6 +80,10 @@ void PositionUtilities::apply_fill (PositionEntry& pos,
void PositionUtilities::apply_fill (PositionEntry& pos,
const Fill& f,
bool is_add){
// strip legs are not applied to positions
if(f.fill_kind() == Fill::FILL_KIND_STRIP_LEG ||
f.fill_kind() == Fill::FILL_KIND_SOD_STRIP_LEG)
return;
PositionUtilities::apply_fill(pos,
f.side(),
f.last_price(),
+7 -1
View File
@@ -331,6 +331,9 @@ message Fill {
FILL_KIND_STANDARD = 0;
FILL_KIND_LEG = 1;
FILL_KIND_SOD = 2;
FILL_KIND_STRIP_LEG = 3;
FILL_KIND_SOD_SPREAD_LEG = 4;
FILL_KIND_SOD_STRIP_LEG = 5;
}
FillKind fill_kind = 10;
@@ -383,7 +386,9 @@ message NativeOrderState {
RoutingRecord routing_record = 5;
NativeOrderType native_type = 6;
string last_transact_time = 7;
uint64 last_transact_time_unix_ns = 9;
double stop_px = 8;
uint64 exchange_order_id_u64 = 10;
}
// Custom order states
@@ -452,7 +457,8 @@ enum TimeInForce {
TIME_IN_FORCE_UNDEFINED = 0;
TIME_IN_FORCE_DAY = 48;
TIME_IN_FORCE_GTC = 49;
TIME_IN_FORCE_IOC = 51;
TIME_IN_FORCE_IOC = 51; // FAK
TIME_IN_FORCE_DATE = 52;
}
enum ManualOrderIndicator {
+2 -1
View File
@@ -43,7 +43,8 @@ message PositionUnit{
message PNLUnit{
int32 record_id = 1 [(bts.api.key)=true];
PositionUnit pos = 10;
double cash_value = 20;
double cash_value = 20;
CppOverride cpp_override = 21;
double bid_price = 30;
double ask_price = 40;
double last_price = 50;
+6
View File
@@ -45,6 +45,12 @@ CMEOptions {
}
}
ICEPublisherOptions {
value {
network_interface: "eth1"
}
}
#to make this work in dev clone the operations repo
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
#modify the easyTunnelProxy.properties file to replace
File diff suppressed because it is too large. Load diff
+22
View File
@@ -0,0 +1,22 @@
syntax='proto2';
package bts.config;
message AlgoTypeContext {
repeated string exchanges = 1;
repeated string symbols = 2;
repeated string security_types = 3;
}
message AlgoTypeDef {
optional string name = 1;
optional bool is_attachable = 2 [default = false];
}
message AlgoTypeRule {
optional AlgoTypeContext context = 1;
repeated AlgoTypeDef algos = 2;
}
message AlgoTypeRuleSet {
repeated AlgoTypeRule rules = 1;
}
+5
View File
@@ -103,6 +103,7 @@ message CMEOptions
// Defines amount of time Feed Engine keeps cycling before going to sleep when no useful activity can be done
optional uint32 spin_time = 4 [default = 1];
optional uint32 channels_per_thread = 5 [default = 3];
// rmem_max
optional uint32 udp_socket_buffer_size = 11 [default = 268435456]; //256mb
}
message CMEChannel {
@@ -110,9 +111,13 @@ message CMEOptions
optional string feed_engine = 3 [default = "default"];
// for re-orders
optional uint32 max_gap_recovery_cache_size = 11 [default = 6];
// for re-orders
optional uint32 lost_packet_wait_timeout_ms = 12 [default = 10];
optional OnixSLogMode onixs_log_mode = 13 [default = Important];
// If using both feeds prefer the B feed for snapshots
optional bool prefer_backup_feeds = 15 [default = false];
optional bool use_tcp_replay = 20 [default = true];
Loaded 100 of 344 files, more files were not shown because too many files have changed in this diff. Show more