Compare commits

...
161 Commits
Author SHA1 Message Date
Jake Norbut 33a71f5f47 Merged in SP-105_account-in-ladder (pull request #294)
RTM - SP-105 selected account in ladder

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 22:21:46 +00:00
Pedro Pinto 2940601f4b Updated release notes 2019-07-08 18:15:31 -04:00
Pedro Pinto 1f7939f49a Merge branch 'SP-105_account-in-ladder' of bitbucket.org:bluetradesys/spark into SP-105_account-in-ladder 2019-07-08 18:14:45 -04:00
Pedro Pinto 48e273a607 Merge remote-tracking branch 'origin' into SP-105_account-in-ladder 2019-07-08 17:50:43 -04:00
John Colvin e35634020d Merged in SP-89-offset-algo (pull request #286)
RTM SP-89 offset algo

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:49:12 +00:00
Pedro Pinto 1ede053f59 Merged with master 2019-07-08 17:48:16 -04:00
Pedro Pinto 93824786c7 Updated release notes. 2019-07-08 17:32:33 -04:00
Pedro Pinto 25b8c0a728 Merged with master 2019-07-08 17:31:22 -04:00
Jake Norbut 0e4fe1b47e filter the SelectorProperty source for updates that actually have items; this keeps the currently selected account through server restarts 2019-07-08 16:29:19 -05:00
Jake Norbut cd744963ff Revert "don't revert to default selected setter when server reboots"
This reverts commit d5fdcc6796.
2019-07-08 16:21:46 -05:00
Kevin Darby fb9d877938 Merged in dme (pull request #293)
RTM add mic for DME

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:08:53 +00:00
Pedro Pinto 087b001510 Updated release notes. 2019-07-08 17:03:56 -04:00
Pedro Pinto 02a7c0ddca Merge branch 'master' into dme 2019-07-08 17:01:50 -04:00
Kevin Darby 6afa1d581d Merged in span (pull request #263)
RTM: Span

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 21:01:01 +00:00
Taha Afzal 5e04abe573 Combining the account lookup for loss_limitm and span 2019-07-08 16:50:30 -04:00
Pedro Pinto 3422a44c87 Updated release notes. 2019-07-08 16:49:32 -04:00
Jake Norbut d5fdcc6796 don't revert to default selected setter when server reboots 2019-07-08 15:37:38 -05:00
Kevin Darby f12217d76c Remove mapping of DME mic per Pedro 2019-07-08 20:12:47 +00:00
Pedro Pinto d85a914046 Merge branch 'master' into span 2019-07-08 15:58:06 -04:00
Pedro Pinto 4fdc890cb5 Merged in ticks_off (pull request #296)
RTM Added ticks_off to top algo server side.

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-07-08 19:55:01 +00:00
Pedro Pinto 3ed0bd9052 Merged with master 2019-07-08 15:54:39 -04:00
Kevin Darby 05e09cb977 Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-07-08 14:33:37 -04:00
Eric Harding dab4f5acbb Merged in SP-104-HCL (pull request #289)
[RTM] SP-104 HCL

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 18:28:44 +00:00
Eric Harding a20c53b23d Merged in SP-68-crash (pull request #292)
[RTM] SP-68 Fix crash on exit by not touching queue on windows.  insert_or_replace() is a no-op on windows anyway.

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 18:27:59 +00:00
Pedro Pinto 8206f457b5 Updated release notes. 2019-07-08 14:27:37 -04:00
Pedro Pinto d4e6d6293d Merge branch 'master' into SP-68-crash 2019-07-08 14:26:48 -04:00
Kevin Darby d1a2197ba4 Merged in fix_auth_tests2 (pull request #297)
RTM fix auth tests to respect difference betwen admin and trader login
2019-07-08 18:20:09 +00:00
Kevin Darby 369fb55e90 fix auth tests to respect difference betwen admin and trader login 2019-07-08 14:20:14 -04:00
Pedro Pinto 37dacff74d Fixed a bug in top update 2019-07-08 14:09:37 -04:00
Pedro Pinto d297b703c7 Merge branch 'master' into ticks_off 2019-07-08 13:36:53 -04:00
Kevin Darby 33f533af9b set span to default off, hook up admin boolean to enforce span behind config key 2019-07-08 13:08:18 -04:00
Kevin Darby 085d6402f4 Merged in SPARK-2524-keycode_fixes (pull request #290)
RTM fixed SPARK-2524, allowing admin logins to not require a keycode session

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 16:26:51 +00:00
Pedro Pinto f4395ef833 Updated release notes. 2019-07-08 12:26:27 -04:00
Pedro Pinto e7a9412124 Updated release notes. 2019-07-08 12:21:39 -04:00
Pedro Pinto da4ee38abd Merge branch 'master' into SPARK-2524-keycode_fixes 2019-07-08 12:20:50 -04:00
Pedro Pinto af39fac368 Merge branch 'master' into SPARK-2524-keycode_fixes 2019-07-08 11:18:39 -04:00
Michael Harding 4f997505f3 Enforce Margin Limit in Spark Admin 2019-07-08 11:12:55 -04:00
Jake Norbut fcbb66361d Merged in SP-103_no_reset_on_agg_side_change (pull request #288)
RTM - SP-103 don't reset last when aggressor side changes

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:46:17 +00:00
Pedro Pinto fa16321691 Updated release notes. 2019-07-08 10:45:56 -04:00
Pedro Pinto ceb3542e15 Merge branch 'master' into SP-103_no_reset_on_agg_side_change 2019-07-08 10:30:25 -04:00
Jake Norbut 32e55d07ee Merged in SP-110_volume_tooltip_nit (pull request #295)
RTM - display tooltip again when re-entering same cell where tooltip was previouly closed

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:28:34 +00:00
Pedro Pinto e079acf91f Updated release notes. 2019-07-08 10:28:18 -04:00
Pedro Pinto fecedb203a Merge branch 'master' into SP-110_volume_tooltip_nit 2019-07-08 10:26:59 -04:00
Jake Norbut 6ac05a47e1 Merged in SP-103_bars_not_req (pull request #291)
RTM - SP-103 bars not required to show last text

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-08 14:15:36 +00:00
Pedro Pinto 36ed80dc87 Updated release notes. 2019-07-08 10:15:14 -04:00
John Colvin f5bbc4245f SP-111 / SP-113 Add ticks off parameter to top algo UI 2019-07-05 16:12:32 -05:00
Pedro Pinto 96fc54ff0f Added ticks_off to top algo server side. 2019-07-05 14:35:18 -04:00
Kevin Darby 25b69e4f7d fix small bug and add manual test 2019-07-05 13:29:22 -04:00
Kevin Darby d871ffca6d temporarily tie span enforcement to config while michael works 2019-07-05 12:59:41 -04:00
Jake Norbut 213567bcdb display tooltip again when re-entering same cell where tooltip was previouly closed 2019-07-05 11:49:34 -05:00
Kevin Darby b7e5c3a374 Merge branch 'span' of bitbucket.org:bluetradesys/spark into span 2019-07-05 12:30:10 -04:00
Kevin Darby ae4a8bbf46 more span 2019-07-05 12:28:58 -04:00
Taha Afzal 850386c1d4 Margin enforced in the limiter 2019-07-05 12:05:16 -04:00
Kevin Darby 2d4e94a636 Types.fs edited online with Bitbucket 2019-07-05 15:39:35 +00:00
Pedro Pinto e447096733 Routed excess liquidity to OrderManager. 2019-07-05 11:00:42 -04:00
Jake Norbut cd68b95a3e add a mechanism for ignoring InlineUserProperty's force_override property 2019-07-05 08:19:05 -05:00
Michael Harding ebad5b389f Fix Save button 2019-07-03 16:01:50 -04:00
John Colvin 160c49514a Merge remote-tracking branch 'origin/master' into SP-89-offset-algo 2019-07-03 14:38:50 -05:00
Michael Harding 4f7fcc5634 Move loss limits to the top of the limits page 2019-07-03 14:54:40 -04:00
Michael Harding 500e5b5fe4 Polling and commas 2019-07-03 14:19:51 -04:00
Michael Harding 9d924fa4ba Margin table formatting 2019-07-03 10:14:01 -04:00
John Colvin 092e63e300 OffsetAlgo attach impl (take 2) 2019-07-03 00:07:23 -05:00
Kevin Darby 4654cd4739 changes in margin workflow for webui 2019-07-02 18:29:10 -04:00
Kevin Darby f2f06d76de PR feedback 2019-07-02 17:43:28 -04:00
Kevin Darby 6bc293a58e add mic for DME 2019-07-02 17:35:29 -04:00
Kevin Darby fdb462eda4 simplify margin server states 2019-07-02 17:34:46 -04:00
Kevin Darby cc579034b2 fix bug in margin service 2019-07-02 17:05:22 -04:00
John Colvin 4b76e91757 SP-89 (pr edit) - 'can_consume_attachment()' returns false for 'Offset' 2019-07-02 15:48:24 -05:00
Eric Harding 8d34279ab0 SP-68 Fix crash on exit by not touching queue on windows. insert_or_replace() is a no-op on windows anyway. 2019-07-02 16:26:34 -04:00
John Colvin 7973b927a2 SP-89 (pr edit) - fwd price updates to child; cache update qty 2019-07-02 15:22:54 -05:00
Kevin Darby 6997aed9bd fixed SPARK-2524, allowing admin logins to not require a keycode session 2019-07-02 15:05:23 -04:00
Michael Harding b5d7d98098 Add DayPnL to SA Margin tab 2019-07-02 15:04:55 -04:00
Jake Norbut 8f22a326ab last on volume foreground text is now white 2019-07-02 14:01:50 -05:00
Jake Norbut b2c3488c5e draw volume numbers when bars not present 2019-07-02 14:01:07 -05:00
Michael Harding 23f10eac70 Add accountPnl to emptyMargin 2019-07-02 14:10:27 -04:00
Kevin Darby cb31269a70 added pnl impact to margin 2019-07-02 13:14:46 -04:00
Jake Norbut 5b75b8718a updating comment to reflect change 2019-07-02 11:14:01 -05:00
Eric Harding 5a256d4634 SP-104 PEBKAC buy with negative mult is nonsense 2019-07-02 11:55:55 -04:00
Jake Norbut db2de3e8c4 remove last_side; change StructLayout Size accordingly 2019-07-02 10:53:11 -05:00
Jake Norbut b9dcad9549 don't reset last trade size when aggressor changes 2019-07-02 10:52:21 -05:00
Jake Norbut ce104479e1 removing last_side; incrementing shm_version 2019-07-02 10:51:06 -05:00
Eric Harding 2d56eedf3e Bugfix in synthetic_book_merge_tests 2019-07-02 10:37:47 -04:00
Eric Harding 049381908c update gitignore 2019-07-02 10:34:28 -04:00
John Colvin 0067e52155 SP-89 (pr edit) - wait 4 mkt 2 launch; attach impl 2019-07-02 00:07:17 -05:00
Kevin Darby 8ed6d297a9 Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-07-01 18:01:34 -04:00
Taha Afzal a06df5099a Merged in SP-24-loss-limit (pull request #285)
RTM SP-24 loss limit

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 21:44:41 +00:00
Pedro Pinto 8ddd5bdbb5 Merged with master 2019-07-01 17:06:00 -04:00
Taha Afzal 5cbe00beab Spread PNL is not counted towards the Account PNL 2019-07-01 16:33:56 -04:00
Jake Norbut 40cd826260 Merged in SP-79 (pull request #280)
RTM - SP-79 arrow indicator on last disappearing too fast

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 20:03:34 +00:00
Pedro Pinto 8bd63f11b1 Updated release notes. 2019-07-01 16:03:13 -04:00
Pedro Pinto 2198a8d6ed Merge remote-tracking branch 'origin' into SP-79 2019-07-01 16:02:15 -04:00
Pedro Pinto 8023dc7aa8 Merge branch 'master' into SP-79 2019-07-01 16:01:53 -04:00
John Colvin e2b0da8103 Merged in SP-78-miniob-settings (pull request #282)
RTM SP-78 In miniob settings changing the column width directly through the grid (i.e. not through the dialog) has no effect beyond the current popup.

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 19:36:01 +00:00
Pedro Pinto aea233d537 Updated release notes. 2019-07-01 15:35:07 -04:00
Pedro Pinto 5756c090f3 Merge remote-tracking branch 'origin' into SP-78-miniob-settings 2019-07-01 15:28:59 -04:00
John Colvin 5ab0acb0ef Merged in SP-81-private-synths (pull request #284)
RTM SP-81 private synths

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-01 19:28:00 +00:00
Pedro Pinto 3950e5b634 Updated release notes. 2019-07-01 15:27:29 -04:00
Pedro Pinto 7ce8f1ed97 Updated release notes 2019-07-01 14:57:00 -04:00
Michael Harding 5d53492ab5 Merged SP-38-span-admin into span 2019-07-01 14:53:52 -04:00
Michael Harding 90fd5bd382 Handle save errors better 2019-07-01 14:52:40 -04:00
Michael Harding beff060a4f Update unchanged margin values 2019-07-01 14:27:44 -04:00
Michael Harding bc3418734f Span Columns and Formatting 2019-07-01 13:22:27 -04:00
Michael Harding ab255c8777 update ui.fsproj 2019-07-01 12:51:17 -04:00
Michael Harding cc2bde1064 Merged span into SP-38-span-admin 2019-07-01 12:43:16 -04:00
Michael Harding 94d66a8e99 Add Margin tab to Spark Admin 2019-07-01 12:40:16 -04:00
Taha Afzal 92f08b89cc Added a sub test to the clip limits test to verify that spread clip limits are independent of futures 2019-07-01 09:01:46 -04:00
Taha Afzal 02769479ea SP-74 bundled into this (it was a small change) 2019-06-30 22:45:29 -04:00
John Colvin 4ada322c56 bring back OffsetOrder gui in ladder/ticket 2019-06-30 12:56:59 -05:00
John Colvin 7de7f6e496 remove old OffsetOrder.cpp/h, replace with new OffsetAlgo in CMakeDeps 2019-06-30 12:47:05 -05:00
John Colvin 0700e9e80d SP-89 Port Offset order to new api 2019-06-29 16:10:34 -05:00
Taha Afzal ccca85d1c5 loss limit manual test updated 2019-06-27 15:46:44 -04:00
Taha Afzal 60e933fca9 SOD also indexes its data on account_id, Cleaned up Limiter's account_data (there was an overlap, after introducing Loss limit related PNL data) 2019-06-27 15:20:12 -04:00
Taha Afzal e2aa6034ab merging master 2019-06-26 20:22:05 -04:00
Taha Afzal 8b4c64e8f4 PositionServiceServer not indixes the state based on account_id istead of names 2019-06-26 19:34:46 -04:00
John Colvin b5ea399e7e WorkspacesDialogView - remove 'Is Shared' checkbox 2019-06-26 15:22:14 -05:00
John Colvin 62baa6aa31 MEFExport UserTuple does not need a contract name 2019-06-26 14:19:49 -05:00
John Colvin 87433aa7c3 add comment for 'KnownUsers' prop 2019-06-26 13:41:37 -05:00
John Colvin 3252cab4bb SP-80 / SP-88 Login dialog should not expose user names (because they are private) 2019-06-26 13:22:02 -05:00
John Colvin fbe9b570a3 (forgot to run 'git add' again) 2019-06-26 12:43:48 -05:00
John Colvin 1bcb209961 remove unused delegate type, and worthless commented line 2019-06-26 11:42:47 -05:00
John Colvin b0726ad3ea just noticed some iobservables in OrderBookVM without OnDispatcher()s (adding them preemptively) 2019-06-26 11:40:10 -05:00
John Colvin e0ba27a0a9 SpreaderModel - remove unneccesary 'async/await' wrappers 2019-06-26 11:40:07 -05:00
John Colvin 345e45c5bf MEFExport UserTuple 2019-06-26 10:28:05 -05:00
John Colvin 490add84d9 replace magic consts w/ regular consts 2019-06-25 23:19:20 -05:00
John Colvin 94fc2c4248 refactor LoginModel.Exports -> LoginContainer.Exports 2019-06-25 23:18:43 -05:00
John Colvin 8a7e2db4ac SP-80 / SP-81 Synths should be private 2019-06-25 19:01:57 -05:00
John Colvin 71c576bbc0 SP-80 / SP-82 Algos should be private 2019-06-25 17:05:41 -05:00
John Colvin 2e4dff4d91 SP-76 In miniob settings, the miniob's width seems to have a minimum that does not reflect the currently displayed columns 2019-06-25 16:20:08 -05:00
John Colvin 73336df814 SP-78 In miniob settings changing the column width directly through the grid (i.e. not through the dialog) has no effect beyond the current popup. 2019-06-25 16:04:24 -05:00
Michael Harding 633edb43eb wait longer for the system to have products 2019-06-25 14:36:52 -04:00
Taha Afzal 29d14404e6 PositionService has access to AdminService to resolve accounts now 2019-06-24 17:15:04 -04:00
Taha Afzal 60b7966f2f Loss limit manual test 2019-06-24 15:33:47 -04:00
Jake Norbut 1059351334 adding the settings_subscription to be disposed; fixes swallowed NullRef; not directly related to this branch 2019-06-24 14:15:53 -05:00
Jake Norbut 179ee000a3 adding case for when the ladder is first brought up and the market has not yet moved 2019-06-24 14:02:24 -05:00
Jake Norbut 43129427f6 fix arrow disappearing from non-Last-based renders 2019-06-24 13:20:38 -05:00
Jake Norbut 7afe1e103d refactor string formatting out of LadderRender 2019-06-24 13:18:29 -05:00
Taha Afzal 7c6a26ebd9 All hooked up, on updates, loss limit is checked only on size increases 2019-06-23 19:25:06 -04:00
Taha Afzal b2766c283d Setting account id in PositionUnit 2019-06-22 22:05:32 -04:00
Pedro Pinto 6b5b4ea2b1 Merge branch 'SP-24-loss-limit' of bitbucket.org:bluetradesys/spark into SP-24-loss-limit 2019-06-22 16:30:39 -04:00
Pedro Pinto 6f7c6db918 Updated fill account_id in FillStore. 2019-06-22 16:29:55 -04:00
Taha Afzal a1ead3d1c1 cpp routed correctly now 2019-06-22 14:24:15 -04:00
Taha Afzal 1fc35ee3cd Merge branch 'SP-24-loss-limit' of bitbucket.org:bluetradesys/spark into SP-24-loss-limit 2019-06-21 18:43:31 -04:00
Taha Afzal a014dcc984 abstracted _get_handle in PositionServicePNLHelper::HandleIndex 2019-06-21 17:58:08 -04:00
Pedro Pinto 6e07a0c109 Fixed build. 2019-06-21 11:51:20 -04:00
Pedro Pinto e936b80dc6 Forwarded pnl updates to order manager. 2019-06-21 11:45:50 -04:00
Pedro Pinto 60ab67213f Ran clang format. 2019-06-20 15:57:09 -04:00
Pedro Pinto 4055a06601 Merged master 2019-06-20 15:28:27 -04:00
Kevin Darby e47af3d7bd update api docs 2019-06-20 10:34:06 -04:00
Kevin Darby c3c427742d Merge branch 'master' of bitbucket.org:bluetradesys/spark into span 2019-06-19 13:34:52 -04:00
Michael Harding 83c432d8e2 Merged in SP-34-loss_limit_admin (pull request #268)
SP-34: Add Loss Limit to Spark Admin

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-18 21:25:47 +00:00
Michael Harding 6a4930767b SP-34: Add Loss Limit to Spark Admin 2019-06-18 16:32:14 -04:00
Kevin Darby 685e69adcc Merge branch 'key_codes' of bitbucket.org:bluetradesys/spark into span 2019-06-18 15:21:10 -04:00
Kevin Darby e9f61dc3c0 added get_excess_liquidity_report rpc for adminui 2019-06-17 18:15:05 -04:00
Kevin Darby f329470165 added spreadsheet to help test span 2019-06-17 16:49:21 -04:00
Kevin Darby d7d319f1e3 fixed span timing 2019-06-17 10:43:27 -04:00
Taha Afzal cddadcb278 PositionService exposes new streaming calls pnl_data and pnl_account_data to get detailed and summary pnls 2019-06-16 22:02:16 -04:00
Kevin Darby 419833d398 slight timing issue with span 2019-06-14 18:10:41 -04:00
Kevin Darby 9883553fb5 MarginService 2019-06-14 11:21:09 -04:00
Kevin Darby be5d037ca9 span works again 2019-06-13 17:52:47 -04:00
Taha Afzal 21f5fd919d Changes in the API for Loss Limit and new parameter in the account to enforce that limit 2019-06-13 15:37:43 -04:00
Kevin Darby 159e8fba23 beginnings of span, should't be long 2019-06-12 18:17:09 -04:00
119 changed files with 2776 additions and 887 deletions

No files matched your search

+1
View File
@@ -32,3 +32,4 @@ trace_data/
/src/lib/orderman/perf_analysis/last_pretty_log.csv
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
.ionide/*
+22 -1
View File
@@ -4,6 +4,26 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder, corrected display of volume tool tip
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
- Added an option for the Top Algo to delete itself if within N ticks from top of book
- Address a rare issue that could lead to an error report on UI exit
- Added margin risk limits
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
- Avoided switching the current account and order type on open ladders when limits are changed
### Internal Changes
- Ensure that admin logins are always allowed to login, regardless of keycode status.
## 2.5.0
### User Visible Changes
- Added loss limit risk check
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
- Allowed the mini order book defaults for column widths to be changed from the mini order book
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
## 2.4.0
### User Visible Settings
- In the ladder, changed the last trade column to accumulate successive trades at the same price
@@ -391,7 +411,8 @@ insert_anchor_links = "right"
- This is configurable from a ladder's settings as well
### Customized settings in all windows
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
- These settings can be saved on a per-window basis or as a Default for newly-launched windows␍
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
## 1.3.1 Through 1.3.4
+6 -14
View File
@@ -7,12 +7,12 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using bts.api.core;
using mbts.ui.shell;
namespace mbts.spark {
[Export]
class AdminModel : IDisposable {
readonly string _user_name;
public string user_name => _user_name;
public readonly UserTuple user;
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
@@ -24,14 +24,14 @@ namespace mbts.spark {
IDisposable _dispose;
[ImportingConstructor]
AdminModel(AdminService service, [Import("username")]string user_name) {
_user_name = user_name;
AdminModel(AdminService service, [Import]UserTuple user) {
this.user = user;
_dispose = service.subscribei_admin_data().Select(update => {
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), _user_name));
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
IEnumerable<BTSUserImmutable> active_users =
from admin_data in update.all
from bts_user in admin_data.Users
where bts_user.Name == user_name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
select bts_user;
IEnumerable<BTSAccountImmutable> active_accounts =
@@ -62,14 +62,6 @@ namespace mbts.spark {
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
public UserTuple? get_current_user() {
foreach (var kv in _user_map) {
// just for good measure, there should never be > 1 element in _user_map, and it should always match _user_name
if (kv.Value == _user_name)
return new UserTuple(kv.Key, kv.Value);
}
return null;
}
public AccountTuple? get_default_account() {
foreach (var kv in _account_map) {
return new AccountTuple(kv.Key, kv.Value);
+2 -1
View File
@@ -3,6 +3,7 @@ using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -22,7 +23,7 @@ namespace bts.ui {
IDisposable _dispose;
[ImportingConstructor]
public AboutDaemon(IBus bus, [Import("username")]string username) {
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
_user = username;
_bus = bus;
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
+28 -2
View File
@@ -10,6 +10,7 @@ using System.Reactive.Linq;
using System.Windows.Threading;
using System.Windows;
using System.Threading.Tasks;
using mbts.ui.shell;
namespace bts.spark
{
@@ -20,7 +21,7 @@ namespace bts.spark
IDisposable _dispose;
[ImportingConstructor]
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
_svc = svc;
_username = username;
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
@@ -82,7 +83,6 @@ namespace bts.spark
}
}
//public static async Task<Result<T>>
public static async Task<(T value, bool success)> safe_execute<T>(this ErrorReporter error_reporter, Func<Task<Result<T>>> execute, Func<T, bool> get_success, Func<T, string> get_error_msg, string desc) {
try {
var result = await execute();
@@ -101,5 +101,31 @@ namespace bts.spark
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
public static async Task<TResult> safe_execute<T, TResult>(
this ErrorReporter error_reporter,
Func<Task<Result<T>>> execute,
Func<T, bool> get_success,
Func<T, string> get_error_msg,
Func<T, bool, TResult> result_selector,
string desc) {
//
try {
var result = await execute();
if (!result.success) {
_report_error(result.error_message);
} else if (!get_success(result.value)) {
_report_error(get_error_msg(result.value));
} else {
return result_selector(result.value, true);
}
} catch (Exception ex) {
_report_error(ex.ToString());
}
return result_selector(default, false);
//
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
}
}
+3 -2
View File
@@ -15,6 +15,7 @@ using System.Collections.Concurrent;
using System.Threading;
using bts.api.user;
using bts.md;
using mbts.ui.shell;
namespace bts {
[Export(typeof(IInitializable))]
@@ -27,8 +28,8 @@ namespace bts {
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
[ImportingConstructor]
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) :
base(get_naggle(), MDSourceOptions.DeferInit) {
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
_bus = bus;
_token = token;
_ss = ss;
@@ -3,6 +3,7 @@ using bts.metrics;
using mbts;
using mbts.config;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -23,7 +24,7 @@ namespace bts.perf {
Ping _pinger = new Ping();
[ImportingConstructor]
public ShellLatencyDaemon(ShellLatencyService service, [Import("username")] string user) {
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
_service = service;
_version = Configuration.get<bts.config.System>().version;
_username = user;
@@ -5,6 +5,7 @@ using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
@@ -18,7 +19,7 @@ namespace bts.shell
class MarketShellModule : IShellModule
{
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.synthetic;
using bts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -14,12 +15,14 @@ namespace mbts.spark {
readonly IBus _bus;
readonly SyntheticSecurityService _service;
readonly ErrorReporter _error;
readonly uint _user_id;
[ImportingConstructor]
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error) {
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
_bus = bus;
_service = service;
_error = error;
_user_id = user_id;
}
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
@@ -39,29 +42,40 @@ namespace mbts.spark {
return (result, success);
});
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
await lookup_strategy_async(security.Id);
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
lookup_strategy_async(security.Id);
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
if (result.success) {
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
if (metadata != null) {
return metadata.Strategy;
}
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) =>
_error.safe_execute(
() => _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } }),
mdc => mdc.Data.Count == 1 && mdc.Data[0].SecurityKey == id,
mdc => $"did not find strategy for security id '{id}.'",
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
"lookup strategy");
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
return null;
if (synthetic.Synthetic.UserId != _user_id) {
var msynth = synthetic.CloneToMutable();
msynth.Synthetic.UserId = _user_id;
synthetic = new SecurityImmutable(msynth);
}
return null;
return _error.safe_execute(
() => _service.create_synthetic_async(synthetic),
r => r.Error == CreateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success ? r.Key : (uint?)null,
"creating synthetic");
}
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
var result = await _service.create_synthetic_async(synthetic.Value);
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
}
public async Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) {
var result = await _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() });
return _error.handle_error(result, "Error saving execution strategy: {0}", r => r.Error, e => e == UpdateResult.Types.Error.Ok);
}
public Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) =>
_error.safe_execute(
() => _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() }),
r => r.Error == UpdateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success,
"saving strategy");
public async Task<bool> verify_name_is_unique(string name) {
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
@@ -134,7 +134,7 @@ namespace mbts.spark {
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
uint? id = await model.create_synthetic(_synthetic);
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
var (smd, success) = await SparkUtils.get_security_md_async(bus, id.Value);
@@ -1,5 +1,4 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.persist.algo;
@@ -110,38 +109,24 @@ namespace mbts.spark {
public void Dispose() =>
Disposable.dispose(ref _dispose);
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) {
if (_try_get_user(out uint user_id)) {
return await _create_definition(definition, user_id);
} else {
_report_no_user_id(ActionMetadata.CreateDefinition);
}
return null;
}
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
await _create_definition(definition, _admin_model.user.id);
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
new TemplateCreateRequest { UserId = user_id, NewTemplate = template.CloneToMutable() }),
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
if (success)
return _Utils.template_data(value.Record);
} else {
_report_no_user_id(ActionMetadata.CreateTemplate);
}
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
if (success)
return _Utils.template_data(value.Record);
return null;
}
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
if (success)
return _Utils.template_data(template);
} else {
_report_no_user_id(ActionMetadata.UpdateTemplate);
}
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
if (success)
return _Utils.template_data(template);
return null;
}
@@ -175,17 +160,6 @@ namespace mbts.spark {
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
// used for error handling/reporting
interface IActionMetadata {
string desc { get; }
@@ -1,14 +1,11 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman.algo;
using bts.persist.algo;
using bts.spark;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -27,7 +24,12 @@ namespace mbts.spark {
_admin_model = admin_model;
_error_reporter = error_reporter;
this.ui_configuration = ui_configuration;
definitions = service.subscribei_algo_definitions().Filter(r => r.Metadata.IsActive).SelectKeyValue(_definition_data, d => d.key).Table().Cache();
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
@@ -39,15 +41,11 @@ namespace mbts.spark {
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = user_id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
} else {
_report_no_user_id("save template");
}
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
return null;
}
@@ -105,16 +103,5 @@ namespace mbts.spark {
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
}
}
@@ -96,14 +96,28 @@ namespace bts.shell {
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, LadderSettings.sanitize(lp.value), lp.security_criteria))),
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
state_settings?.sanitize();
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
/// 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;
LadderSettings last_value = fit_to_sec(state_settings);
var settings_subject = new BehaviorSubject<LadderSettings>(last_value);
LadderVM vm = null;
var settings_subject = new Subject<LadderSettings>();
var settings_source = Observable.Create<LadderSettings>(o => {
ensure_synced();
if (last_value != null)
o.OnNext(last_value);
return settings_subject.Subscribe(s => {
if (s != null)
o.OnNext(s);
});
});
// vm
var vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_subject.Where(s => s != null));
vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_source);
vm.settings_invalidated += on_settings_invalidated;
vm.order_properties_vm.open_algo_popup_on_select = _ladder_conf.open_algo_popup_on_select;
vm.order_properties_vm.use_algo_param_grid = _ladder_conf.use_algo_param_grid;
vm.request_settings_dialog += open_settings_dialog;
bool initialized = false;
var settings_subscription = _settings_hub.subscribe_default_settings().Subscribe(on_user_settings);
vm.OnDisposed += disposing => {
Disposable.dispose(ref settings_subscription);
@@ -119,8 +133,25 @@ namespace bts.shell {
LadderSettings fit_to_sec(LadderSettings s) =>
_fit_to_sec(s, md.security.ContractCase);
void on_settings_invalidated() =>
needs_resync = true;
void ensure_synced() {
if (!needs_resync)
return;
var curr = vm.get_save_state();
if (settings_id == 0) {
last_value = curr;
} else if (!curr.Equals(last_value)) {
settings_id = 0;
last_value = curr;
}
needs_resync = false;
}
void on_user_settings(UserSettings us) {
if (initialized) {
ensure_synced();
if (settings_id == 0) {
// not on a profile, don't respond to default settings changes
return;
@@ -290,4 +321,4 @@ namespace bts.shell {
[ProtoMember(3)]
public uint settings_id;
}
}
}
@@ -355,8 +355,17 @@ namespace mbts.spark {
}
public LadderSettings get_result() {
var clone = SparkUtils.clone_proto(_current);
clone.mini_ob = new LadderMiniOrderBookSettings(mini_ob.create());
LadderSettings clone;
if (_current == null) {
clone = new LadderSettings {
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
};
} else {
clone = SparkUtils.clone_proto(_current);
#pragma warning disable CS0612 // Type or member is obsolete
clone.mini_ob = new LadderMiniOrderBookSettings(mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion));
#pragma warning restore CS0612 // Type or member is obsolete
}
clone.badge_style = badge_style;
clone.alternate_order_size = alternate_order_size;
clone.favorite_algos = favorite_algos_vm.create();
@@ -241,7 +241,7 @@ namespace mbts.spark {
public IBus bus => _bus;
public event Action on_tick, request_settings_dialog;
public event Action on_tick, request_settings_dialog, settings_invalidated;
public MDConsumerHandle md => _model.md;
// we return only entries who have piqs and whose piqs are not uint.Max
@@ -394,6 +394,12 @@ namespace mbts.spark {
}
}
public void on_mini_orderbook_closed(bts.shell.orderbook.OrderBookStartupData ob_state) {
current_settings = SparkUtils.clone_proto(current_settings);
current_settings.mini_ob = new LadderMiniOrderBookSettings(ob_state.orderbook_grid.general);
settings_invalidated?.Invoke();
}
public void send_order((double price, Side? side, MouseButton button) args) {
if (order_properties_vm == null || args.button == MouseButton.Middle)
return;
@@ -67,7 +67,8 @@ namespace mbts.spark {
NativePriceSize _last_trade;
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
int _old_tooltip_row = int.MaxValue;
double _old_last_trade_price = 0.0;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
bool _tooltips_are_hooked_up = false;
ToolTip tooltip_view = new ToolTip();
IDisposable _parameterized_order_popup;
@@ -693,12 +694,14 @@ namespace mbts.spark {
// At any rate, we need this part hiding the tooltip so that we don't leave random tooltips around
// and we have to reset to old row so that when we go back to what could be our preivous row, the tooltip gets drawn again
//System.Diagnostics.Debug.WriteLine(" Tooltip - Volume is 0");
_old_tooltip_row = int.MaxValue;
_hide_tool_tip();
//_old_tooltip_row = Int32.MaxValue;
}
}
} else {
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
_old_tooltip_row = int.MaxValue;
_hide_tool_tip();
}
}
@@ -861,21 +864,43 @@ namespace mbts.spark {
void _refresh_volume_at_price() {
if (!_settings.volume_column.general.show)
return;
// if the last trade has a new size then we need to explicitly
// mark the volume container as dirty because there is a chance
// we have arrived here off a size change and not a volume change
// or a random change, like applying new settings
bool last_trade_size_changed = _old_last_trade.HasValue ? _last_trade.size != _old_last_trade.Value.size : true;
if (last_trade_size_changed && _settings.volume_column.show_ltq) {
_volume_container.mark_dirty();
}
var vapls = _volume_container.read_new_value();
if (vapls != null) {
Log.assert_d(_price_list != null);
var ltp = _last_trade.price;
var last_index = LadderRender.index_of(_price_list, ltp, _price_comparer);
string direction_arrow = string.Empty;
if (MathUtils.epsilon_equals(ltp, _old_last_trade_price)) {
// do nothing if in here b/c direction_arrow already string.empty
} else if (ltp > _old_last_trade_price) {
direction_arrow = "▲";
} else {
direction_arrow = "▼";
}
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _last_trade, direction_arrow);
_old_last_trade_price = ltp;
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
_old_last_trade = _last_trade;
}
string _get_last_arrow_string() {
// if _old_last_trade doesn't have a value, this is the first time the ladder has been launched
// and so the direction should be drawn without an arrow since there is no basis for comparison
if (!_old_last_trade.HasValue) {
_direction_arrow = string.Empty;
// if the Lasts are different then we need to reformat the direction arrow in some fashion
} else if (_old_last_trade != _last_trade) {
var ltp = _last_trade.price;
if (MathUtils.epsilon_equals(ltp, _old_last_trade.Value.price)) {
// update on the same price, so size must be different
_direction_arrow = string.Empty;
} else if (ltp > _old_last_trade.Value.price) {
_direction_arrow = "▲";
} else {
_direction_arrow = "▼";
}
}
// regardless of if we've arrived here via a new Last (and new direction arrow)
// or we're reusing the old one, this format bit is the same
return $"{_direction_arrow} {_last_trade.size} ";
}
}
@@ -9,9 +9,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace mbts.spark {
@@ -156,7 +154,10 @@ namespace mbts.spark {
popup.SetBinding(System.Windows.Controls.Primitives.Popup.IsOpenProperty,
nameof(OrderBookVM.is_row_collection_populated)
);
popup.Closed += (sender, event_args) => (popup.DataContext as IDisposable)?.Dispose();
popup.Closed += (sender, event_args) => {
(_parent.DataContext as LadderVM)?.on_mini_orderbook_closed(obvm.create());
(popup.DataContext as IDisposable)?.Dispose();
};
popup.MouseUp += (sender, event_args) => {
if (event_args.ChangedButton == MouseButton.Middle) {
popup.IsOpen = false;
@@ -196,8 +196,7 @@ namespace mbts.spark {
ContractSpecificationImmutable spec,
VolumeColumnSettings settings,
int last_index,
NativePriceSize last_trade,
string direction_arrow
string last_size_and_direction
) {
if (!data.Any()) {
_clear_visual(dv);
@@ -216,16 +215,16 @@ namespace mbts.spark {
Brush total_brush = null;
if (settings.total.bars.result.HasValue) {
total_brush = new SolidColorBrush(settings.total.bars.result.Value);
} else if (components.Count == 0) {
} else if (components.Count == 0 && !settings.show_ltq) {
_clear_visual(dv);
return;
}
// inflate the max so that the graph never fills the available space
double max = data.Max(v => v.Volume) * 1.1;
Brush text_brush = settings.general.foreground.rgb_brush();
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
/// local methods
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
Brush text_brush = settings.general.foreground.rgb_brush();
double y = row_height * r;
var last_trade_here = r == last_index;
@@ -259,7 +258,8 @@ namespace mbts.spark {
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
numbers_text.Add($"{direction_arrow} {last_trade.size} ");
numbers_text.Add(last_size_and_direction);
text_brush = BTSCoreColors.white;
}
if (numbers_text.Count > 0) {
string str = string.Join("/", numbers_text);
@@ -14,7 +14,6 @@
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
mc:Ignorable="d"
MaxHeight="240"
MinWidth="875"
d:DesignHeight="600" d:DesignWidth="1080"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}">
@@ -1177,7 +1177,6 @@ namespace mbts.spark {
public class CommitColumn : FrameworkElementColumnBase<Columns> {
public CommitColumn() : base(Columns.Commit) {
id = key.ToString();
Header = "";
Width = 20;
}
@@ -331,8 +331,8 @@ namespace mbts.spark {
IDisposable connect_order_mod_vm() =>
Disposable.Create(
_selected_params_mon.subscribe_algos().Subscribe(order_mod_vm.algo_vm.load_many),
order_mod_vm.subscribe_is_dirty().Subscribe(_ => {
_selected_params_mon.subscribe_algos().OnDispatcher().Subscribe(order_mod_vm.algo_vm.load_many),
order_mod_vm.subscribe_is_dirty().OnDispatcher().Subscribe(_ => {
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
revert_selected_orders_cmd.RaiseCanExecuteChanged();
}),
@@ -340,10 +340,10 @@ namespace mbts.spark {
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
Disposable.Create(
_selected_params_mon.subscribe_security().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
_selected_params_mon.subscribe_price().Subscribe(vm.on_common_price),
_selected_params_mon.subscribe_size().Subscribe(vm.on_common_size),
_selected_params_mon.subscribe_account().Subscribe(newval => vm.selected_account = newval));
_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));
}
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
@@ -994,11 +994,13 @@ namespace mbts.spark {
var join = config.Configuration.get<bts.config.JoinAttachUIOptions>();
var lean = config.Configuration.get<bts.config.LeanAttachUIOptions>();
var top = config.Configuration.get<bts.config.TopAttachUIOptions>();
var offset = config.Configuration.get<bts.config.OffsetAttachUIOptions>();
_algo_type_conf = new Dictionary<CustomOrderDefinition.CustomOneofCase, (bool, bool)> {
{ CustomOrderDefinition.CustomOneofCase.Cover, (cover.show_price, cover.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Join, (join.show_price, join.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Lean, (lean.show_price, lean.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) }
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) },
{ CustomOrderDefinition.CustomOneofCase.Offset, (offset.show_price, offset.show_quantity) }
};
}
+9 -10
View File
@@ -168,17 +168,14 @@ namespace mbts.spark.utilities {
// this will be the tag 50 on CME orders
// current user might be okay, but we might also want to get it
// from the store that serves the risk admin gui
ManualOrderIndicator = ManualOrderIndicator.Yes
ManualOrderIndicator = ManualOrderIndicator.Yes,
User = _admin_model.user.name,
UserId = _admin_model.user.id
};
if (account.HasValue) {
rv.Account = account.Value.name;
rv.AccountId = account.Value.id;
}
var user = _admin_model.get_current_user();
if (user.HasValue) {
rv.User = user.Value.name;
rv.UserId = user.Value.id;
}
return rv;
}
@@ -605,7 +602,8 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset
},
// The following spreader types are not yet ready for production use:
// CustomOrderDefinition.CustomOneofCase.Dynamic,
@@ -622,7 +620,8 @@ namespace mbts.spark.utilities {
CustomOrderDefinition.CustomOneofCase.Lean,
CustomOrderDefinition.CustomOneofCase.Join,
CustomOrderDefinition.CustomOneofCase.Cover,
CustomOrderDefinition.CustomOneofCase.Top
CustomOrderDefinition.CustomOneofCase.Top,
CustomOrderDefinition.CustomOneofCase.Offset
},
outright_order_types = new[] {
CustomOrderDefinition.CustomOneofCase.Market,
@@ -718,7 +717,7 @@ namespace mbts.spark.utilities {
!b.HasChild);
public static bool top_equals(TopOrderDefinitionImmutable a, TopOrderDefinitionImmutable b) =>
a.WatchLevelTickOffset == b.WatchLevelTickOffset &&
a.TicksOff == b.TicksOff &&
(a.HasTopThreshold ?
b.HasTopThreshold && quantity_measure_equals(a.TopThreshold, b.TopThreshold) :
!b.HasTopThreshold) &&
@@ -909,7 +908,7 @@ namespace mbts.spark.utilities {
add_component(_get_hash(_get_tick_child(tick)));
break;
case CustomOrderDefinition.CustomOneofCase.Top:
add_component(def.Top.WatchLevelTickOffset.GetHashCode());
add_component(def.Top.TicksOff.GetHashCode());
add_component(def.Top.HasTopThreshold ? def.Top.TopThreshold.GetHashCode() : 0);
add_component(_get_top_child(def.Top).GetHashCode());
break;
@@ -105,13 +105,18 @@ namespace mbts.spark {
var inline_account_src = order_properties.AggregateLatest(
model.factory.subscribe_accounts(),
(last, change, cache) => convert_inline_account(change.New.account, cache),
(last, props, change) => convert_inline_account(props.account, change.New),
(last, props, change) => convert_inline_account(props.account, change.New, false),
() => new InlineUserProperty<AccountTuple>(),
new InlineOrderProperties(),
DictionaryHelper.empty<uint, string>());
// Note that with the .Where on the account_tuples, we will ignore any
// updates for which all accounts have been removed.
// This was a safer change than changing SelectorProperty's IEnumberable.OnNext
// so that it would not reset the selected item on server reboots; i.e. this
// change only affects this IObservable.
_accounts = backing(
nameof(selected_account),
model.factory.subscribe_account_tuples(),
model.factory.subscribe_account_tuples().Where(items => items.Any()),
inline_account_src,
a => new AccountTuple?(a),
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
@@ -263,9 +268,18 @@ namespace mbts.spark {
break;
}
}
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map) =>
//
// Regarding the 3rd parameter, force_override_from_inline_property:
// When using an InlineUserProperty, in the context of assigning a default vs selected item,
// there is somewhat of a conflict between the IObserver<InlineUserProperty<TItem>> and the
// IObserver<IEnumerable<TItem>> OnNext methods. This 3rd paramter provides yet another override
// to say if the force_override should come from the inline property or if the force_override should
// be disbled, by way of the immediate return in ViewModelBase._try_auto_select when force_override
// is false. Our intent is then accomplished because we are no longer attempting to set a default/
// selected item when the account_tuples potentially changes
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map, bool force_override_from_inline_property = true) =>
account_id.has_value && account_map.TryGetValue(account_id.value, out string name) ?
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), account_id.force_override) :
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), force_override_from_inline_property ? account_id.force_override : false) :
new InlineUserProperty<AccountTuple>();
}
@@ -5,13 +5,16 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
readonly Property<uint?> _ticks_off;
readonly Property<QuantityMeasureImmutable?> _top_threshold;
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_ticks_off = add_nullable_property<uint>(nameof(ticks_off), TopDef.TicksOffFieldNumber);
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
}
public uint? ticks_off { get => _ticks_off.Value; set => _ticks_off.set(value); }
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
}
}
@@ -820,6 +820,11 @@ namespace mbts.ui.shell {
void _try_auto_select() {
if (_is_user_selected_item) {
// if we have a user-selected item and the _desired_item for which we might want to set as the selected item
// isn't force_override, then no change
if (!_desired_item.force_override) {
return;
}
if (desired_item_valid() && _desired_item.force_override && _try_match_item(_desired_item.value, out TValue match)) {
_set_selected_item(match, SelectionReason.SettingsMandatedSelection);
return;
@@ -489,6 +489,8 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
<RowDefinition Height="*"/>
@@ -503,9 +505,11 @@
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="If the order is at the top of book, the quantity remaining below which the order will be canceled."/>
</StackPanel>
<uag:QuantityMeasureControl x:Name="PART_TopThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
<TextBlock Grid.Row="2">Ticks Off</TextBlock>
<c:TickUpDownUInt Grid.Row="2" Grid.Column="2" Value="{Binding ticks_off}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
Grid.Row="2"
Grid.Row="4"
Grid.ColumnSpan="3"
ItemsSource="{Binding children_vm.child.algos_view}"
SelectedItem="{Binding children_vm.child.selected_algo}"
@@ -28,16 +28,27 @@ namespace mbts.ui.shell {
model.Dispose();
}
[Export("username")]
[Export(Exports.UserName)]
public string UserName { get; private set; }
[Export("userid")]
[Export(Exports.UserID)]
public uint UserId { get; private set; }
[Export]
public UserTuple UserTuple => new UserTuple(UserId, UserName);
[Export]
public AuthTokenImmutable Token { get; private set; }
[Export("workspace")]
public string Workspace { get; private set; }
public static class Exports {
public const string
UserName = "username",
UserID = "userid",
UserTuple = "usertuple";
}
}
public struct User {
@@ -49,8 +60,7 @@ namespace mbts.ui.shell {
public readonly UInt32 id;
}
public class LoginModel : IDisposable
{
public class LoginModel : IDisposable {
// Singleton to help bootstrap the system
private static LoginModel _instance = new LoginModel();
public static LoginModel instance { get { return _instance; } }
@@ -70,6 +80,11 @@ namespace mbts.ui.shell {
public bool HasFatalError { get; set; }
public User[] Users { get; private set; }
public bool try_get_user(string name, out User user) =>
Users.TryGetFirst(u => u.name == name, out user);
public bool user_exists(string name) =>
Users.Any(u => u.name == name);
// The final username
public (string, uint, AuthTokenImmutable) get_user() {
@@ -6,6 +6,7 @@
xmlns:ui="clr-namespace:mbts.ui"
xmlns:uicore="clr-namespace:mbts.ui"
xmlns:converters="clr-namespace:mbts.ui.converters"
xmlns:autocomplete="clr-namespace:WPFTextBoxAutoComplete;assembly=WPFTextBoxAutoComplete"
mc:Ignorable="d" MouseDown="Window_MouseDown"
Width="500" Height="309" WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="CenterScreen"
ui:StateManager.VisualState="{Binding VisualState}">
@@ -82,7 +83,13 @@
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding UserNames}" Grid.ColumnSpan="2" Margin="10" SelectedItem="{Binding Name, Mode=TwoWay}" Width="160" />
<TextBox
Grid.ColumnSpan="2"
autocomplete:AutoCompleteBehavior.AutoCompleteItemsSource="{Binding KnownUsers}"
Margin="10"
Width="160"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
/>
<ComboBox ItemsSource="{Binding Workspaces}" Grid.Row="1" Grid.ColumnSpan="2" Margin="10" SelectedItem="{Binding SelectedWorkspace, Mode=TwoWay}" Width="160" MaxWidth="160" />
<PasswordBox Grid.Row="2" Margin="10" MinWidth="120" Grid.ColumnSpan="2" x:Name="_password" PasswordChanged="_passwordChanged" Style="{DynamicResource PasswordBoxStyle1}" Width="160">
@@ -18,7 +18,7 @@ namespace mbts.ui.shell
_dispatcher = disp;
_model = model;
Workspaces = new ObservableCollection<string>();
LoginCommand = new DelegateCommand(OnLogin, _ => UserNames.Count > 0 && SelectedWorkspace != null);
LoginCommand = new DelegateCommand(OnLogin, _ => !string.IsNullOrEmpty(_name) && SelectedWorkspace != null);
CancelCommand = new DelegateCommand(OnCancel);
EmailCommand = new DelegateCommand(OnSendMail);
if (_model.HasFatalError) {
@@ -42,9 +42,10 @@ namespace mbts.ui.shell
void _model_Ready() {
_dispatcher.BeginInvoke(() => {
if (!_model.HasFatalError) {
this.UserNames.AddRange(_model.Users.Select(u => u.name));
this.VisualState = "LoginState";
load_username();
if (!string.IsNullOrEmpty(_name))
KnownUsers.Add(_name);
LoginCommand.RaiseCanExecuteChanged();
}
else {
@@ -57,8 +58,8 @@ namespace mbts.ui.shell
private string _vstate = "Normal";
public string VisualState { get { return _vstate; } set { _vstate = value; RaisePropertyChanged("VisualState"); } }
private ObservableCollection<string> _user_names = new ObservableCollection<string>();
public ObservableCollection<string> UserNames { get { return _user_names; } }
/// <see cref="KnownUsers"/> should only contain the user-name of the last login (if none exists, it should be empty).
public ObservableCollection<string> KnownUsers { get; } = new ObservableCollection<string>();
private string _name;
public string Name {
get { return _name; }
@@ -82,12 +83,13 @@ namespace mbts.ui.shell
private async void update_workspaces(string name) {
Workspaces.Clear();
Workspaces.Add("New workspace...");
var user = _model.Users.First(u => u.name == name);
var to_add = await _model.workspaces_for(user);
this.SelectedWorkspace = Workspaces.First();
if (to_add != null && name == _name && Workspaces.Count == 1) {
Workspaces.AddRange(to_add);
load_last_workspace(name);
SelectedWorkspace = Workspaces.First();
if (_model.try_get_user(name, out var user)) {
var to_add = await _model.workspaces_for(user);
if (to_add != null && name == _name && Workspaces.Count == 1) {
Workspaces.AddRange(to_add);
load_last_workspace(name);
}
}
RaisePropertyChanged("SelectedWorkspace");
LoginCommand.RaiseCanExecuteChanged();
@@ -111,7 +113,7 @@ namespace mbts.ui.shell
var value = key.GetValue("login");
if (value != null) {
var n = value.ToString();
if (UserNames.Contains(n)) {
if (_model.user_exists(n)) {
Name = n;
}
}
@@ -1,14 +1,14 @@
using System;
using bts;
using bts.api;
using bts.spark;
using mbts.ui.native;
using mbts.ui.utility;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using mbts.ui.native;
using mbts.ui.utility;
using bts.api;
using bts.spark;
using bts;
namespace mbts.ui.shell {
[Export(typeof(ILauncher))]
@@ -23,7 +23,7 @@ namespace mbts.ui.shell {
[ImportingConstructor]
public LauncherVM(
ObservableCollection<IShellModule> modules,
[Import("username")]string username,
[Import(LoginContainer.Exports.UserName)]string username,
ErrorReporter errors, IBus bus) {
_user = username;
_modules = new ObservableAutoWrapper<ShellModuleVM, IShellModule>(modules, m => new ShellModuleVM(m, bus));
@@ -45,12 +45,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Margin="4" Text="{Binding SaveName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" />
<CheckBox Margin="4,0,4,0" Grid.Row="1" IsChecked="{Binding IsPublicWorkspace}" HorizontalAlignment="Left">Shared</CheckBox>
<Button Grid.Column="1" HorizontalAlignment="Right" Margin="4" Command="{Binding SaveWorkspace}" IsDefault="True" MinWidth="60">Save</Button>
</Grid>
</Grid>
+1 -2
View File
@@ -253,14 +253,13 @@ namespace mbts.co2 {
}
}
[StructLayout(LayoutKind.Sequential, Size = 712)]
[StructLayout(LayoutKind.Sequential, Size = 704)]
internal struct ColdEntry {
public int exchange_status;
// Last trade
public AtomicPriceSize last_trade;
public ulong total_volume;
public utils.LLDateTime last_trade_time;
private AggressorSide last_side;
//// Settlement
public UInt64 misc_timestamp;
+19 -19
View File
@@ -1,36 +1,33 @@
using bts.api;
using bts.api.synthetic;
using mbts;
using mbts.co2;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Linq;
using SecurityKey = System.UInt32;
using System.Collections.Concurrent;
using System.Threading;
using bts.api.user;
using System.Collections.Generic;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using SecurityKey = System.UInt32;
namespace bts.md
{
namespace bts.md {
[Flags]
enum MDSourceOptions {
DeferInit = 1,
}
class MDSource : IObserver<List<SecurityAndHandle>>, IDisposable
{
{
const uint _no_user_id = 0;
protected readonly MDConsumer _consumer = new MDConsumer();
protected readonly Co2RelayControl _relay;
protected DateTime _version = DateTime.MinValue;
protected readonly ConcurrentDictionary<SecurityKey, SecurityMD> _exchange_sec_map = new ConcurrentDictionary<SecurityKey, SecurityMD>();
protected List<IDisposable> _dispose = new List<IDisposable>();
protected readonly MDSourceOptions _options;
readonly uint _user_id;
public MDSource(uint naggle_ms, MDSourceOptions options = 0)
public MDSource(uint naggle_ms, MDSourceOptions options = 0, uint user_id = _no_user_id)
{
_options = options;
_user_id = user_id;
_relay = new Co2RelayControl(_consumer, naggle_ms);
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
_dispose.Add(_consumer.on_security.Subscribe(this));
@@ -54,12 +51,15 @@ namespace bts.md
public void OnCompleted() { }
public void OnError(Exception error) => Log.Md.error(error.ToString());
public void OnNext(List<SecurityAndHandle> values)
{
if (values.Count > 0)
{
foreach (var v in values)
{
public void OnNext(List<SecurityAndHandle> values) {
if (values.Count > 0) {
foreach (var v in values) {
if (_user_id != _no_user_id &&
v.security.ContractCase == Security.ContractOneofCase.Synthetic &&
v.security.Synthetic.UserId != _user_id &&
v.security.Synthetic.UserId != _no_user_id) {
continue;
}
var smd = new SecurityMD(new SecurityImmutable(v.security), v.handle);
_exchange_sec_map.AddOrUpdate(smd.security.Id, smd, (_, __) => smd);
}
@@ -93,4 +93,4 @@ namespace bts.md
}
}
}
}
Binary file not shown.
@@ -13,6 +13,8 @@ namespace bts.excel.rtd
class JsonFormatter : IExcelFormatter
{
private object _data;
static Google.Protobuf.JsonFormatter _protoformatter =
new Google.Protobuf.JsonFormatter(Google.Protobuf.JsonFormatter.Settings.Default.WithFormatDefaultValues(true));
public JsonFormatter(object data)
{
_data = data;
@@ -81,7 +83,7 @@ namespace bts.excel.rtd
{
string json = null;
if(data is IMessage imsg)
json = Google.Protobuf.JsonFormatter.Default.Format(imsg);
json = _protoformatter.Format(imsg);
else
json = JsonConvert.SerializeObject(data);
+4
View File
@@ -73,6 +73,10 @@ Commands:
order <exchange> <security_type> <symbol> <security_desc> <signed_quantity> [<price>]
Arguments:
--pump=<pump_interval_in_ms=100 default>
Example:
To print simulated market data to the screen:
spark_sdk.exe md SIM FUTURE ES SIM:ESU9 --user=bts
```
* Check out sample code
+2 -1
View File
@@ -143,6 +143,7 @@ namespace bts.tests.utils {
products = tmpp.value;
break;
}
await Task.Delay(10000);
}
bts.test.Assert.IsTrue(products != null, "No products found");
@@ -173,7 +174,7 @@ namespace bts.tests.utils {
Result<Response> oret = await OrderUtilities.send_order(order_service, odef, 0,1);
var sub = order_service.subscribei_orders().Subscribe(async update => {
if(service_value == null) {
if(service_value == null && oret.value.OrderKey > 0) {
await OrderUtilities.cancel_orders(order_service, null, oret.value.OrderKey);
service_value = update;
}
+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/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.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/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/quickbus_cfg.proto;config/protos/spreader.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/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/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.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/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;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;metrics/system_metrics.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/MDSupplementarySource.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SODPosition.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServices.cpp;orderman/actors/order_manLine 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/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.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/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/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/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.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/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/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.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/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;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;metrics/system_metrics.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/MDSupplementarySource.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SODPosition.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderMLine 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/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.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/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/quickbus_cfg.proto;config/protos/spreader.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/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/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.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/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;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.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/crypto/CipherType.cpp;utils/crypto/CryptoInit.cpp;utils/crypto/Digest.cpp;utils/crypto/KeyCodeHelper.cpp;utils/crypto/PasswordCallback.cpp;utils/crypto/PrivateKey.cpp;utils/crypto/PublicKey.cpp;utils/crypto/iostreambio.cpp;utils/datetime.cpp;utils/download.cpp;utLine 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/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.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/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/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/quickbus_cfg.proto;config/protos/span.proto;config/protos/spreader.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/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/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.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/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;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.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/crypto/CipherType.cpp;utils/crypto/CryptoInit.cpp;utils/crypto/Digest.cpp;utils/crypto/KeyCodeHelper.cpp;utils/crypto/PasswordCallback.cpp;utils/crypto/PrivateKey.cpp;utils/cryptoLine truncated
+3 -2
View File
@@ -50,6 +50,7 @@ message VoidMessage {}
message BytesMessage {
bytes buffer = 1;
}
message BoolResultMessage {
bool success = 1;
message ResultMessage {
bool success = 1;
string error_message = 2;
}
+7 -2
View File
@@ -27,7 +27,12 @@ namespace api {
_removed_items(std::move(removed))
{}
Update(const Update& other) = delete;
Update(const Update& other) : _all_items(other._all_items),
_added_items(other._added_items),
_changed_items(other._changed_items),
_removed_items(other._removed_items)
{}
Update(Update&& other) :
_all_items(std::move(other._all_items)),
_added_items(std::move(other._added_items)),
@@ -104,4 +109,4 @@ namespace api {
}
}}//namespace
}}//namespace
+6
View File
@@ -149,6 +149,7 @@ message BTSAccount{
ExchangeAccountData exchange_account_data = 110;
// extra parameters for risk enforcement
RiskLimitEnforcement risk_limit_enforcement=120;
AccountLossLimit account_loss_limit = 130;
}
message RiskLimitEnforcement{
@@ -190,6 +191,11 @@ message AccountExchangeConnections{
repeated string exchange_connection_names = 20;
}
message AccountLossLimit{
bool enforce = 10;
uint32 total_loss_limit = 20;
// if we need per product we can add it here
}
/************************************************************************/
/***************************Exchange Data********************************/
/************************************************************************/
+67
View File
@@ -0,0 +1,67 @@
syntax="proto3";
package bts.api.position;
option csharp_namespace="bts.api.position";
import "lib/api/apioptions.proto";
import "lib/api/apicore.proto";
import "lib/api/services/PositionService.proto";
message MarginResult {
enum ResultStatus { NewValid = 0; CachedValid = 1; Error = 2; }
string account = 10 [(bts.api.key)=true];
ResultStatus status = 12;
string portfolio_id = 15;
string settle_indicator = 20;
string settle_qualifier = 30;
string as_of_time = 40;
string calculation_id = 50;
double opt_value = 100;
double nonopt_value = 110;
double maint_margin = 120;
double initial_margin = 130;
}
message ExcessLiquidityReport {
string account = 10 [(bts.api.key)=true];
uint32 account_id = 11;
double leverage_factor = 20;
double cash_balance = 30;
MarginResult margin_result = 40;
AccountPNL account_pnl = 50;
bool should_enforce_margin = 60;
double excess_liquidity = 100;
}
message MarginRequest {
string account = 10;
}
message CashFactorUpdate {
string account = 10;
double leverage_factor = 20;
}
message CashBalanceUpdate {
string account = 10;
double cash_balance = 20;
}
message EnforceUpdate {
string account = 10;
bool should_enforce_margin = 20;
}
service MarginService {
option (bts.api.service_description) = "Provides CME Margin Information per account";
rpc get_margin(MarginRequest) returns (MarginResult);
rpc get_excess_liquidity_report(MarginRequest) returns (ExcessLiquidityReport);
rpc set_should_enforce_margin(EnforceUpdate) returns (bts.api.ResultMessage);
rpc set_cash_factor(CashFactorUpdate) returns (bts.api.ResultMessage);
rpc set_cash_balance(CashBalanceUpdate) returns (bts.api.ResultMessage);
rpc margin_data(bts.api.VoidMessage) returns (stream MarginResult);
rpc excess_liquidity_report(bts.api.VoidMessage) returns (stream ExcessLiquidityReport);
}
+1 -1
View File
@@ -160,8 +160,8 @@ message JoinOrderDefinition {
CustomOrderDefinition child = 6;
}
message TopOrderDefinition {
uint32 watch_level_tick_offset = 1;
spark.QuantityMeasure top_threshold = 2;
uint32 ticks_off = 3;
CustomOrderDefinition child = 6;
}
message TriggerOrderDefinition {
+19 -1
View File
@@ -8,6 +8,7 @@ import "lib/domain_model/Security.proto";
message PositionUnit{
int32 record_id = 1 [(bts.api.key)=true];
string account = 10;
uint32 account_id = 15;
uint32 security_id = 20;
double sod_long_pos = 30;
double sod_long_avg_price = 40;
@@ -19,6 +20,22 @@ message PositionUnit{
double day_short_avg_price = 100;
}
message PNLUnit{
int32 record_id = 1 [(bts.api.key)=true];
PositionUnit pos = 10;
double cash_value = 20;
double bid_price = 30;
double ask_price = 40;
double last_price = 50;
double settlement_price = 60;
}
message AccountPNL{
uint32 account_id = 1 [(bts.api.key)=true];
string account_name = 10;
double day_pnl = 20;
}
message ManualFill {
int32 record_id = 1 [(bts.api.key)=true];
uint32 security_key = 2;
@@ -53,5 +70,6 @@ service PositionService {
// subscription
rpc manual_fills(bts.api.VoidMessage) returns (stream ManualFill);
rpc position_data(bts.api.VoidMessage) returns (stream PositionUnit);
rpc pnl_data(bts.api.VoidMessage) returns (stream PNLUnit);
rpc pnl_account_data(bts.api.VoidMessage) returns (stream AccountPNL);
}
+9
View File
@@ -0,0 +1,9 @@
SpanMargin {
value {
enabled: false
login: 'api_bts1'
password: 'Summer123'
url: 'https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/'
}
}
+1 -1
View File
@@ -8,4 +8,4 @@ OrdersSoftCleanup {
value {
enabled: true
}
}
}
+5
View File
@@ -141,6 +141,11 @@ message TopAttachUIOptions {
optional bool show_quantity = 2 [default = false];
}
message OffsetAttachUIOptions {
optional bool show_price = 1 [default = false];
optional bool show_quantity = 2 [default = false];
}
message ManualFillsOptions {
optional bool modal_transactions = 1 [default = true];
}
+10
View File
@@ -0,0 +1,10 @@
syntax='proto2';
package bts.config;
message SpanMargin
{
optional bool enabled = 10;
optional string login = 20;
optional string password = 30;
optional string url = 40;
}
+1 -1
View File
@@ -53,4 +53,4 @@ message EnsureOnScreenWithWorkAreaRect {
// and the new ensure_onscreen code has only been tested with a single monitor arrangement.
// Should verify that the code works with every monitor arrangement before enabling by default.
optional bool enabled = 1 [default = false];
}
}
+1
View File
@@ -143,6 +143,7 @@ message SpreadContract {
message SyntheticContract {
string name = 1;
uint32 user_id = 2;
repeated ContractLeg legs = 20;
PriceCalc price_calc = 30;
enum PriceCalc {
+2 -3
View File
@@ -102,14 +102,13 @@ void MDProducerHandle::set_status(CoconutSecurityStatus status) {
}
void MDProducerHandle::add_trade(TimeStamp time, const PriceSize price, LLDateTime tradetime, co2::AggressorSide agside, uint32_t volume) {
// Add this trade to the last trade if price/side match
// Add this trade to the last trade if price match
auto current = std::get<PriceSize>(cdata().last_trade.load());
if (current.int_price() == price.int_price() && agside == cdata().last_side) {
if (current.int_price() == price.int_price()) {
cdata().last_trade.store(PriceSize { price.int_price(), price.size() + current.size() }, time);
}
else {
cdata().last_trade.store(price,time);
cdata().last_side = agside;
}
cdata().last_trade_time = tradetime;
cdata().total_volume = volume;
+2
View File
@@ -22,6 +22,7 @@ void SecurityCacheImpl::enqueue_update_db(const SecurityPtr& security) {
}
void SecurityCacheImpl::flush() {
#ifndef _WIN32
if (!update_queue.empty()) {
std::vector<SecurityPtr> work;
SecurityPtr temp;
@@ -33,6 +34,7 @@ void SecurityCacheImpl::flush() {
}
}
}
#endif
}
//////////////////
+1 -3
View File
@@ -26,7 +26,7 @@ namespace md {
using utils::TimeStamp;
const uint32_t shm_version = 101;
const uint32_t shm_version = 102;
struct ALIGNED_(CACHE_LINE) HotHeader {
size_t count;
@@ -214,7 +214,6 @@ struct ColdEntry {
last_trade(),
total_volume(0),
last_trade_time(),
last_side(co2::AggressorSide::AGGRESSOR_SIDE_NONE),
misc(),
deleted(false)
{
@@ -230,7 +229,6 @@ struct ColdEntry {
AtomicPriceSize last_trade;
size_t total_volume;
datetime::LLDateTime last_trade_time;
co2::AggressorSide last_side;
MiscData misc;
+110 -2
View File
@@ -111,7 +111,7 @@ tuple<shared_ptr<SyntheticStrategy>,vector<Co2TestInstrument>> make_securities(c
l->mutable_security()->mutable_contract_spec()->set_low_tick(tick_size);
l->mutable_security()->mutable_contract_spec()->set_high_tick(tick_size);
l->set_quantity(quantity);
l->set_price_multiplier(quantity * (side == api::SIDE_BUY ? 1.0 : -1));
l->set_price_multiplier(mult);
l->set_side(side);
synthetic->legs.push_back(instr.consumer());
legs.push_back(instr);
@@ -126,7 +126,8 @@ tuple<shared_ptr<SyntheticStrategy>,vector<Co2TestInstrument>> make_securities(c
double quantity, tick_size;
api::Side side;
std::tie(quantity, tick_size, side) = t;
legs.push_back({ quantity, quantity, tick_size, side });
double mult = quantity * (side == api::SIDE_BUY ? 1.0 : -1);
legs.push_back({ quantity, mult, tick_size, side });
}
return make_securities(legs);
}
@@ -457,6 +458,113 @@ TEST_CASE("Synthetic_net_change", "[md][synthetic]") {
}
}
TEST_CASE("HCL_SP_104", "[md][synthetic]") {
shared_ptr<SyntheticStrategy> synthetic;
vector<Co2TestInstrument> test_instr;
tie(synthetic, test_instr) = make_securities({
// (quantity, multiplier, tick_size, side)
{1.0, 1.0, 0.01, api::SIDE_BUY},
{1.0, 1.0, 0.01, api::SIDE_BUY},
{1.0, -1.0, 0.01, api::SIDE_SELL},
{1.0, -1.0, 0.01, api::SIDE_SELL }
});
auto leg1 = test_instr[0]; // CLQ9-CLU9
auto leg2 = test_instr[1]; // HCLQ9-CLQ9
auto leg3 = test_instr[2]; // HCLQ9-HCLU9
auto leg4 = test_instr[3]; // HCLU9-CLU9
setup_market(leg1.producer(),
{
{ -0.10, 3299 },
{ -0.11, 2885 },
{ -0.12, 2558 },
{ -0.13, 2447 },
{ -0.14, 1540 },
{ -0.15, 2334 },
{ -0.16, 1396 },
{ -0.17, 1190 },
{ -0.18, 1221 },
{ -0.19, 812 }
},
{
{ -0.09, 1318 },
{ -0.08, 2174 },
{ -0.07, 3057 },
{ -0.06, 2331 },
{ -0.05, 2543 },
{ -0.04, 2638 },
{ -0.03, 2232 },
{ -0.02, 3221 },
{ -0.01, 2863 },
{ 0.00, 3327 }
});
setup_market(leg2.producer(),
{
{ 4.06, 1 },
{ 4.05, 2 },
{ 4.04, 3 },
{ 4.03, 12 },
{ 4.02, 6 },
{ 4.01, 1 }
},
{
{ 4.07, 1 },
{ 4.08, 2 },
{ 4.10, 8 },
{ 4.11, 10 },
{ 4.12, 2 },
{ 4.14, 1 }
});
setup_market(leg3.producer(),
{
{ 0.20, 2 },
{ 0.19, 6 },
{ 0.18, 5 },
{ 0.17, 18 },
{ 0.16, 2 },
{ 0.15, 3 },
{ 0.14, 2 }
},
{
{ 0.22, 2 },
{ 0.23, 4 },
{ 0.24, 9 },
{ 0.25, 17},
{ 0.26, 5 },
{ 0.27, 5 },
{ 0.28, 4 },
{ 0.29, 2 },
{ 0.30, 2 }
});
setup_market(leg4.producer(),
{
{ 3.75, 1 },
{ 3.74, 3 },
{ 3.73, 4 },
{ 3.72, 12 },
{ 3.71, 1 },
{ 3.70, 3 }
},
{
{ 3.78, 1 },
{ 3.79, 2 },
{ 3.80, 6 },
{ 3.81, 1 },
{ 3.82, 10 },
{ 3.83, 1 }
});
MDConsumerHandle synthetic_handle(synthetic);
// dump_market(synthetic_handle);
expect_market(synthetic_handle,
{ { -0.04,1 },{ -0.06,1 },{ -0.07,1 },{ -0.09,3 },{ -0.11,3 },{ -0.12,1 },{ -0.13,5 },{ -0.14,3 },{ -0.15,2 },{ -0.16,1 } },
{ { 0.03,1 },{ 0.05,1 },{ 0.06,1 },{ 0.08,1 },{ 0.09,4 },{ 0.11,3 },{ 0.12,2 },{ 0.13,7 },{ 0.14,1 },{ 0.16,2 } }
);
}
//
// Benchmarks
+2
View File
@@ -126,6 +126,7 @@ void FillStore::on_execution_report(
if (it != me->known_orders.end()) {
auto &order_def = it->second;
fill.set_account(order_def.account());
fill.set_internal_account_id(order_def.account_id());
} else
me->fills_with_unresolved_accounts[er.order_key]
.fills.push_back(fill);
@@ -153,6 +154,7 @@ void FillStore::on_order_definition_update(
auto &unresolved_fills = me->fills_with_unresolved_accounts[ev.key];
for (auto &f : unresolved_fills.fills) {
f.set_account(ev.def->account());
f.set_internal_account_id(ev.def->account_id());
auto fv = utils::pb::to_string(f);
me->store->update(fills_ns, f.fill_key(), fv);
publish(me->fill_stream, f, *unresolved_fills.security);
+16 -7
View File
@@ -1,6 +1,7 @@
#include "lib/orderman/actors/SODPosition.h"
#include "lib/config/config.h"
#include "lib/config/protos/orders_fills_cleanup.pb.h"
#include "lib/api/service_helpers/AdminServiceUtils.h"
#include "lib/config/protos/toggles.pb.h"
#include "lib/logging/logging.h"
#include "lib/utils/PBUtils.h"
@@ -131,6 +132,7 @@ void SODPosition::_merge_pos(Fill &pos, const Fill &fill) {
void SODPosition::_zero_pos(Fill &pos, const Fill &fill, api::Side side) {
pos.set_security_key(fill.security_key());
pos.set_account(fill.account());
pos.set_internal_account_id(fill.internal_account_id());
pos.set_side(side);
pos.mutable_timestamp()->set_seconds(_sod_time.tv_sec);
pos.mutable_timestamp()->set_nanos(0);
@@ -140,14 +142,20 @@ void SODPosition::_zero_pos(Fill &pos, const Fill &fill, api::Side side) {
pos.set_last_shares(0);
}
bool SODPosition::add_fill_to_sod_pos(const Fill &fill) {
bool SODPosition::add_fill_to_sod_pos(const Fill &fill) {
if (_enabled && fill.timestamp().seconds() < _sod_time.tv_sec) {
uint32_t seckey = fill.security_key();
const std::string &acct = fill.account();
auto acct_it = _sod_map.find(acct);
if (!bts::admin::AdminServiceUtils::has_id(fill.internal_account_id())){
LOG(persist, Error, "SODPosition::add_fill_to_sod_pos Could not find "
"account_id for fill:{} account:{}",
fill.fill_key(), fill.account());
return false;
}
uint32_t seckey = fill.security_key(),
account_id = fill.internal_account_id();
auto acct_it = _sod_map.find(account_id);
if (acct_it == _sod_map.end())
_sod_map[acct] = SecPosMap();
acct_it = _sod_map.find(fill.account());
_sod_map[account_id] = SecPosMap();
acct_it = _sod_map.find(account_id);
bts_assert(acct_it != _sod_map.end());
SecPosMap &sec_map = acct_it->second;
auto sec_it = sec_map.find(seckey);
@@ -165,7 +173,8 @@ bool SODPosition::add_fill_to_sod_pos(const Fill &fill) {
else
_merge_pos(pos.second, fill);
return true;
} else
}
else
return false;
}
+1 -2
View File
@@ -16,8 +16,7 @@ SecPosMap;
class SODPosition{
private:
std::map<std::string,SecPosMap> _sod_map;
std::map<uint32_t, const api::Security*> _sec_ptr_map;
std::map<uint32_t,SecPosMap> _sod_map;
const bool _enabled;
const timeval _sod_time;
private:
+40 -4
View File
@@ -1,10 +1,14 @@
#include "ServiceBridge.h"
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/api/services/PositionService.pb.h"
#include "lib/mercury/broker/BrokerProxy.h"
#include "lib/orderman/events/AccountPnLUpdateEvent.h"
#include "lib/orderman/events/ActivateOrderEvent.h"
#include "lib/orderman/events/CancelOrderEvent.h"
#include "lib/orderman/events/DetachOrderEvent.h"
#include "lib/orderman/events/ExcessLiquidityUpdateEvent.h"
#include "lib/orderman/events/NewAdminDataEvent.h"
#include "lib/orderman/events/OrderManagerReadyEvent.h"
#include "lib/orderman/events/OrderOpResultEvent.h"
@@ -13,13 +17,11 @@
#include "lib/orderman/events/UpdateOrderEvent.h"
#include "lib/orderman/order_factory/OrderFactory.h"
#include "lib/orderman/order_router/OrderRouter.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/orderman/orders/Order.h"
#include "lib/orderman/securities/MapSecurityDB.h"
#include "lib/tracing/PerfLog.h"
#include "lib/utils/PBUtils.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/orderman/orders/Order.h"
#include <future>
namespace bts {
@@ -50,6 +52,8 @@ struct ServiceBridge::PImpl {
mercury::Stream<CancelOrderEvent> cancel_order_pub;
mercury::Stream<DetachOrderEvent> detach_order_pub;
mercury::Stream<NewAdminDataEvent> new_admin_data_pub;
mercury::Stream<AccountPnLUpdateEvent> account_pnl_pub;
mercury::Stream<ExcessLiquidityUpdateEvent> excess_liquidity_pub;
std::unordered_set<OrderKey> known_orders;
std::vector<std::shared_ptr<const api::admin::AdminData>> admin_data_updates;
mercury::BrokerProxy &service_call_marshaller;
@@ -81,6 +85,8 @@ void ServiceBridge::init() {
me->cancel_order_pub = make_stream<CancelOrderEvent>();
me->detach_order_pub = make_stream<DetachOrderEvent>();
me->new_admin_data_pub = make_stream<NewAdminDataEvent>();
me->account_pnl_pub = make_stream<AccountPnLUpdateEvent>();
me->excess_liquidity_pub = make_stream<ExcessLiquidityUpdateEvent>();
me->ready.set_value();
}
@@ -217,6 +223,36 @@ void ServiceBridge::update_admin_data(
});
}
void ServiceBridge::update_account_pnl(
api::position::Pnl_account_dataUpdate const &pnl) {
me->service_call_marshaller.schedule_action([this, pnl]() {
for (auto const &account_pnl : pnl.added()) {
publish(me->account_pnl_pub, AccountID(account_pnl->account_id()),
account_pnl->day_pnl());
}
for (auto const &account_pnl : pnl.changed()) {
publish(me->account_pnl_pub,
AccountID(account_pnl.current()->account_id()),
account_pnl.current()->day_pnl());
}
});
}
void ServiceBridge::update_excess_liquidity_report(
api::position::Excess_liquidity_reportUpdate const &elr) {
me->service_call_marshaller.schedule_action([this, elr]() {
for (auto const &elr : elr.added()) {
publish(me->excess_liquidity_pub, AccountID(elr->account_id()),
elr->excess_liquidity());
}
for (auto const &elr : elr.changed()) {
publish(me->excess_liquidity_pub,
AccountID(elr.current()->account_id()),
elr.current()->excess_liquidity());
}
});
}
bool ServiceBridge::is_ready() const {
return me->have_orders && me->order_manager_ready;
}
+18 -9
View File
@@ -1,18 +1,26 @@
#pragma once
#include "lib/api/services/MarginService.client.pb.h"
#include "lib/api/services/PositionService.client.pb.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/mercury/actors/Actor.h"
#include "lib/md/SecurityOracle.h"
#include "lib/mercury/actors/Actor.h"
#include <memory>
// fwds
namespace bts {
namespace mercury {class BrokerProxy;}
namespace mercury {
class BrokerProxy;
}
namespace api {
class ServiceContext;
namespace admin {
class AdminData;
}
namespace position {
class AccountPNL;
}
namespace orderman {
class OrderRecord;
class Fill;
@@ -45,16 +53,14 @@ class ServiceBridge : public mercury::Actor {
using OnReady = std::function<void()>;
using OnOrders =
std::function<void(std::vector<api::orderman::OrderRecord> const &)>;
std::function<void(std::vector<api::orderman::OrderRecord> const &)>;
using OnFills =
std::function<void(std::vector<api::orderman::Fill> const &)>;
std::function<void(std::vector<api::orderman::Fill> const &)>;
using OnOrderOpResult = std::function<void(OrderOpResultEvent const &)>;
using OnOrderOpResult =
std::function<void(OrderOpResultEvent const&)>;
ServiceBridge(OnReady const &, OnOrders const &, OnFills const &,
OnOrderOpResult const&,
DynamicOrderRegistry const &,
OnOrderOpResult const &, DynamicOrderRegistry const &,
mercury::BrokerProxy &service_call_marshaller,
md::SecurityOracle o);
void init() override;
@@ -71,6 +77,9 @@ class ServiceBridge : public mercury::Actor {
void
update_admin_data(std::shared_ptr<api::admin::AdminData const> const &admin);
void update_account_pnl(api::position::Pnl_account_dataUpdate const &);
void update_excess_liquidity_report(
api::position::Excess_liquidity_reportUpdate const &);
private:
struct PImpl;
@@ -4,6 +4,8 @@
#include "lib/mercury/events/TimerEvent.h"
#include "lib/orderman/actors/order_manager/OrderServices.h"
#include "lib/orderman/actors/order_manager/OrderServicesData.h"
#include "lib/orderman/events/AccountPnLUpdateEvent.h"
#include "lib/orderman/events/ExcessLiquidityUpdateEvent.h"
#include "lib/orderman/events/NewAdminDataEvent.h"
#include "lib/orderman/events/NewSecuritiesEvent.h"
#include "lib/orderman/events/OrderManagerReadyEvent.h"
@@ -52,6 +54,11 @@ OrderManager::~OrderManager() {}
void OrderManager::init() {
subscribe<NewAdminDataEvent>(
[this](auto const &admin_update) { this->on_admin_data(admin_update); });
subscribe<AccountPnLUpdateEvent>(
[this](auto const &pnl_update) { this->on_pnl_update(pnl_update); });
subscribe<ExcessLiquidityUpdateEvent>([this](auto const &liq_update) {
this->on_excess_liquidity_update(liq_update);
});
}
void OrderManager::on_admin_data(const mercury::Update<NewAdminDataEvent> &m) {
@@ -74,6 +81,20 @@ void OrderManager::on_admin_data(const mercury::Update<NewAdminDataEvent> &m) {
}
}
void OrderManager::on_pnl_update(
const mercury::Update<AccountPnLUpdateEvent> &u) {
if (u.events)
for (auto ev : *u.events)
me->services_data.limiter.set_day_pnl(ev.account_id, ev.day_pnl);
}
void OrderManager::on_excess_liquidity_update(
const mercury::Update<ExcessLiquidityUpdateEvent> &u) {
if (u.events)
for (auto ev : *u.events)
me->services_data.limiter.set_excess_liquidity(ev.account_id, ev.excess_liquidity);
}
void OrderManager::on_securities(
const mercury::Update<NewSecuritiesEvent> &update) {
@@ -116,9 +137,9 @@ void OrderManager::on_fill_recovery(
for (auto const &ev : *(update.events)) {
auto trimmed_account_name =
boost::algorithm::trim_copy(ev.fill.account());
me->services_data.limiter.add(*ev.security, trimmed_account_name,
ev.fill.side(),
Quantity(ev.fill.last_shares()),false);
me->services_data.limiter.add(
*ev.security, trimmed_account_name, ev.fill.side(),
Quantity(ev.fill.last_shares()), false);
}
me->stored_order_sub = subscribe<StoredOrderEvent>(
@@ -34,6 +34,8 @@ class OrderServices;
class CreateFIXBufferReplyEvent;
class GatewayStatusChangeEvent;
class NewAdminDataEvent;
class AccountPnLUpdateEvent;
class ExcessLiquidityUpdateEvent;
struct SessionServicesData;
class DynamicOrderRegistry;
} // namespace orderman
@@ -83,6 +85,8 @@ class OrderManager : public mercury::Actor, public mercury::IPollableActor {
void on_gateway_state(const mercury::Update<GatewayStatusChangeEvent> &);
void on_timer(const mercury::Update<mercury::TimerEvent> &);
void on_admin_data(const mercury::Update<NewAdminDataEvent> &);
void on_pnl_update(const mercury::Update<AccountPnLUpdateEvent> &);
void on_excess_liquidity_update(const mercury::Update<ExcessLiquidityUpdateEvent> &);
void on_poll(trace::PerfLog& log) override;
// end external events
@@ -0,0 +1,27 @@
#pragma once
#include "lib/api/services/AdminService.pb.h"
#include "lib/mercury/common/mercury_common.h"
#include "lib/mercury/events/Event.h"
#include "lib/mercury/synopsis/MergeByKey.h"
#include <memory>
namespace bts {
namespace orderman {
class AccountPnLUpdateEvent : public mercury::Event<AccountPnLUpdateEvent> {
public:
using SynopsisPolicy = mercury::MergeByKey<AccountPnLUpdateEvent>;
AccountID key() const { return account_id; }
AccountPnLUpdateEvent(AccountID id, double pnl)
: account_id(id), day_pnl(pnl) {}
AccountID account_id;
double day_pnl;
};
} // namespace orderman
} // namespace bts
@@ -0,0 +1,25 @@
#pragma once
#include "lib/mercury/common/mercury_common.h"
#include "lib/mercury/events/Event.h"
#include "lib/mercury/synopsis/MergeByKey.h"
#include <memory>
namespace bts {
namespace orderman {
class ExcessLiquidityUpdateEvent : public mercury::Event<ExcessLiquidityUpdateEvent> {
public:
using SynopsisPolicy = mercury::MergeByKey<ExcessLiquidityUpdateEvent>;
AccountID key() const { return account_id; }
ExcessLiquidityUpdateEvent(AccountID id, double excess_liquidity)
: account_id(id), excess_liquidity(excess_liquidity) {}
AccountID account_id;
double excess_liquidity;
};
} // namespace orderman
} // namespace bts
+24 -3
View File
@@ -5,8 +5,10 @@ namespace orderman {
LimitStore::LimitStore(const bts::api::admin::AdminData &admin_data,
const std::map<uint32_t, api::Security const *> &sec_map)
: _admin_data(bts::admin::AdminServiceUtils::sort_limits(admin_data)),
_account_map(bts::admin::AdminServiceUtils::accounts_map(_admin_data)) {
: _admin_data(bts::admin::AdminServiceUtils::sort_limits(admin_data)) {
for (const auto& acct : _admin_data.accounts())
_account_data_map.insert(std::pair<uint32_t,AccountData>(
acct.id(),AccountData(acct)));
for (auto it = sec_map.begin(); it != sec_map.end(); it++) {
const api::Security &security = *(it->second);
_add_product_entry(security, _admin_data);
@@ -22,7 +24,7 @@ LimitStore::LimitStore(const bts::api::admin::AdminData &admin_data,
}
_set_user_data();
}
void LimitStore::copy_positions_from(LimitStore &source) {
void LimitStore::copy_data_from(LimitStore &source) {
// copy securities
for (auto it = _sec_map.begin(); it != _sec_map.end(); it++) {
SecurityLimitTree *source_security_limit_tree =
@@ -30,6 +32,25 @@ void LimitStore::copy_positions_from(LimitStore &source) {
if (source_security_limit_tree != nullptr)
it->second.copy_positions_from(*source_security_limit_tree);
}
// copy pnl's and liqs
for (auto it=_account_data_map.begin();it!=_account_data_map.end();it++){
auto source_it = source._account_data_map.find(it->first);
if(source_it != source._account_data_map.end()) {
it->second.curr_day_pnl = source_it->second.curr_day_pnl;
it->second.excess_liquidity = source_it->second.excess_liquidity;
}
}
}
void LimitStore::set_pnl (AccountID account_id, double pnl){
auto it = _account_data_map.find(account_id.value());
bts_assert(it != _account_data_map.end());
it->second.curr_day_pnl = pnl;
}
void LimitStore::set_excess_liquidity (AccountID account_id,
double excess_liquidity){
auto it = _account_data_map.find(account_id.value());
bts_assert(it != _account_data_map.end());
it->second.excess_liquidity = excess_liquidity;
}
void LimitStore::_add_product_entry(
const api::Security &security,
+125 -13
View File
@@ -9,6 +9,10 @@
#include "Limiter.h"
#include "ProductLimitTree.h"
#include "SecurityLimitTree.h"
#include "lib/os/os.h"
#include "lib/config/config.h"
#include "lib/config/protos/span.pb.h"
namespace bts {
namespace orderman {
@@ -33,9 +37,22 @@ private:
return nullptr;
}
};
class AccountData{
public:
const bts::api::admin::BTSAccount& account;
double curr_day_pnl;
double excess_liquidity;
AccountData (const bts::api::admin::BTSAccount& account)
:
account(account),
curr_day_pnl (os::nan()),
excess_liquidity (os::nan())
{}
};
const bts::api::admin::AdminData _admin_data;
// just an easy way to find accounts
const std::map<uint32_t, bool> _account_map;
// easy account access (uses for loss limit)
std::map<uint32_t, AccountData> _account_data_map;
// map of user id's vs the user pointer and acccounts
std::map<uint32_t, UserData> _user_data_map;
// map of securities vs their account limits and positions
@@ -52,14 +69,20 @@ public:
// accessors ///////////////
////////////////////////////
bool has_account (AccountID account_id) const
{return _account_map.find(account_id.value()) != _account_map.end();}
bool has_account (AccountID account_id) const{
return _account_data_map.find(account_id.value()) !=
_account_data_map.end();
}
bool has_security (uint32_t sec_id) const
{return _sec_map.find(sec_id) != _sec_map.end();}
const bts::api::admin::AdminData& admin_data () const {return _admin_data;}
// position updates ////////
////////////////////////////
void copy_positions_from(LimitStore& source);
// data updates ////////////
// once the LimitStore is created its position and
// pnl is updated from outside.
////////////////////////////
// copy_data_from copies the position and
// data from another source
void copy_data_from(LimitStore& source);
void add_position(const api::Security& security,
AccountID account_id, api::Side side,
Quantity quantity, bool recurse){
@@ -88,6 +111,8 @@ public:
}
}
}
void set_pnl (AccountID account_id, double pnl);
void set_excess_liquidity (AccountID account_id, double excess_liquidity);
// risk limit checks
////////////////////
// checks to see if the user is allowed to trade
@@ -130,11 +155,21 @@ public:
Limiter::LimiterQueryResult can_send_new_order(AccountID account_id,
const api::Security& security,
api::Side side,
Quantity quantity) const{
Quantity quantity,
bool enforce_clip = true) const{
Limiter::LimiterQueryResult rv; rv.result = Limiter::OK;
const auto acct_it = _account_data_map.find(account_id.value());
AccountData const* account_ptr = (acct_it == _account_data_map.end()) ?
nullptr : &(acct_it->second);
_loss_limit(account_ptr, rv);
if(!rv.ok())
return rv;
_margin_limit(account_ptr,rv);
if(!rv.ok())
return rv;
const SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
rv = sec_tree->can_send_new_order(account_id, side, quantity);
rv = sec_tree->can_send_new_order(account_id, side, quantity, enforce_clip);
if (rv.ok() && security.has_spread()){
// if its rejected we dont need to validate further,
// however if its ok and its a spread we need to check
@@ -142,7 +177,12 @@ public:
for (const api::ContractLeg& leg : security.spread().legs()){
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_qty = Quantity (quantity.value() * std::abs(leg.quantity()));
rv = can_send_new_order (account_id, leg.security(), effective_leg_side, effective_leg_qty);
// in the case of a spread with future legs the clip limits
// for the legs are not checked. if the legs are are spreads
// themselves then they are checked (eventually it will recuse
// down to a non spread)
rv = can_send_new_order (account_id, leg.security(), effective_leg_side, effective_leg_qty,
leg.security().has_spread());
if (!rv.ok())
return rv;
}
@@ -152,18 +192,29 @@ public:
Limiter::LimiterQueryResult can_update_order(AccountID account_id,
const api::Security& security,
api::Side side,
Quantity from, Quantity to) const{
Quantity from, Quantity to,
bool enforce_clip = true) const{
Limiter::LimiterQueryResult rv; rv.result = Limiter::OK;
const auto acct_it = _account_data_map.find(account_id.value());
AccountData const* account_ptr = (acct_it == _account_data_map.end()) ?
nullptr : &(acct_it->second);
// in case of order modifications, loss limits are only checked
// for size increases
if (to.value() > from.value())
_loss_limit(account_ptr, rv);
if(!rv.ok())
return rv;
const SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
rv = sec_tree->can_update_order(account_id, side, from, to);
rv = sec_tree->can_update_order(account_id, side, from, to, enforce_clip);
// same logic for the spread legs as above
if (rv.ok() && security.has_spread()){
for (const api::ContractLeg& leg : security.spread().legs()){
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_from_qty = Quantity (from.value() * std::abs(leg.quantity()));
Quantity effective_leg_to_qty = Quantity (to.value() * std::abs(leg.quantity()));
rv = can_update_order (account_id, leg.security(), effective_leg_side, effective_leg_from_qty, effective_leg_to_qty);
rv = can_update_order (account_id, leg.security(), effective_leg_side, effective_leg_from_qty, effective_leg_to_qty,
leg.security().has_spread());
if (!rv.ok())
return rv;
}
@@ -190,5 +241,66 @@ private:
void _add_product_entry(const api::Security& security,
const bts::api::admin::AdminData& admin_data);
void _set_user_data();
void _loss_limit(AccountData const* account_ptr,
Limiter::LimiterQueryResult& out_res) const{
// the usual flow should not alter out_res.
// its set to an error state iff :
// i. account is not found
// ii. limit check is enabled but pnl is nan
// iii. limit check is enable and it actually fails
if(account_ptr != nullptr){
const auto& account_data = *account_ptr;
const auto& account = account_data.account;
if (account.has_account_loss_limit() &&
account.account_loss_limit().enforce()){
double pnl = account_data.curr_day_pnl;
if(os::is_nan(pnl)){
out_res.result = Limiter::LimiterErrorCode::LOSS_LIMIT_BREACHED;
out_res.error_msg = "Could not find account PNL";
}
else{
double loss_limit = -1.0 * static_cast<double>(
account.account_loss_limit().total_loss_limit());
if(pnl<loss_limit){
out_res.result = Limiter::LimiterErrorCode::LOSS_LIMIT_BREACHED;
out_res.error_msg = fmt::format("Loss Limit (Max {} Current {})",
loss_limit, pnl);
}
}
}
}
else{
out_res.result = Limiter::LimiterErrorCode::NO_SUCH_ACCOUNT;
out_res.error_msg = "Could not find account";
}
}
void _margin_limit(AccountData const* account_ptr,
Limiter::LimiterQueryResult& out_res) const{
// similiar flow as the loss_limit above
if(account_ptr != nullptr){
const auto& account_data = *account_ptr;
const auto& account = account_data.account;
if(config::get<bts::config::SpanMargin>().enabled()) {
if (account.has_margin_limit() && account.margin_limit().enforce_margin()){
double ex_liq = account_data.excess_liquidity;
if(os::is_nan(ex_liq)){
out_res.result = Limiter::LimiterErrorCode::MARGIN_LIMIT_BREACHED;
out_res.error_msg = "Could not find account Excess Liquidity";
}
else{
if(ex_liq < 0){
out_res.result = Limiter::LimiterErrorCode::MARGIN_LIMIT_BREACHED;
out_res.error_msg = fmt::format("Margin Limit Excess Liquidity : {}",
ex_liq);
}
}
}
}
}
else{
out_res.result = Limiter::LimiterErrorCode::NO_SUCH_ACCOUNT;
out_res.error_msg = "Could not find account";
}
}
};
}}
+15 -1
View File
@@ -73,6 +73,20 @@ void Limiter::remove(const api::Security &security,
else
remove(security, AccountID(acct_ptr->id()), side, quantity, recurse);
}
void Limiter::set_day_pnl (AccountID account_id, double pnl){
if (!me->limit_store->has_account(account_id))
_add_account_and_reset(account_id);
me->limit_store->set_pnl(account_id, pnl);
}
void Limiter::set_excess_liquidity (AccountID account_id,
double excess_liquidity) {
LOG(OrderMan, Info,
"Limiter::set_excess_liquidity account:{} excess_liquidity: {}",
account_id, excess_liquidity);
if (!me->limit_store->has_account(account_id))
_add_account_and_reset(account_id);
me->limit_store->set_excess_liquidity(account_id, excess_liquidity);
}
Limiter::LimiterQueryResult Limiter::can_trade(UserID user_id,
AccountID account_id) const {
LimiterQueryResult rv = me->limit_store->can_trade(user_id, account_id);
@@ -119,7 +133,7 @@ void Limiter::_add_account_and_reset(AccountID account_id) {
}
void Limiter::_reset(const bts::api::admin::AdminData &admin_data) {
LimitStore *new_store = new LimitStore(admin_data, me->sec_map);
new_store->copy_positions_from(*me->limit_store);
new_store->copy_data_from(*me->limit_store);
me->limit_store.reset(new_store);
}
+6 -2
View File
@@ -33,7 +33,9 @@ class Limiter {
api::Side, Quantity, bool recurse);
void remove(api::Security const &, std::string const & /* account_name */,
api::Side, Quantity, bool recurse);
void set_day_pnl (AccountID account_id, double pnl);
void set_excess_liquidity(AccountID account_id, double excesss_liquidity);
enum LimiterErrorCode {
OK,
NO_SUCH_SECURITY,
@@ -45,7 +47,9 @@ class Limiter {
ACCOUNT_DEACTIVATED,
NO_SUCH_ACCOUNT,
USER_DEACTIVATED,
NO_SUCH_USER
NO_SUCH_USER,
LOSS_LIMIT_BREACHED,
MARGIN_LIMIT_BREACHED
};
struct LimiterQueryResult {
+7 -4
View File
@@ -63,9 +63,11 @@ public:
}
// risk limit checks
////////////////////
Limiter::LimiterQueryResult can_send_new_order(api::Side side, Quantity quantity) const{
Limiter::LimiterQueryResult can_send_new_order(api::Side side,
Quantity quantity,
bool enforce_clip = true) const{
Limiter::LimiterQueryResult result;result.result= Limiter::OK;
if (quantity.value() > _clip_size){
if (enforce_clip && quantity.value() > _clip_size){
result.result = Limiter::CLIP_SIZE_BREACHED;
result.error_msg = fmt::format("Security Clip Size (Max {} Qty {})", _clip_size, quantity.value());
return result;
@@ -78,9 +80,10 @@ public:
result = _product_node.can_send_new_order(side, quantity);
return result;
}
Limiter::LimiterQueryResult can_update_order(api::Side side, Quantity from, Quantity to) const{
Limiter::LimiterQueryResult can_update_order(api::Side side, Quantity from, Quantity to,
bool enforce_clip = true) const{
Limiter::LimiterQueryResult result;result.result=Limiter::OK;
if (to.value() > _clip_size){
if (enforce_clip && to.value() > _clip_size){
result.result = Limiter::CLIP_SIZE_BREACHED;
result.error_msg = fmt::format("Security Clip Size (Max {} Qty {})", _clip_size, to.value());
return result;
+6 -4
View File
@@ -50,17 +50,19 @@ public:
////////////////////
Limiter::LimiterQueryResult can_send_new_order(AccountID account_id,
api::Side side,
Quantity quantity) const{
Quantity quantity,
bool enforce_clip = true) const{
const SecurityLimitNode* node_ptr = _find_account(account_id.value());
bts_assert(node_ptr!=nullptr);
return node_ptr->can_send_new_order(side, quantity);
return node_ptr->can_send_new_order(side, quantity, enforce_clip);
}
Limiter::LimiterQueryResult can_update_order(AccountID account_id,
api::Side side,
Quantity from, Quantity to) const{
Quantity from, Quantity to,
bool enforce_clip = true) const{
const SecurityLimitNode* node_ptr = _find_account(account_id.value());
bts_assert(node_ptr!=nullptr);
return node_ptr->can_update_order(side, from, to);
return node_ptr->can_update_order(side, from, to, enforce_clip);
}
// accessors
////////////
@@ -19,8 +19,8 @@
#include "lib/orderman/orders/algo/algo_orders/TopAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/IcebergAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/LeanAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/OffsetAlgo.h"
#include "lib/orderman/orders/book_algos/OCOOrder.h"
#include "lib/orderman/orders/book_algos/OffsetOrder.h"
#include "lib/orderman/orders/book_algos/PricerOrder.h"
#include "lib/orderman/orders/spreader_orders/SpreaderOrder.h"
#include "lib/orderman/orders/CompositeOrder.h"
@@ -183,7 +183,7 @@ Order &OrderFactory::allocate(OrderKey k, OrderKey pk,
new orderman::AlgoWrapperOrder(std::make_unique<CoverAlgo>()));
break;
case CustomOrderDefinition::kOffset:
order_ptr.reset(new orderman::OffsetOrder());
order_ptr.reset(new orderman::AlgoWrapperOrder(std::make_unique<OffsetAlgo>()));
break;
case CustomOrderDefinition::kQueueHolder:
order_ptr.reset(new orderman::QueueHolderOrder());
@@ -14,6 +14,7 @@ bool can_consume_attachment(api::orderman::CustomOrderDefinition::CustomCase ord
case CustomOrderDefinition::kTop:
case CustomOrderDefinition::kCover:
case CustomOrderDefinition::kLean:
case CustomOrderDefinition::kOffset:
return true;
default:
return false;
@@ -44,6 +45,10 @@ bool should_forward_attachment(
return
cdef.lean().has_child() &&
can_consume_attachment(cdef.lean().child().custom_case());
case CustomOrderDefinition::kOffset:
return
cdef.offset().has_child() &&
can_consume_attachment(cdef.offset().child().custom_case());
default:
return false;
}
@@ -0,0 +1,190 @@
#include "lib/orderman/orders/algo/IAlgoServices.h"
#include "lib/orderman/orders/algo/algo_orders/OffsetAlgo.h"
#include "lib/orderman/order_factory/IOrderFactory.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/os/os.h"
#include "lib/utils/PBUtils.h"
namespace bts {
namespace orderman {
using namespace api::orderman;
using namespace std;
enum class OffsetAlgo::Status { WAITING_FOR_MD, DEPLOYED };
struct OffsetAlgo::PImpl {
IAlgoServices *algo;
Status status = Status::WAITING_FOR_MD;
OrderKey child_key = null_order_key();
ChildTemplate *child_template = nullptr;
Book last_book;
uint32_t tick_offset_on_deploy;
double child_price = os::nan();
};
OffsetAlgo::OffsetAlgo() : me(new PImpl()) {}
OffsetAlgo::~OffsetAlgo() {}
IAlgoServices &OffsetAlgo::algo() const { return *me->algo; }
OrderOpResult OffsetAlgo::do_before_launch(IBeforeLaunchAlgoServices &serv) {
auto &def = serv.definition();
bts_assert(def.has_custom_def());
bts_assert(def.custom_def().has_offset());
// Validate the offset part of the definition
auto &offset = def.custom_def().offset();
if (!offset.has_child() &&
OrderKey(def.attach_target_order_key()) == null_order_key()) {
return OrderOpResult{
ORDER_ERROR_INVALID_ORDER_DEFINITON,
"Offset order must have either a child order definition or an "
"order to attach to"};
}
me->tick_offset_on_deploy = offset.tick_offset_on_deploy();
if (!serv.is_attaching()) {
// if we are not attaching create a child template so we can
// quickly launch our child when the time comes.
auto child_def = def;
*child_def.mutable_custom_def() = def.custom_def().offset().child();
me->child_template = &(serv.make_child_template(child_def));
}
return {};
}
void OffsetAlgo::do_launch(IAlgoServices &algo) {
me->algo = &algo;
bts_assert(me->child_template);
algo.subscribe_book(SecurityKey(algo.security().id()));
}
void OffsetAlgo::do_launch_attached(IAlgoServices &algo, OrderKey k,
ChildState st) {
me->algo = &algo;
me->child_key = k;
me->child_price = st.price.value();
algo.subscribe_book(SecurityKey(algo.security().id()));
}
void OffsetAlgo::do_update(api::orderman::Update const &update) {
LOG(OrderMan, Diag, "OffsetAlgo order {} receiving update: {}",
algo().order_key().value(), utils::pb::to_string(update));
if (update.has_custom()) {
if (!update.custom().has_offset()) {
algo().terminate_error("Invalid update");
return;
}
me->tick_offset_on_deploy =
update.custom().offset().tick_offset_on_deploy();
}
switch (me->status) {
case Status::WAITING_FOR_MD:
if (update.has_custom()) {
if (algo().is_attaching()) {
// don't change the child's price
// (need md to determine initial deploy price)
bts_assert(!os::is_nan(me->child_price));
algo().update_child(me->child_key, mk_child_update(update, me->child_price));
} else {
auto &def = algo().definition();
auto child_def = def;
*child_def.mutable_custom_def() = update.custom().offset().child();
me->child_template = &(algo().make_child_template(child_def));
}
}
break;
case Status::DEPLOYED:
algo().update_child(me->child_key, mk_child_update(update));
break;
}
}
void OffsetAlgo::on_book(SecurityKey, Book const &b) {
me->last_book = b;
// log top of book
LOG(OrderMan, Diag, "OffsetAlgo order {} on book. BID: {}@{} ASK: {}@{}",
algo().order_key().value(), b.side(api::SIDE_BUY)[0].size(),
b.side(api::SIDE_BUY)[0].price(), b.side(api::SIDE_SELL)[0].size(),
b.side(api::SIDE_SELL)[0].price());
// attempt deploy
if (me->status == Status::WAITING_FOR_MD)
try_deploy();
}
Update OffsetAlgo::mk_child_update(const Update &source, const double &price_override) {
Update target;
if (source.has_custom()) {
bts_assert(source.custom().has_offset());
*target.mutable_custom() = source.custom().offset().child();
}
if (!os::is_nan(price_override))
target.set_price(price_override);
else
target.set_price(source.price());
target.set_quantity(source.quantity());
return target;
}
void OffsetAlgo::try_deploy() {
bts_assert(me->status == Status::WAITING_FOR_MD);
auto side = algo().definition().side();
auto &top_psz = me->last_book.side(side)[0];
if (!top_psz.size()) {
// log state (still waiting for md)
LOG(OrderMan, Diag,
"OffsetAlgo order {} still waiting for md. Order: {} {}@ {} ticks "
"away.",
algo().order_key().value(), algo().definition().side(),
algo().quantity().value(), me->tick_offset_on_deploy);
} else {
const double &offset = securities::tick_size(algo().security()).value() *
me->tick_offset_on_deploy;
me->child_price =
securities::worsen_price(side, Price(top_psz.price()), Price(offset)).value();
if (algo().is_attaching()) {
// log imminent update
LOG(OrderMan, Diag,
"OffsetAlgo order {} attaching child {}. Order: {} {}@ {} ({} ticks "
"away).",
algo().order_key().value(),
me->child_key.value(), side,
algo().quantity().value(),
me->child_price, me->tick_offset_on_deploy);
// update
Update child_update;
child_update.set_quantity(algo().quantity().value());
child_update.set_price(me->child_price);
algo().update_child(me->child_key, child_update);
} else {
// log imminent deploy
LOG(OrderMan, Diag,
"OffsetAlgo order {} deploying child. Order: {} {}@ {} ({} ticks "
"away).",
algo().order_key().value(), side,
algo().quantity().value(),
me->child_price, me->tick_offset_on_deploy);
// deploy
me->child_key =
algo().launch_child(*me->child_template, Price(me->child_price), algo().quantity());
}
algo().set_ladder_visibility(false);
me->status = Status::DEPLOYED;
}
}
void OffsetAlgo::on_child_change(OrderKey, ChildState const &st) {
me->child_price = st.price.value();
algo().update_fill_state(st.total_fills, st.last_price);
switch (st.status) {
case ChildStatus::WORKING:
return;
case ChildStatus::COMPLETED:
algo().complete();
return;
case ChildStatus::CANCELED:
algo().cancel("Unexpected child cancel");
return;
}
}
} // namespace orderman
} // namespace bts
@@ -0,0 +1,38 @@
#pragma once
#include "lib/orderman/orders/algo/IAlgoOrder.h"
using Update = bts::api::orderman::Update;
namespace bts {
namespace orderman {
// Offset sends a child-order/algo N ticks away from the top of the book.
// If necessary, it will wait for the market to arrive before shooting the
// child.
class OffsetAlgo : public IAlgoOrder {
public:
OffsetAlgo();
~OffsetAlgo();
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
void do_launch(IAlgoServices &) override;
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
void do_update(Update const &) override;
void on_book(SecurityKey, Book const &) override;
void on_child_change(OrderKey, ChildState const &) override;
private:
enum class Status;
struct PImpl;
std::unique_ptr<PImpl> me;
static Update mk_child_update(const Update& source_update, const double& price_override = os::nan());
IAlgoServices &algo() const;
void try_deploy();
};
} // namespace orderman
} // namespace bts
@@ -16,6 +16,7 @@ using namespace api::orderman;
struct TopAlgo::PImpl {
IAlgoServices *algo;
spark::QuantityMeasure top_threshold_measure;
uint32_t ticks_off = 0;
OrderKey child_key = null_order_key();
ChildTemplate *child_template = nullptr;
Book last_book;
@@ -41,7 +42,7 @@ OrderOpResult TopAlgo::do_before_launch(IBeforeLaunchAlgoServices &serv) {
}
me->top_threshold_measure = top.top_threshold();
me->ticks_off = top.ticks_off();
if (!serv.is_attaching()) {
// if we are not attaching create a child template so we can
// quickly launch our child when the time comes.
@@ -87,13 +88,14 @@ void TopAlgo::do_update(api::orderman::Update const &update) {
auto &top_update = update.custom().top();
*child_update.mutable_custom() = top_update.child();
me->top_threshold_measure = top_update.top_threshold();
me->ticks_off = top_update.ticks_off();
}
algo().update_child(me->child_key, child_update);
}
void TopAlgo::on_book(SecurityKey, Book const &b) {
me->last_book = b;
ensure_min_quantity();
ensure_order_meets_constraints();
}
void TopAlgo::on_child_change(OrderKey, ChildState const &st) {
@@ -111,14 +113,14 @@ void TopAlgo::on_child_change(OrderKey, ChildState const &st) {
}
}
bool TopAlgo::ensure_min_quantity() {
bool TopAlgo::ensure_order_meets_constraints() {
LOG(OrderMan, Diag,
"Top order {} received book update. Current top of book: {}@{}",
algo().order_key().value(), top_of_book_quantity().value(),
top_of_book_price().value());
if (better_than_top()) {
LOG(OrderMan, Diag,
LOG(OrderMan, Info,
"Top order {} became better than the top of the book, canceling",
algo().order_key().value());
algo().cancel("Order would become BBO");
@@ -135,7 +137,7 @@ bool TopAlgo::ensure_min_quantity() {
algo().order_key().value(), total_min_quantity,
top_of_book_quantity().value());
if (top_of_book_quantity() < Quantity(total_min_quantity)) {
LOG(OrderMan, Diag,
LOG(OrderMan, Info,
"Top order {} is canceling because min quantiy is not met",
algo().order_key().value());
algo().cancel("Top of book is below threshold");
@@ -147,6 +149,26 @@ bool TopAlgo::ensure_min_quantity() {
algo().order_key().value(), algo().price().value(),
top_of_book_price().value());
if (me->ticks_off > 0) {
auto tick_diff = securities::price_diff_to_ticks(
algo().price().value(), top_of_book_price().value(),
securities::tick_size(algo().security()).value());
if (std::abs(tick_diff) <= (int)me->ticks_off) {
LOG(OrderMan, Info,
"Top order {} price {} is less than {} ticks away from top ({}). "
"Canceling",
algo().order_key().value(), algo().price().value(),
me->ticks_off + 1, top_of_book_price().value());
algo().cancel("Order is too close to top of the book");
return false;
} else
LOG(OrderMan, Diag,
"Top order {} price {} is {} ticks away from top. Top: {} Ticks "
"off: {}",
algo().order_key().value(), algo().price().value(),
std::abs(tick_diff), top_of_book_price().value(), me->ticks_off);
}
return true;
}
@@ -27,7 +27,7 @@ class TopAlgo : public IAlgoOrder {
bool at_top_of_book() const;
bool better_than_top() const;
Quantity min_quantity_at_top() const;
bool ensure_min_quantity();
bool ensure_order_meets_constraints();
bool has_running_child() const;
Price top_of_book_price() const;
Quantity top_of_book_quantity() const;
@@ -1,102 +0,0 @@
#include "OffsetOrder.h"
#include "lib/orderman/actors/order_manager/OrderBuilder.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/utils/PBUtils.h"
#include <google/protobuf/timestamp.pb.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
struct OffsetOrder::PImpl
{
PImpl() {}
uint32_t tick_offset_on_deploy;
};
OffsetOrder::OffsetOrder() : me(new PImpl()) {}
OffsetOrder::~OffsetOrder() {}
void OffsetOrder::setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState& initial_state, api::orderman::OrderDefinition* out_child)
{
initial_state.mutable_offset();
auto const &offset_def = this_def.custom_def().offset();
*out_child->mutable_custom_def() = offset_def.child();
me->tick_offset_on_deploy = offset_def.tick_offset_on_deploy();
}
void OffsetOrder::on_book_update(const bts::orderman::BookUpdateEvent& book)
{
switch (this_state().status())
{
case ORDER_STATUS_WORKING:
switch (child_state().status())
{
case ORDER_STATUS_INACTIVE:
{
if(definition().side() == api::SIDE_BUY)
{
const auto& same = book.book.side(api::Side::SIDE_BUY)[0];
double price = same.price() - tick_size() * me->tick_offset_on_deploy;
activate_child(Price(price), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
else
{
const auto& same = book.book.side(api::Side::SIDE_SELL)[0];
double price = same.price() + tick_size() * me->tick_offset_on_deploy;
activate_child(Price(price), Quantity(this_state().quantity()));
transition(ORDER_STATUS_WORKING);
}
report_state();
}
break;
case ORDER_STATUS_WORKING:
{
detach();
}
break;
case ORDER_STATUS_PENDING_WORKING:
break;
// child cannot be in any of these states if we are in the working state
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
break;
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
}
}
@@ -1,29 +0,0 @@
#pragma once
#include "lib/orderman/orders/book_algos/BookAlgo.h"
namespace bts {
namespace orderman {
class OrderStateUpdateEvent;
class BookUpdateEvent;
//OffsetOrder deploys N ticks away from the initial same-side inside market it's mainly for composition / fat finger
class OffsetOrder : public BookAlgo
{
public:
OffsetOrder();
virtual ~OffsetOrder();
protected:
void setup_child(const api::orderman::OrderDefinition& this_def, const PreActivationOrderServices&,
api::orderman::OrderState&, api::orderman::OrderDefinition*) override;
void on_book_update(const bts::orderman::BookUpdateEvent&) override;
private:
struct PImpl;
std::unique_ptr<PImpl> me;
};
}}
+39 -21
View File
@@ -1,11 +1,13 @@
#include "OrderServiceServer.h"
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/AdminService.client.pb.h"
#include "lib/api/services/MarginService.client.pb.h"
#include "lib/api/services/PositionService.client.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/config/protos/orderman_cfg.pb.h"
#include "lib/md/MDConsumer.h"
#include "lib/md/MDClient.h"
#include "lib/md/MDConsumer.h"
#include "lib/mercury/actors/Timer.h"
#include "lib/mercury/executor/Executor.h"
#include "lib/metrics/metrics.h"
@@ -45,14 +47,14 @@ struct ActorThread {
stats.publish(loop_metric);
}
void start(trace::PerfLog& log) {
void start(trace::PerfLog &log) {
if (cfg.poll())
thread = threading::make_thread(cfg.thread_name(), [this,&log]() {
thread = threading::make_thread(cfg.thread_name(), [this, &log]() {
for (;;)
executor.do_work(log);
});
else {
thread = threading::make_thread(cfg.thread_name(), [this,&log]() {
thread = threading::make_thread(cfg.thread_name(), [this, &log]() {
const auto inner_iter = 1000;
while (!done) {
auto start = Timer::now();
@@ -90,14 +92,16 @@ ActorThread *find_thread_for_actor(
}
struct OrderServiceServer::PImpl {
PImpl(bts::api::ServiceContext &ctx,
md::MDClient& mdc,
PImpl(bts::api::ServiceContext &ctx, md::MDClient &mdc,
DynamicOrderRegistry const &d, metrics::MetricsContainer &stats,
trace::PerfLog &log)
: service_bridge(), service_ready(false), known_orders(), known_fills(),
publish_orders(), publish_fills(),
admin_service(ctx.locate<api::admin::AdminService>()), md_consumer(mdc.current_consumer()),
stats(stats), dyn_order_registry(d), log(log), in_flight_order_ops(),
admin_service(ctx.locate<api::admin::AdminService>()),
position_service(ctx.locate<api::position::PositionService>()),
margin_service(ctx.locate<api::position::MarginService>()),
md_consumer(mdc.current_consumer()), stats(stats),
dyn_order_registry(d), log(log), in_flight_order_ops(),
current_req_op_id(0) {}
ServiceBridge *service_bridge;
@@ -111,7 +115,9 @@ struct OrderServiceServer::PImpl {
OrderServiceServer::OrdersPublisher publish_orders;
OrderServiceServer::FillsPublisher publish_fills;
api::admin::AdminService admin_service;
bts::md::MDConsumer& md_consumer;
api::position::PositionService position_service;
api::position::MarginService margin_service;
bts::md::MDConsumer &md_consumer;
metrics::MetricsContainer &stats;
DynamicOrderRegistry const &dyn_order_registry;
trace::PerfLog &log;
@@ -120,12 +126,13 @@ struct OrderServiceServer::PImpl {
uint32_t current_req_op_id;
};
OrderServiceServer::OrderServiceServer(
bts::api::ServiceContext &ctx,
md::MDClient& mdc,
DynamicOrderRegistry const &d, metrics::MetricsContainer &stats,
trace::PerfLog &log)
: api::orderman::OrderServiceBase(ctx), me(new PImpl(ctx, mdc, d, stats,log)) {
OrderServiceServer::OrderServiceServer(bts::api::ServiceContext &ctx,
md::MDClient &mdc,
DynamicOrderRegistry const &d,
metrics::MetricsContainer &stats,
trace::PerfLog &log)
: api::orderman::OrderServiceBase(ctx),
me(new PImpl(ctx, mdc, d, stats, log)) {
me->publish_orders = make_orders_publisher();
me->publish_fills = make_fills_publisher();
@@ -152,7 +159,7 @@ OrderServiceServer::OrderServiceServer(
bts_assert_msg(thread,
"Could not find a configured thread for OrderManager. See "
"the OrdermanCfg key");
thread->executor.spawn<OrderManager>(d,me->log, mdc.oracle());
thread->executor.spawn<OrderManager>(d, me->log, mdc.oracle());
// Timer
thread = find_thread_for_actor("Timer", me->actor_threads);
@@ -212,12 +219,10 @@ OrderServiceServer::OrderServiceServer(
},
[this, &ctx](auto const &order_op_result) {
ctx.post([this, order_op_result] {
this->on_order_op_result(order_op_result);
this->on_order_op_result(order_op_result);
});
},
me->dyn_order_registry,
thread->executor.make_proxy(),mdc.oracle());
me->dyn_order_registry, thread->executor.make_proxy(), mdc.oracle());
thread->executor.activate(me->service_bridge);
@@ -235,6 +240,19 @@ OrderServiceServer::OrderServiceServer(
bts_assert(update.all().size() == 1);
me->service_bridge->update_admin_data(update.all()[0]);
});
// subscribe to position updates and foward them to the service bridge
me->position_service.subscribe_pnl_account_data() |
rxcpp::operators::subscribe<api::position::Pnl_account_dataUpdate>(
[this](auto const &update) {
me->service_bridge->update_account_pnl(update);
});
me->margin_service.subscribe_excess_liquidity_report() |
rxcpp::operators::subscribe<api::position::Excess_liquidity_reportUpdate>(
[this](auto const &update) {
me->service_bridge->update_excess_liquidity_report(update);
});
}
OrderServiceServer::~OrderServiceServer() {
@@ -358,7 +376,7 @@ void OrderServiceServer::get_registered_dynamic_order_types(
void OrderServiceServer::on_order_op_result(
OrderOpResultEvent const &op_result) {
auto it = me->in_flight_order_ops.find(op_result.req_id);
bts_assert(it!=me->in_flight_order_ops.end());
bts_assert(it != me->in_flight_order_ops.end());
auto &responder = it->second;
api::orderman::Response r;
r.set_order_key(op_result.ok.value());
+6 -1
View File
@@ -86,7 +86,7 @@ bool AuthSessionManager::add_or_replace_session(const api::user::TokenPayload& t
return true;
if(datetime::ll_diff_in_us(datetime::ll_now(), _expiration) < 0)
{
LOG(Gen,Error, "Your BTS Keycode is expired!");
LOG(User,Error, "Your BTS Keycode is expired!");
if(msg)
*msg = "Your BTS Keycode is EXPIRED! Please contact sales@bluetradesys.com";
return false;
@@ -105,13 +105,18 @@ bool AuthSessionManager::add_or_replace_session(const api::user::TokenPayload& t
if(it != _map.end())
{
_map[tok.id()] = Session{tok};
LOG(User, Warn, "KEYCODE: Replacing session {} ", tok.id());
return true;
}
else if(_map.size() < _nusers)
{
_map[tok.id()] = Session{tok};
LOG(User, Warn, "KEYCODE: Creating session {} ", tok.id());
return true;
}
if(msg)
*msg = "KEYCODES: Not Enough Logins available for session";
LOG(User, Error, "KEYCODES: Not Enough Logins available for session {}", tok.id());
return false;
}
+32 -1
View File
@@ -51,6 +51,35 @@ AuthSessionManager& session_manager()
return mgr;
}
bool requires_auth_session(const admin::BTSUser& user)
{
//if the user can trade in any account or see market data, it needs a session
if(user.has_user_data() &&
user.user_data().has_account_permissions() &&
user.user_data().account_permissions().account_permissions_size())
{
for(const auto& i: user.user_data().account_permissions().account_permissions())
{
if(i.second.has_trading_permission() && i.second.trading_permission().has_permissions())
return true;
}
}
if(user.has_user_data() &&
user.user_data().has_exchange_user_data())
{
if(user.user_data().exchange_user_data().has_cme_data())
{
for(const auto& i: user.user_data().exchange_user_data().cme_data().md_permissions())
{
if(i.enabled())
return true;
}
}
}
return false;
}
AuthToken generate_token(const admin::BTSUser& user, std::string* msg)
{
api::user::TokenPayload payload;
@@ -58,7 +87,9 @@ AuthToken generate_token(const admin::BTSUser& user, std::string* msg)
payload.set_id(user.id());
if(user.name() == "bts")
payload.set_wheel(true);
session_manager().add_or_replace_session(payload, msg);
if(requires_auth_session(user))
session_manager().add_or_replace_session(payload, msg);
auto token = encrypt(payload.SerializeAsString());
bts_assert_d(token.size() < 255-8);
AuthToken result;
+14
View File
@@ -214,6 +214,20 @@ void Dispatcher::post(Action&& action, int64_t delay_ms)
}
}
void Dispatcher::post_copy(Action action, int64_t delay_ms)
{
if (delay_ms > 0)
{
shared_ptr<deadline_timer> timer = make_shared<deadline_timer>(_me->service);
timer->expires_from_now(boost::posix_time::milliseconds(delay_ms));
timer->async_wait([timer, action] (const boost::system::error_code& err) { if (!err) action(); });
}
else
{
_me->service.post(action);
}
}
io_service* Dispatcher::io_service() { return &_me->service; }
//
+1
View File
@@ -88,6 +88,7 @@ public:
void invoke(Action action);
// Queue an action on the dispatcher thread and return immediately.
void post(Action&& action, int64_t delay_ms=0);
void post_copy(Action action, int64_t delay_ms=0);
boost::asio::io_service* io_service();
+25 -10
View File
@@ -5,19 +5,34 @@ namespace bts {
namespace test {
TEST_CASE("AuthToken", "[security]") {
api::admin::BTSUser user;
user.set_id(42);
user.set_name("user1");
api::admin::BTSUser admin_user;
admin_user.set_id(42);
admin_user.set_name("user1");
api::admin::BTSUser trader_user;
trader_user.set_id(43);
trader_user.set_name("trader1");
trader_user.mutable_user_data()->mutable_exchange_user_data()->mutable_cme_data()->add_md_permissions()->set_enabled(true);
SECTION("round trip") {
auto token = api::auth::generate_token(user);
SECTION("round trip admin") {
auto token = api::auth::generate_token(admin_user);
// We use 1 byte for api header size
REQUIRE(token.ByteSize() <= 240);
api::user::TokenPayload payload;
REQUIRE(!api::auth::decode_token(token, &payload));
REQUIRE(payload.id() == admin_user.id());
REQUIRE(payload.wheel() == false);
}
SECTION("round trip trader") {
auto token = api::auth::generate_token(trader_user);
// We use 1 byte for api header size
REQUIRE(token.ByteSize() <= 240);
api::user::TokenPayload payload;
REQUIRE(api::auth::decode_token(token, &payload));
REQUIRE(payload.id() == user.id());
REQUIRE(payload.id() == trader_user.id());
REQUIRE(payload.wheel() == false);
}
}
SECTION("empty token") {
api::user::TokenPayload payload;
@@ -28,11 +43,11 @@ TEST_CASE("AuthToken", "[security]") {
}
SECTION("bts is the big wheel") {
user.set_name("bts");
auto token = api::auth::generate_token(user);
admin_user.set_name("bts");
auto token = api::auth::generate_token(admin_user);
api::user::TokenPayload payload;
REQUIRE(api::auth::decode_token(token, &payload));
REQUIRE(payload.id() == user.id());
REQUIRE(payload.id() == admin_user.id());
REQUIRE(payload.wheel());
}
}
+1 -1
View File
@@ -1 +1 @@
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/WebServer.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IMarginObserver.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/PositionServiceCore.cpp;position/PositionServiceServer.cpp;position/tests/PositionTests.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;cmds/Web.cpp;dspark/mirage/quick_queue/quick_queue.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/WebServer.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/ice_onixs/ICEListener.cpp;md/platforms/ice_onixs/ICEMain.cpp;md/platforms/ice_onixs/tests/ice_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;position/IPositionObserver.cpp;position/MarginServiceCore.cpp;position/MarginServiceServer.cpp;position/PositionAggregator.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/tests/PositionTests.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
+4 -4
View File
@@ -29,6 +29,7 @@
#include "server/sparkd/md/MarketDataAuxiliaryServiceServer.h"
#include "server/sparkd/md/run_md.h"
#include "server/sparkd/position/PositionServiceServer.h"
#include "server/sparkd/position/MarginServiceServer.h"
#include "server/sparkd/support/ErrorReportService.h"
#include "server/sparkd/support/ShellLatencyServer.h"
#include "server/sparkd/user/AdminServiceServer.h"
@@ -84,11 +85,10 @@ int Run::execute(std::vector<std::string> const &) {
md::auxiliary::MarketDataAuxiliaryServiceServer md_aux_server(sc);
//orderman::AlgoServiceServer algo_server(sc);
bts::api::orderman::algo::AlgoDefinitionServiceServer algo_definition_server(sc);
position::PositionServiceServer position_service(sc);
position::PositionServiceServer position_service(sc,mdclient);
position::MarginServiceServer margin_service(sc, mdclient);
metrics::ShellLatencyServer shell_latency_service(
sc, stats, utils::Dispatcher::background());
+51 -6
View File
@@ -1,15 +1,61 @@
#include "Span.h"
#include "server/sparkd/position/PositionAggregator.h"
#include "server/sparkd/position/MarginServiceCore.h"
#include "server/sparkd/position/Position.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
namespace bts
{
namespace cmd
{
#include "lib/config/config.h"
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/AuthService.client.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/utils/helper_macros.h"
#include "lib/utils/Dispatcher.h"
#include <google/protobuf/text_format.h>
#include <future>
#include <memory>
#include <stdlib.h>
#include <condition_variable>
#include <mutex>
#include <time.h>
using namespace bts::position;
using namespace bts::api::position;
namespace bts {
namespace cmd {
int Span::execute(const std::vector<std::string>&)
{
srand(time(NULL));
auto& disp = utils::Dispatcher::current();
MarginServiceCore core(disp);
auto acct = orderman::to<orderman::AccountName>("ACC A");
Position p;
p.security_desc = "ESU9";
p.account = acct;
p.position = Quantity(rand() % 100);
p.side = api::SIDE_BUY;
std::vector<Position> pos;
pos.push_back(p);
p.position = Quantity(rand() % 200);
p.side = api::SIDE_SELL;
pos.push_back(p);
//core.set_verbose_debugging(true);
core.compute_margin(acct, pos, [](const api::position::MarginResult& m)
{
std::string str;
google::protobuf::TextFormat::PrintToString(m, &str);
std::cout << std::endl << str << std::endl << std::endl;
});
std::cout << "Waiting for margin results, press any key to exit" << std::endl;
disp.run();
return 0;
}
@@ -18,7 +64,6 @@ std::string Span::help()
return "\t test CME CORE Span integration \n";
}
}
}
}}
@@ -1,9 +0,0 @@
#include "IMarginObserver.h"
namespace bts
{
namespace position
{
IMarginObserver::~IMarginObserver() {}
}
}
@@ -1,18 +0,0 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
namespace bts
{
namespace position
{
struct MarginResult;
class
IMarginObserver
{
public:
virtual ~IMarginObserver();
virtual void on_margin_change(const orderman::AccountName&, const MarginResult&) = 0;
};
}
}
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/domain_model/Security.pb.h"
namespace bts
{
namespace position
{
struct MarginResult
{
bool valid;
std::string settle_indicator;
std::string settle_qualifier;
std::string portfolio_id;
std::string as_of_time;
std::string calculation_id;
double opt_value;
double nonopt_value;
double maint_margin;
double initial_margin;
};
}
}
+110 -109
View File
@@ -1,18 +1,16 @@
#include "MarginServiceCore.h"
#include "PositionServiceCore.h"
#include "Position.h"
#include "MarginResult.h"
#include "IMarginObserver.h"
#include "lib/config/config.h"
#include "lib/api/services/AuthService.pb.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/Uuid.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/bts_chrono.h"
#include "lib/logging/logging.h"
#include "lib/utils/helper_macros.h"
#include "lib/utils/datetime.h"
#include "lib/config/protos/span.pb.h"
#include "curl/curl.h"
@@ -23,13 +21,7 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include <utility>
#define CONSOLE_LOG(...) std::cout , __VA_ARGS__ , std::endl
template <typename T> std::ostream& operator,(std::ostream& out, const T& t) { out << t; return out; }
std::ostream& operator,(std::ostream& out, std::ostream&(*f)(std::ostream&)) { out << f; return out; }
#define MARGIN_LOG(subsystem,level,...) \
bts::log::_thread_logger.logger->enqueue(log::log_ts(),bts::log::subsystem,level,__FILE__,__LINE__,__VA_ARGS__); \
CONSOLE_LOG(__VA_ARGS__)
using bts::api::position::MarginResult;
namespace bts
{
@@ -37,32 +29,34 @@ namespace position
{
using MarginMap = boost::container::flat_map<orderman::AccountName, MarginResult>;
struct MarginServiceCore::PImpl
{
securities::ISecurityDB& db;
PositionServiceCore position_service;
IMarginObserver* observer;
MarginMap last_results, last_valid_results;
utils::Dispatcher& dispatcher;
MarginMap last_results, last_valid_results;
std::string url_base, core_user, core_password, post_margin_path, get_margin_path;
size_t max_iterations;
bool verbose;
int throttle;
bts::datetime::PTime last_update;
PImpl(securities::ISecurityDB& d, IMarginObserver* obs) :
db(d), position_service(d), observer(obs), last_results(), last_valid_results(),
url_base("https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/"), core_user("api_bts1"), core_password("BT5u53r!"),
PImpl(utils::Dispatcher& disp) :
dispatcher(disp),
last_results(), last_valid_results(), //api_bts_span BT5Margin!
url_base("https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/"), core_user("api_bts1"), core_password("Summer123"),
post_margin_path("margins/?complete=true"),
get_margin_path("margins/"),
max_iterations(20),
verbose(false),
throttle(30000),
last_update()
throttle(30000)
{
if(config::get<bts::config::SpanMargin>().login() != "")
core_user = config::get<bts::config::SpanMargin>().login();
if(config::get<bts::config::SpanMargin>().password() != "")
core_password = config::get<bts::config::SpanMargin>().password();
if(config::get<bts::config::SpanMargin>().url() != "")
url_base = config::get<bts::config::SpanMargin>().url();
auto res = curl_global_init(CURL_GLOBAL_DEFAULT);
if(res != CURLE_OK)
{
MARGIN_LOG(Gen,Error,"curl_global_init() failed: ", std::string(curl_easy_strerror(res)));
LOG(Gen,Error,"curl_global_init() failed: {}", std::string(curl_easy_strerror(res)));
}
}
~PImpl()
@@ -151,7 +145,7 @@ struct MarginServiceCore::PImpl
struct curl_slist* setup_url_and_headers(CURL* curl, const std::string& xpath)
{
std::string full_url = url_base + xpath;
MARGIN_LOG(Gen,Info,"CORE Margin URL: ",full_url);
LOG(Gen,Info,"CORE Margin URL: {}",full_url);
curl_easy_setopt(curl, CURLOPT_URL, full_url.c_str());
//curl_easy_setopt(curl, CURLOPT_URL, "https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/margins/");
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, debug_callback);
@@ -182,19 +176,19 @@ struct MarginServiceCore::PImpl
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&reply);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
MARGIN_LOG(Gen,Info,"CORE Margin Payload: ", payload);
LOG(Gen,Info,"CORE Margin Payload: {}", payload);
auto res = curl_easy_perform(curl);
if(res == CURLE_OK)
{
std::string resp(reply.data.begin(), reply.data.end());
MARGIN_LOG(Gen,Info,"CORE Margin Post Response: ", resp);
LOG(Gen,Info,"CORE Margin Post Response: {}", resp);
succ = true;
}
else
{
std::string error(curl_easy_strerror(res));
MARGIN_LOG(Gen,Error,"POST curl_easy_perform() failed: ", error);
std::cout << fmt::format("CORE Margin CURL Error: ", error);
LOG(Gen,Error,"POST curl_easy_perform() failed: {}", error);
//std::cout << fmt::format("CORE Margin CURL Error: ", error);
}
curl_easy_cleanup(curl);
if(header)
@@ -202,7 +196,7 @@ struct MarginServiceCore::PImpl
}
else
{
MARGIN_LOG(Gen,Error,"curl_easy_init() failed\n");
LOG(Gen,Error,"curl_easy_init() failed\n");
}
return succ;
}
@@ -221,12 +215,12 @@ struct MarginServiceCore::PImpl
if(res == CURLE_OK)
{
std::string resp(reply.data.begin(), reply.data.end());
MARGIN_LOG(Gen,Info,"CORE Margin GET Response: ", resp);
LOG(Gen,Info,"CORE Margin GET Response: {}", resp);
succ = true;
}
else
{
MARGIN_LOG(Gen,Error,"GET curl_easy_perform() failed: ", std::string(curl_easy_strerror(res)));
LOG(Gen,Error,"GET curl_easy_perform() failed: {}", std::string(curl_easy_strerror(res)));
}
curl_easy_cleanup(curl);
if(header)
@@ -234,7 +228,7 @@ struct MarginServiceCore::PImpl
}
else
{
MARGIN_LOG(Gen,Error,"curl_easy_init() failed\n");
LOG(Gen,Error,"curl_easy_init() failed\n");
}
return succ;
}
@@ -284,7 +278,7 @@ struct MarginServiceCore::PImpl
void parse_margin_calculation(const CurlMsg& msg, MarginResult& result)
{
result.valid = false;
result.set_status(MarginResult::Error);
std::string str(msg.data.begin(), msg.data.end());
pugi::xml_document doc;
doc.load_string(str.c_str());
@@ -293,19 +287,19 @@ struct MarginServiceCore::PImpl
child = child.child("margin");
if(child)
{
result.settle_indicator = child.attribute("settleInd").as_string();
result.settle_qualifier = child.attribute("settleQual").as_string();
result.portfolio_id = child.attribute("portfolioId").as_string();
result.as_of_time = child.attribute("asOfTime").as_string();
result.calculation_id = child.attribute("id").as_string();
result.set_settle_indicator(child.attribute("settleInd").as_string());
result.set_settle_qualifier(child.attribute("settleQual").as_string());
result.set_portfolio_id(child.attribute("portfolioId").as_string());
result.set_as_of_time(child.attribute("asOfTime").as_string());
result.set_calculation_id(child.attribute("id").as_string());
auto val = child.child("amounts");
if(val && result.portfolio_id != "")
if(val && result.portfolio_id() != "")
{
result.opt_value = child.attribute("optVal").as_double();
result.nonopt_value = child.attribute("nonOptVal").as_double();
result.maint_margin = child.attribute("maint").as_double();
result.initial_margin = child.attribute("init").as_double();
result.valid = true;
result.set_opt_value(val.attribute("optVal").as_double());
result.set_nonopt_value(val.attribute("nonOptVal").as_double());
result.set_maint_margin(val.attribute("maint").as_double());
result.set_initial_margin(val.attribute("init").as_double());
result.set_status(MarginResult::NewValid);
}
}
}
@@ -320,10 +314,10 @@ struct MarginServiceCore::PImpl
}
};
bool compute_margin(const std::string& account_name, const std::string& portfolid_id,
const std::vector<Position>& pos, MarginResult& margin)
{
margin.valid = false;
bool compute_margin(const orderman::AccountName& acct, const std::string& portfolid_id,
const std::vector<Position>& pos, std::function<void(const MarginResult&)> cb)
{
auto account_name = orderman::to_trim_str(acct);
pugi::xml_document doc;
pugi::xml_node trades_node;
create_margin_request(portfolid_id, doc, trades_node);
@@ -331,12 +325,9 @@ struct MarginServiceCore::PImpl
size_t count = 0;
for(const auto& i : pos)
{
if(i.security && i.security->has_glbx())
{
int p = i.position.value() * (i.side == api::SIDE_SELL ? -1 : 1);
request += fmt::format("BTS, {}, {}, {} {}", account_name, i.security->glbx().security_desc(), p, "\n");
count++;
}
int p = i.position.value() * (i.side == api::SIDE_SELL ? -1 : 1);
request += fmt::format("BTS, {}, {}, {}, {}", account_name, i.security_desc, p, "\n");
count++;
}
if(count > 0)
{
@@ -345,78 +336,88 @@ struct MarginServiceCore::PImpl
xml_string_writer writer(xml);
doc.save(writer);
size_t iters = 0;
while(iters++ < max_iterations)
{
CurlMsg reply;
sleep_for(5s);
post(post_margin_path, xml, reply);
std::string portid_resp, calcid_resp;
if(parse_margin_response(reply, portid_resp, calcid_resp))
{
CurlMsg reply2;
sleep_for(2s);
if(get(get_margin_path + calcid_resp, reply2))
CurlMsg reply;
post(post_margin_path, xml, reply);
std::string portid_resp, calcid_resp;
if(parse_margin_response(reply, portid_resp, calcid_resp))
{
std::function<void()> do_web;
do_web =
[=]()
{
parse_margin_calculation(reply2, margin);
return true;
}
}
MarginResult margin;
margin.set_status(MarginResult::Error);
margin.set_account(account_name);
CurlMsg reply2; bool done = false;
try
{
if(get(get_margin_path + calcid_resp, reply2))
{
parse_margin_calculation(reply2, margin);
cb(margin);
done = true;
}
}
catch(const std::exception& ex)
{
LOG(Gen, Error, "Exception in parse_margin_calculation {}", std::string(ex.what()));
}
catch(...)
{
LOG(Gen, Error, "Unknown Exception in parse_margin_calculation");
}
if(!done)
dispatcher.post_copy(do_web, 5000);
};
dispatcher.post_copy(do_web, 5000);
return true;
}
}
return false;
}
};
MarginServiceCore::MarginServiceCore(securities::ISecurityDB& db, IMarginObserver* obs)
: me(new PImpl(db, obs))
MarginServiceCore::MarginServiceCore(utils::Dispatcher& disp) : me(new PImpl(disp))
{
}
MarginServiceCore::~MarginServiceCore() {}
bool MarginServiceCore::pump(const orderman::AccountName& acct)
{
MarginResult result;
return compute_margin(acct, result);
}
bool MarginServiceCore::compute_margin(const orderman::AccountName& acct, MarginResult& result)
void MarginServiceCore::compute_margin(const orderman::AccountName& acct, const std::vector<Position>& pos,
std::function<void(const MarginResult&)> cb)
{
auto now = bts::datetime::now();
auto diff = bts::datetime::ptime_diff_in_ms(now, me->last_update);
if(me->last_update.is_not_a_date_time() || abs(diff) > me->throttle)
me->dispatcher.post([=] ()
{
query_margin_webapi(acct, result);
me->last_update = now;
return true;
}
else
{
get_last_valid_result(acct, result);
return false; //false, we did not get new data
}
query_margin_webapi(acct, pos, cb);
}, me->throttle);
}
bool MarginServiceCore::query_margin_webapi(const orderman::AccountName& acct, MarginResult& margin)
{
std::vector<Position> pos;
me->position_service.query_all_positions(acct, pos);
utils::Uuid guid;
bool succ = me->compute_margin(orderman::to_str(acct), guid.to_text_string(), pos, margin);
if(me->observer)
me->observer->on_margin_change(acct, margin);
me->last_results.insert_or_assign(acct, margin);
if(succ && margin.valid)
me->last_valid_results.insert_or_assign(acct, margin);
return succ;
}
bool MarginServiceCore::query_margin_webapi(const std::vector<Position>& pos, MarginResult& margin)
bool MarginServiceCore::query_margin_webapi(const orderman::AccountName& acct, const std::vector<Position>& pos,
std::function<void(const MarginResult&)> cb)
{
utils::Uuid guid;
std::string str = guid.to_text_string();
return me->compute_margin(str.substr(0, 3), str, pos, margin);
try
{
return me->compute_margin(acct, guid.to_text_string(), pos,
[=] (const MarginResult& m)
{
me->last_results.insert_or_assign(acct, m);
if(m.status() == MarginResult::NewValid)
me->last_valid_results.insert_or_assign(acct, m);
cb(m);
});
}
catch(const std::exception& ex)
{
LOG(Gen, Error, "Exception in query_margin_webapi {}", std::string(ex.what()));
return false;
}
catch(...)
{
LOG(Gen, Error, "Unknown Exception in query_margin_webapi");
return false;
}
}
void MarginServiceCore::set_verbose_debugging(bool verb)
+16 -15
View File
@@ -1,6 +1,7 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/api/services/MarginService.pb.h"
#include "server/sparkd/position/Position.h"
#include "lib/domain_model/Security.pb.h"
@@ -8,33 +9,33 @@
namespace bts
{
namespace utils { class Dispatcher; }
namespace securities { class ISecurityDB; }
namespace position
{
struct MarginResult;
class IMarginObserver;
class MarginServiceCore
{
BTS_NO_COPY(MarginServiceCore);
public:
MarginServiceCore(securities::ISecurityDB&, IMarginObserver* obs = nullptr);
MarginServiceCore(utils::Dispatcher&);
~MarginServiceCore();
//Call as much as you want; it will update IMarginObserver at most once every 30s
bool pump(const orderman::AccountName&);
//Call as much as you want; it will query the webapi at most once every 30s
bool compute_margin(const orderman::AccountName&, MarginResult&);
void compute_margin(const orderman::AccountName&, const std::vector<Position>&,
std::function<void(const api::position::MarginResult&)>);
void set_verbose_debugging(bool);
bool get_last_valid_result(const orderman::AccountName&, api::position::MarginResult&);
bool get_last_result(const orderman::AccountName&, api::position::MarginResult&);
private:
friend class PositionTests;
bool get_last_valid_result(const orderman::AccountName&, MarginResult&);
bool get_last_result(const orderman::AccountName&, MarginResult&);
//This is a blocking call to a WebAPI. Very Slow.
bool query_margin_webapi(const orderman::AccountName&, MarginResult&);
//This is a blocking call to a WebAPI. Very Slow.
bool query_margin_webapi(const std::vector<Position>&, MarginResult&);
void set_verbose_debugging(bool);
//This is a blocking call to a WebAPI. Very Slow.
bool query_margin_webapi(const orderman::AccountName&, const std::vector<Position>&,
std::function<void(const api::position::MarginResult&)>);
private:
struct PImpl;
std::unique_ptr<PImpl> me;
@@ -0,0 +1,298 @@
#include "MarginServiceServer.h"
#include "MarginServiceCore.h"
#include "PositionAggregator.h"
#include "Position.h"
#include "lib/api/service_helpers/AdminServiceUtils.h"
#include "lib/orderman/orderman_utils/orderman_utils.h"
#include "lib/persistence/SqliteStore.h"
#include "lib/config/config.h"
#include "lib/config/protos/span.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/Dispatcher.h"
#include "lib/os/os.h"
#include <set>
#include <utility>
using namespace bts::api::orderman;
using namespace bts::api::position;
using namespace bts::admin;
using namespace std;
using bts::api::position::MarginResult;
namespace bts {
namespace position {
MarginServiceServer::MarginServiceServer(bts::api::ServiceContext& context, md::MDClient& mdclient)
: bts::api::position::MarginServiceBase (context),
_admin_service(context.locate<bts::api::admin::AdminService>()),
_publisher(make_margin_data_stateful_publisher()),
_report_publisher(make_excess_liquidity_report_stateful_publisher()),
_dispatcher(utils::Dispatcher::get_named("MarginServiceServer")),
_timer(_dispatcher, 30000, [=]() { publish_all();} ),
_position(new PositionAggregator(context.locate<bts::api::position::PositionService>(), mdclient)),
_margin(new MarginServiceCore(_dispatcher)),
//state below
_lock(),
_last_update(),
_admin_data(),
_last_report(),
_first_time(true)
{
LOG(Gen, Info, "MarginServiceServer: service started");
// _admin_service.get_admin_data(api::VoidMessage(),
// [=] (const bts::api::Result<::bts::api::admin::AdminData> r)
// {
// if(r.value())
// _admin_data = *r.value();
// });
_admin_service.subscribe_admin_data() |
rxcpp::operators::subscribe<api::admin::Admin_dataUpdate>(
[this](auto const &update)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
bts_assert(update.all().size() == 1);
_admin_data = *update.all()[0];
if(_first_time)
{
auto accts = AdminServiceUtils::accounts(_admin_data, false);
for(const auto& i : accts)
{
incremental_update_report(i->name(), i->id());
}
_first_time = false;
}
});
_timer.start();
}
void MarginServiceServer::publish_all()
{
if(config::get<bts::config::SpanMargin>().enabled())
{
std::scoped_lock<std::recursive_mutex> l(_lock);
auto accts = AdminServiceUtils::accounts(_admin_data, false);
for(const auto& i : accts)
{
datetime::LLDateTime since = 0;
std::string acct = i->name();
auto it = _last_update.find(acct);
if(it != _last_update.end())
since = it->second;
std::vector<Position> pos; AccountPNL pnl;
if(_position->query_positions(acct, pos, pnl, &since) && pos.size())
{
LOG(Gen, Info, "MarginServiceServer: requesting margin for {} ", acct);
auto aname = orderman::to<orderman::AccountName>(acct);
_margin->compute_margin(aname, pos, [this, since, acct, pnl](const MarginResult& m)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
publish_margin(m);
publish_excess_liquidity_report(m, pnl);
_last_update[acct] = since;
});
}
}
}
}
MarginServiceServer::~MarginServiceServer() {}
void MarginServiceServer::get_margin(const api::position::MarginRequest& request,
const api::Response<api::position::MarginResult>& reply)
{
if(!config::get<bts::config::SpanMargin>().enabled())
{
MarginResult res;
res.set_account(request.account());
reply(res);
}
else
{
orderman::AccountName acct;
orderman::str_to(request.account(), &acct);
std::vector<Position> pos; AccountPNL pnl;
_position->query_positions(request.account(), pos, pnl);
_margin->compute_margin(acct, pos, [=](const MarginResult& m)
{
reply(m);
});
}
}
void MarginServiceServer::get_excess_liquidity_report(const api::position::MarginRequest& request,
const api::Response<api::position::ExcessLiquidityReport>& reply)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
ExcessLiquidityReport rep;
rep.set_account(request.account());
auto it = _last_report.find(request.account());
if(it != _last_report.end())
rep.CopyFrom(it->second);
reply(rep);
}
void MarginServiceServer::publish_margin(const api::position::MarginResult& result)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
_publisher.add_or_update(result);
_publisher.flush();
}
void MarginServiceServer::publish_excess_liquidity_report(const api::position::MarginResult& result, const api::position::AccountPNL& pnl)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
ExcessLiquidityReport report;
report.set_account(result.account());
report.set_account_id(pnl.account_id());
report.mutable_account_pnl()->CopyFrom(pnl);
report.mutable_margin_result()->CopyFrom(result);
auto acct = AdminServiceUtils::find_account(_admin_data, result.account(), false);
if(acct && acct->has_margin_limit())
{
report.set_leverage_factor( acct->margin_limit().leverage_factor_percentage() );
report.set_cash_balance(acct->margin_limit().cash_balance());
report.set_should_enforce_margin(acct->margin_limit().enforce_margin() &&
config::get<bts::config::SpanMargin>().enabled());
}
double netliq = report.leverage_factor() * report.cash_balance();
if(!bts::os::is_nan(result.maint_margin()))
netliq -= result.maint_margin();
if(!bts::os::is_nan(pnl.day_pnl()))
netliq += pnl.day_pnl();
report.set_excess_liquidity(netliq);
_last_report[result.account()] = report;
_report_publisher.add_or_update(report);
_report_publisher.flush();
}
//so the admin ui knows what to do
void MarginServiceServer::incremental_update_report(const std::string& account, uint32_t account_id)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
MarginResult margin;
if(!_margin->get_last_valid_result(orderman::to<orderman::AccountName>(account), margin))
{
margin.Clear();
margin.set_account(account);
}
AccountPNL pnl;
if(!_position->query_pnl(account, pnl))
{
pnl.Clear();
pnl.set_account_name(account);
pnl.set_account_id(account_id);
}
publish_excess_liquidity_report(margin, pnl);
_last_update[account] = 0;
}
void MarginServiceServer::set_cash_factor(const ::bts::api::position::CashFactorUpdate& request,
const api::Response<::bts::api::ResultMessage>& reply)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
auto acct = AdminServiceUtils::find_account(_admin_data, request.account(), false);
if(!acct)
{
auto msg = "Account " + request.account() +"not found";
api::ResultMessage resp;
resp.set_success(false);
resp.set_error_message(msg);
reply(resp);
}
else
{
acct->mutable_margin_limit()->set_leverage_factor_percentage(request.leverage_factor());
AccountUpdateAPIArgument update;
update.mutable_complete_data()->CopyFrom(*acct);
_admin_service.update_account(update,
[this, request, reply, update](const api::Result<bts::api::admin::AdminServiceOperationResult>& admin_result)
{
api::ResultMessage resp;
resp.set_success(admin_result.success());
resp.set_error_message(admin_result.error_message());
reply(resp);
incremental_update_report(request.account(), update.complete_data().id());
});
}
}
void MarginServiceServer::set_cash_balance(const ::bts::api::position::CashBalanceUpdate& request,
const api::Response<::bts::api::ResultMessage>& reply)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
auto acct = AdminServiceUtils::find_account(_admin_data, request.account(), false);
if(!acct)
{
auto msg = "Account " + request.account() +"not found";
api::ResultMessage resp;
resp.set_success(false);
resp.set_error_message(msg);
reply(resp);
}
else
{
acct->mutable_margin_limit()->set_cash_balance(request.cash_balance());
AccountUpdateAPIArgument update;
update.mutable_complete_data()->CopyFrom(*acct);
_admin_service.update_account(update,
[this, request, reply, update](const api::Result<bts::api::admin::AdminServiceOperationResult>& admin_result)
{
api::ResultMessage resp;
resp.set_success(admin_result.success());
resp.set_error_message(admin_result.error_message());
reply(resp);
incremental_update_report(request.account(), update.complete_data().id());
});
}
}
void MarginServiceServer::set_should_enforce_margin(const ::bts::api::position::EnforceUpdate& request,
const api::Response<::bts::api::ResultMessage>& reply)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
auto acct = AdminServiceUtils::find_account(_admin_data, request.account(), false);
if(!acct)
{
auto msg = "Account " + request.account() +"not found";
api::ResultMessage resp;
resp.set_success(false);
resp.set_error_message(msg);
reply(resp);
}
else
{
acct->mutable_margin_limit()->set_enforce_margin(request.should_enforce_margin());
AccountUpdateAPIArgument update;
update.mutable_complete_data()->CopyFrom(*acct);
_admin_service.update_account(update,
[this, request, reply, update](const api::Result<bts::api::admin::AdminServiceOperationResult>& admin_result)
{
api::ResultMessage resp;
resp.set_success(admin_result.success());
resp.set_error_message(admin_result.error_message());
reply(resp);
incremental_update_report(request.account(), update.complete_data().id());
});
}
}
}}
@@ -0,0 +1,72 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/md/SecurityOracle.h"
#include "lib/persistence/IStore.h"
#include "lib/api/public/ServiceContext.h"
#include "lib/api/services/MarginService.server.pb.h"
#include "lib/api/services/AdminService.pb.h"
#include "lib/api/services/AdminService.client.pb.h"
#include "lib/api/services/PositionService.pb.h"
#include "lib/api/services/PositionService.client.pb.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/datetime.h"
#include <map>
#include <memory>
#include <mutex>
namespace bts {
namespace utils { class Dispatcher; }
namespace position {
struct Position;
class PositionAggregator;
class MarginServiceCore;
class MarginServiceServer : public api::position::MarginServiceBase
{
public:
MarginServiceServer(bts::api::ServiceContext&, md::MDClient&);
~MarginServiceServer();
void get_margin(const api::position::MarginRequest& request,
const api::Response<api::position::MarginResult>& reply) override;
void get_excess_liquidity_report(const api::position::MarginRequest& request,
const api::Response<api::position::ExcessLiquidityReport>& reply) override;
void set_cash_factor(const ::bts::api::position::CashFactorUpdate& request,
const api::Response<::bts::api::ResultMessage>& reply) override;
void set_cash_balance(const ::bts::api::position::CashBalanceUpdate& request,
const api::Response<::bts::api::ResultMessage>& reply) override;
void set_should_enforce_margin(const ::bts::api::position::EnforceUpdate& request,
const api::Response<::bts::api::ResultMessage>& reply) override;
private:
void publish_margin(const api::position::MarginResult&);
void publish_excess_liquidity_report(const api::position::MarginResult&, const api::position::AccountPNL&);
void publish_all();
void incremental_update_report(const std::string& account, uint32_t account_id);
private:
api::admin::AdminService _admin_service;
MarginServiceBase::Margin_dataStatefulPublisher _publisher;
MarginServiceBase::Excess_liquidity_reportStatefulPublisher _report_publisher;
utils::Dispatcher& _dispatcher;
utils::DispatcherTimer _timer;
std::unique_ptr<PositionAggregator> _position;
std::unique_ptr<MarginServiceCore> _margin;
private:
//state
std::recursive_mutex _lock;
std::map<std::string, datetime::LLDateTime> _last_update;
bts::api::admin::AdminData _admin_data;
std::map<std::string, api::position::ExcessLiquidityReport> _last_report;
bool _first_time;
};
}}
+4 -9
View File
@@ -1,19 +1,14 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/domain_model/Security.pb.h"
namespace bts
{
namespace position
{
namespace bts {
namespace position {
struct Position
{
orderman::AccountName account;
const api::Security* security;
std::string security_desc;
Quantity position;
api::Side side;
Price avg_price;
};
}
}
}}
@@ -0,0 +1,132 @@
#include "PositionAggregator.h"
#include "Position.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include "lib/orderman/orderman_utils/orderman_utils.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/Dispatcher.h"
#include <set>
#include <utility>
using namespace bts::api::orderman;
using namespace bts::api::position;
using namespace std;
namespace bts {
namespace position {
PositionAggregator::PositionAggregator(api::position::PositionService&& pos, md::MDClient& mdclient)
: _position_service(std::move(pos)),
_position_map(),
_pnl_map(),
_accounts(),
_oracle(mdclient),
_lock()
{
_position_service.subscribe_position_data() |
rxcpp::operators::subscribe<api::position::Position_dataUpdate>(
[this](auto const& update)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
for (const auto& o : update.removed())
{
const auto& it = _position_map.find(o->record_id());
if(it != _position_map.end())
_position_map.erase(it);
}
for (const auto& ch : update.changed())
{
const auto& o = ch.current();
_position_map[o->record_id()] = *o;
_accounts[o->account()] = bts::datetime::ll_now();
}
for (const auto& o : update.added())
{
_position_map[o->record_id()] = *o;
_accounts[o->account()] = bts::datetime::ll_now();
}
});
_position_service.subscribe_pnl_account_data() |
rxcpp::operators::subscribe<api::position::Pnl_account_dataUpdate>(
[this](auto const& update)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
for (const auto& o : update.removed())
{
const auto& it = _pnl_map.find(o->account_name());
if(it != _pnl_map.end())
_pnl_map.erase(it);
}
for (const auto& ch : update.changed())
{
const auto& o = ch.current();
_pnl_map[o->account_name()] = *o;
}
for (const auto& o : update.added())
{
_pnl_map[o->account_name()] = *o;
}
});
}
PositionAggregator::~PositionAggregator() {}
void eraseSubStr(std::string & mainStr, const std::string & toErase)
{
size_t pos = mainStr.find(toErase);
if (pos != std::string::npos)
mainStr.erase(pos, toErase.length());
}
bool PositionAggregator::query_pnl(const std::string& account, api::position::AccountPNL& pnl)
{
auto pit = _pnl_map.find(account);
if(pit != _pnl_map.end())
{
pnl = pit->second;
return true;
}
return false;
}
bool PositionAggregator::query_positions(const std::string& account, std::vector<Position>& result,
api::position::AccountPNL& pnl,
datetime::LLDateTime* since)
{
auto last_update = _accounts[account];
if(!since || *since == 0 || bts::datetime::ll_diff_in_us(*since, last_update) > 0)
{
std::scoped_lock<std::recursive_mutex> l(_lock);
for(auto& entry : _position_map)
{
const PositionUnit& unit = entry.second;
if(unit.account() != account)
continue;
auto sec = _oracle.get_security( SecurityKey(unit.security_id()) );
if(sec && sec->has_glbx() && sec->has_future())
{
Position pos;
pos.account = orderman::to<orderman::AccountName>(account);
pos.security_desc = securities::descriptor(*sec);
eraseSubStr(pos.security_desc, "SIM:");
int32_t totalpos = unit.sod_long_pos() + unit.day_long_pos() - unit.sod_short_pos() - unit.day_short_pos();
pos.position = Quantity(abs(totalpos));
pos.side = (totalpos < 0.0 ? api::SIDE_SELL : api::SIDE_BUY);
result.push_back(pos);
}
}
if(since)
*since = last_update;
return query_pnl(account, pnl);
}
return false;
}
}}
@@ -0,0 +1,37 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/md/SecurityOracle.h"
#include "lib/api/services/PositionService.pb.h"
#include "lib/api/services/PositionService.client.pb.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/utils/datetime.h"
#include <map>
#include <memory>
#include <mutex>
namespace bts {
namespace position {
struct Position;
class PositionAggregator
{
public:
PositionAggregator(api::position::PositionService&&, md::MDClient&);
~PositionAggregator();
bool query_positions(const std::string& account, std::vector<Position>& result, api::position::AccountPNL& pnl,
datetime::LLDateTime* since = nullptr);
bool query_pnl(const std::string& account, api::position::AccountPNL& pnl);
private:
api::position::PositionService _position_service;
std::map<int32_t,api::position::PositionUnit> _position_map;
std::map<std::string,api::position::AccountPNL> _pnl_map;
std::map<std::string, datetime::LLDateTime> _accounts;
md::SecurityOracle _oracle;
std::recursive_mutex _lock;
};
}}
@@ -1,141 +0,0 @@
#include "PositionServiceCore.h"
#include "Position.h"
#include "IPositionObserver.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/orderman/securities/ISecurityDB.h"
#include "lib/utils/bts_assert.h"
#include "lib/logging/logging.h"
#include <boost/container/flat_map.hpp>
#include <utility>
namespace bts
{
namespace position
{
template <typename T> int sgn(T val) {
return (T(0) < val) - (val < T(0));
}
class BookEntry
{
public:
orderman::AccountName account;
SecurityKey security_key;
double position;
double avg_price;
public:
BookEntry(const orderman::AccountName& acc, SecurityKey k) :
account(acc), security_key(k), position(0.0), avg_price(0.0)
{
}
void add_fill(double last_size, double last_price, api::Side side)
{
if(side == api::SIDE_SELL)
last_size = -last_size;
if(position == 0.0 || sgn(position) == sgn(last_size))
{
double prev = position * avg_price;
position += last_size;
avg_price = prev + (last_size*last_price) / position;
}
else
position += last_size;
}
};
struct PositionServiceCore::PImpl
{
typedef std::pair<orderman::AccountName, SecurityKey> KeyType;
boost::container::flat_map<KeyType, std::shared_ptr<BookEntry>> book;
IPositionObserver* observer;
PImpl(IPositionObserver* obs) : book(), observer(obs)
{
}
BookEntry& find_entry(const orderman::AccountName& account, SecurityKey skey)
{
KeyType key(account, skey);
auto& entry = book[key];
if(!entry)
entry = std::make_shared<BookEntry>(account, skey);
return *entry;
}
void book_raw_fill(const orderman::AccountName& account, SecurityKey sec,
double last_size, double last_price, api::Side side)
{
auto& entry = find_entry(account, sec);
entry.add_fill(last_size, last_price, side);
if(observer)
observer->on_position_change(account, sec, entry.position, entry.avg_price);
}
};
PositionServiceCore::PositionServiceCore(securities::ISecurityDB& db, IPositionObserver* obs)
: me(new PImpl(obs)), _db(db)
{
}
PositionServiceCore::~PositionServiceCore() {}
bool PositionServiceCore::query_position(const orderman::AccountName& account, SecurityKey seckey, Position& result)
{
auto& entry = me->find_entry(account, seckey);
auto sec = _db.resolve(entry.security_key);
if(sec)
{
result.account = entry.account;
result.avg_price = Price(entry.avg_price);
result.position = Quantity(abs(entry.position));
result.side = (entry.position < 0.0 ? api::SIDE_SELL : api::SIDE_BUY);
result.security = sec;
return true;
}
else
return false;
}
void PositionServiceCore::query_all_positions(const orderman::AccountName& account, std::vector<Position>& result)
{
result.reserve(me->book.size());
for(auto& entry : me->book)
{
if(entry.first.first != account)
continue;
auto sec = _db.resolve(entry.second->security_key);
if(sec)
{
Position pos;
pos.account = entry.second->account;
pos.avg_price = Price(entry.second->avg_price);
pos.position = Quantity(abs(entry.second->position));
pos.side = (entry.second->position < 0.0 ? api::SIDE_SELL : api::SIDE_BUY);
pos.security = sec;
result.push_back(pos);
}
}
}
void PositionServiceCore::consume_raw_fill(const std::string& account, uint32_t security_key,
double last_size, double last_price, api::Side side)
{
orderman::AccountName acc = orderman::to<orderman::AccountName>(account);
SecurityKey skey(security_key);
auto sec = _db.resolve(skey);
if(sec->Contract_case() == api::Security::ContractCase::kSpread)
{
//TODO this is wrong; need the leg fills
for(int i = 0; i < sec->spread().legs_size(); i++)
me->book_raw_fill(acc, SecurityKey(sec->spread().legs(i).security().id()),
0.0, 0.0, side);
}
else
me->book_raw_fill(acc, skey, last_size, last_price, side);
}
}
}
@@ -1,36 +0,0 @@
#pragma once
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/utils/bts_no_copy.h"
#include "lib/domain_model/Security.pb.h"
#include <memory>
namespace bts
{
namespace securities { class ISecurityDB; }
namespace position
{
struct Position;
class IPositionObserver;
class PositionServiceCore
{
BTS_NO_COPY(PositionServiceCore);
public:
PositionServiceCore(securities::ISecurityDB&, IPositionObserver* obs = nullptr);
~PositionServiceCore();
bool query_position(const orderman::AccountName&, SecurityKey, Position&);
void query_all_positions(const orderman::AccountName&, std::vector<Position>&);
private:
void consume_raw_fill(const std::string& account, uint32_t security_key,
double last_size, double last_price, api::Side side);
private:
struct PImpl;
std::unique_ptr<PImpl> me;
securities::ISecurityDB& _db;
};
}
}
@@ -0,0 +1,263 @@
#include "lib/api/services/OrderService.client.pb.h"
#include "lib/logging/logging.h"
#include "lib/md/MDClient.h"
#include "lib/os/os.h"
#include "PositionServicePNLHelper.h"
#include "lib/config/protos/cme_reference_data.pb.h"
#include "lib/config/config.h"
#include "lib/utils/LockBox.h"
#include <vector>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
#include <map>
#include <set>
#include <utility>
namespace bts {
namespace position{
using namespace bts::api::position;
using namespace bts::api::orderman;
using namespace bts::md;
class HandleIndex {
public:
enum PNLType{
UNRESOLVED = 0,
SPREAD,
OUTRIGHT
};
bts::md::MDClient& _client;
int _sub;
std::optional<bts::md::MDConsumer> _consumer;
public:
utils::LockBox<std::unordered_map<SecurityKey,MDConsumerHandle>> index;
HandleIndex(md::MDClient& client):
_client(client),
_sub(0),
_consumer(std::nullopt),
index() {
_sub = _client.on_consumer().subscribe([=](const MDConsumer& c) { on_consumer(c); });
on_consumer(_client.current_consumer());
}
~HandleIndex() { _client.on_consumer().unsubscribe(_sub); }
MDConsumerHandle _get_handle(SecurityKey key) const{
auto handle = index.lock([id=key](const auto& index) {
auto iter = index.find(id);
return iter != index.end() ? iter->second : MDConsumerHandle {};
});
return handle;
}
PNLType get_pnl_type (SecurityKey key) const{
auto h = _get_handle(key);
if (h.is_initialized()){
const auto& sec = h.get_normalized_security();
if (sec.has_future() || sec.has_option())
return PNLType::OUTRIGHT;
else
return PNLType::SPREAD;
}
else
return PNLType::UNRESOLVED;
}
std::string get_symbol (SecurityKey key) const{
auto handle = _get_handle(key);
return handle.get_usersymbol().symbol();
}
void get_md(SecurityKey key,
double& bid, double& ask, double& last, double& settle){
auto handle = _get_handle(key);
bid = bts::os::nan();
ask = bid;
last = bid;
settle = bid;
if(handle.is_initialized()){
TimeStamp time;
PriceSize bid_ps,ask_ps;
handle.get_direct_book(time, 1, &bid_ps, &ask_ps);
bid = bid_ps.price();
ask = ask_ps.price();
PriceSize last_ps;
datetime::LLDateTime trade_time;
handle.get_last_trade(time, last_ps, trade_time, nullptr);
last = last_ps.price();
settle = handle.settlement_price().price();
}
}
private:
void on_consumer(const MDConsumer& c) {
MDConsumer temp(c);
if (temp.all_heartbeats_ok()) {
auto existing = _consumer ? _consumer->id() : 0;
LOG(md,Info, "Updating MBOService MDConsumer. Old {} new {}", existing, temp.id());
// clear the index
index.lock([](auto& index) { index.clear(); });
// Swap to new consumer
if (_consumer) {
_consumer->swap(temp);
}
else { _consumer = temp; }
// Subscribe to on_security
auto id = _consumer->id();
_consumer->on_security([this,id](const SecurityPtr&, MDConsumerHandle handle) {
if (_consumer->id() == id) {
index.lock([handle](auto& index) {
index[SecurityKey(handle.key())] = handle;
});
}
});
}
}
};
struct PositionServicePNLHelper::PImpl {
HandleIndex handle_index;
std::map<std::string,double> symbol_cpp_map;
std::map<uint32_t,double> sec_cpp_map;
PImpl(bts::md::MDClient& md_client)
:
handle_index(md_client),
symbol_cpp_map(),
sec_cpp_map()
{
const auto& cpp_map = config::get<config::CppConfig>().cpp_entry();
LOG(md,Info,"PositionServicePNLHelper::cons loaded {} cpps",cpp_map.size());
for (const auto& p : cpp_map){
symbol_cpp_map[std::string(p.first)]=p.second;
LOG(md,Info,"PositionServicePNLHelper loading cpp's Sym:{} cpp:{}",
p.first,p.second);
}
}
};
void PositionServicePNLHelper::compute_pnl(
const std::vector<const PositionUnit*>& in_pos,
const std::map<std::string,uint32_t>& account_map,
std::vector<PNLUnit>& out_pnl,
std::vector<AccountPNL>& out_account_pnl){
out_account_pnl.clear();
for (const PositionUnit* pu_ptr : in_pos) {
const PositionUnit& pu = *pu_ptr;
uint32_t sec_key = pu.security_id();
double cpp = _get_cpp(sec_key);
double bid, ask, last, settle;
me->handle_index.get_md(SecurityKey(sec_key),bid,ask,last,settle);
PNLUnit pnlu;
pnlu.set_record_id(pu.record_id());
pnlu.mutable_pos()->CopyFrom(pu);
pnlu.set_cash_value(cpp);
pnlu.set_bid_price(bid);
pnlu.set_ask_price(ask);
pnlu.set_last_price(last);
pnlu.set_settlement_price(settle);
out_pnl.push_back(pnlu);
}
_aggregate_account_pnl(out_pnl,account_map,out_account_pnl);
}
void PositionServicePNLHelper::_aggregate_account_pnl(
const std::vector<api::position::PNLUnit>& in_pnl,
const std::map<std::string,uint32_t>& account_map,
std::vector<api::position::AccountPNL>& out_account_pnl){
out_account_pnl.clear();
if (account_map.size() == 0)
return;
std::map<uint32_t, AccountPNL> acc_pnl;
for (const auto& pnlu : in_pnl){
uint32_t acct_id = pnlu.pos().account_id();
const std::string& acct_name = pnlu.pos().account();
auto it=acc_pnl.find(acct_id);
if(it==acc_pnl.end()){
AccountPNL new_acc;
new_acc.set_account_id(acct_id);
new_acc.set_account_name(acct_name);
new_acc.set_day_pnl(0.0);
acc_pnl[acct_id]=new_acc;
it=acc_pnl.find(acct_id);
}
AccountPNL& acc_pnl = it->second;
double pnl_dollars = _compute_day_pnl(pnlu);
acc_pnl.set_day_pnl(acc_pnl.day_pnl()+pnl_dollars);
}
// for accounts with no positions add 0
for (const auto& p : account_map){
const std::string& acct_name = p.first;
uint32_t acct_id = p.second;
if(acc_pnl.find(acct_id) == acc_pnl.end()){
AccountPNL new_acc;
new_acc.set_account_id(acct_id);
new_acc.set_account_name(acct_name);
new_acc.set_day_pnl(0.0);
acc_pnl[acct_id]=new_acc;
}
}
for(auto& u : acc_pnl)
out_account_pnl.push_back(u.second);
}
double PositionServicePNLHelper::_compute_day_pnl (
const bts::api::position::PNLUnit& u){
// long
double pnl;
auto pnl_type = me->handle_index.get_pnl_type(
SecurityKey(u.pos().security_id()));
switch(pnl_type){
case HandleIndex::OUTRIGHT:{
double md = (u.bid_price() + u.ask_price())*0.5;
md = bts::os::is_nan(md) ? u.bid_price() : md;
md = bts::os::is_nan(md) ? u.ask_price() : md;
md = bts::os::is_nan(md) ? u.settlement_price() : md;
double long_pnl = std::abs(u.pos().day_long_pos()) * u.cash_value() *
(md - u.pos().day_long_avg_price()),
short_pnl = std::abs(u.pos().day_short_pos()) * u.cash_value() *
(u.pos().day_short_avg_price() - md);
pnl = long_pnl + short_pnl;
break;
}
case HandleIndex::SPREAD:
pnl = 0.0;
break;
case HandleIndex::UNRESOLVED:
pnl = os::nan();
break;
}
return pnl;
}
double PositionServicePNLHelper::_get_cpp(uint32_t sec_key){
auto it = me->sec_cpp_map.find(sec_key);
if(it == me->sec_cpp_map.end()){
// try to find it and put the value there
std::string symbol = me->handle_index.get_symbol(SecurityKey(sec_key));
auto cppit = me->symbol_cpp_map.find(symbol);
if(cppit == me->symbol_cpp_map.end()){
LOG(md,Warn,"Could not find cpp for key:{} symbol:{}",
sec_key,symbol);
return bts::os::nan();
}
else{
double cpp = cppit->second;
me->sec_cpp_map[sec_key]=cpp;
return cpp;
}
}
else
return it->second;
}
PositionServicePNLHelper::PositionServicePNLHelper(md::MDClient& md_client)
:
me(new PImpl(md_client)){
}
PositionServicePNLHelper::~PositionServicePNLHelper(){
}
}}
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more